mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-23 00:36:53 +00:00
Add pinned messages section.
This commit is contained in:
parent
aefef948cd
commit
fc92e3fadd
@ -533,6 +533,8 @@ PRIVATE
|
||||
history/view/history_view_object.h
|
||||
history/view/history_view_pinned_bar.cpp
|
||||
history/view/history_view_pinned_bar.h
|
||||
history/view/history_view_pinned_section.cpp
|
||||
history/view/history_view_pinned_section.h
|
||||
history/view/history_view_pinned_tracker.cpp
|
||||
history/view/history_view_pinned_tracker.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
|
@ -383,7 +383,6 @@ void ChannelData::setUnavailableReasons(
|
||||
void ChannelData::setAvailableMinId(MsgId availableMinId) {
|
||||
if (_availableMinId != availableMinId) {
|
||||
_availableMinId = availableMinId;
|
||||
clearPinnedMessages(_availableMinId + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "facades.h" // Ui::showPeerProfile
|
||||
#include "app.h"
|
||||
|
||||
@ -468,14 +470,15 @@ MsgId PeerData::topPinnedMessageId() const {
|
||||
|
||||
void PeerData::ensurePinnedMessagesCreated() {
|
||||
if (!_pinnedMessages) {
|
||||
_pinnedMessages = std::make_unique<Data::PinnedMessages>(
|
||||
peerToChannel(id));
|
||||
_pinnedMessages = std::make_unique<Data::PinnedMessages>(this);
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::removeEmptyPinnedMessages() {
|
||||
if (_pinnedMessages && _pinnedMessages->empty()) {
|
||||
_pinnedMessages = nullptr;
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,69 +494,24 @@ void PeerData::setTopPinnedMessageId(MsgId messageId) {
|
||||
clearPinnedMessages();
|
||||
return;
|
||||
}
|
||||
if (session().settings().hiddenPinnedMessageId(id) != messageId) {
|
||||
const auto hiddenId = session().settings().hiddenPinnedMessageId(id);
|
||||
if (hiddenId != 0 && hiddenId != messageId) {
|
||||
session().settings().setHiddenPinnedMessageId(id, 0);
|
||||
session().saveSettingsDelayed();
|
||||
}
|
||||
ensurePinnedMessagesCreated();
|
||||
_pinnedMessages->setTopId(messageId);
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
|
||||
void PeerData::clearPinnedMessages(MsgId lessThanId) {
|
||||
if (lessThanId == ServerMaxMsgId
|
||||
&& session().settings().hiddenPinnedMessageId(id) != 0) {
|
||||
void PeerData::clearPinnedMessages() {
|
||||
if (session().settings().hiddenPinnedMessageId(id) != 0) {
|
||||
session().settings().setHiddenPinnedMessageId(id, 0);
|
||||
session().saveSettingsDelayed();
|
||||
}
|
||||
if (!_pinnedMessages) {
|
||||
return;
|
||||
}
|
||||
_pinnedMessages->clearLessThanId(lessThanId);
|
||||
session().storage().remove(Storage::SharedMediaRemoveAll(
|
||||
id,
|
||||
Storage::SharedMediaType::Pinned));
|
||||
removeEmptyPinnedMessages();
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
|
||||
void PeerData::addPinnedMessage(MsgId messageId) {
|
||||
if (messageIdTooSmall(messageId)) {
|
||||
return;
|
||||
}
|
||||
ensurePinnedMessagesCreated();
|
||||
_pinnedMessages->add(messageId);
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
|
||||
void PeerData::addPinnedSlice(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgRange noSkipRange,
|
||||
std::optional<int> count) {
|
||||
const auto min = [&] {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->availableMinId();
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
ids.erase(
|
||||
ranges::remove_if(ids, [&](MsgId id) { return id <= min; }),
|
||||
end(ids));
|
||||
if (noSkipRange.from <= min) {
|
||||
noSkipRange.from = 0;
|
||||
}
|
||||
if (ids.empty() && !_pinnedMessages) {
|
||||
return;
|
||||
}
|
||||
ensurePinnedMessagesCreated();
|
||||
_pinnedMessages->add(std::move(ids), noSkipRange, count);
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
|
||||
void PeerData::removePinnedMessage(MsgId messageId) {
|
||||
if (!_pinnedMessages) {
|
||||
return;
|
||||
}
|
||||
_pinnedMessages->remove(messageId);
|
||||
removeEmptyPinnedMessages();
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
|
||||
bool PeerData::canExportChatHistory() const {
|
||||
|
@ -330,13 +330,7 @@ public:
|
||||
[[nodiscard]] bool canEditMessagesIndefinitely() const;
|
||||
[[nodiscard]] MsgId topPinnedMessageId() const;
|
||||
void setTopPinnedMessageId(MsgId messageId);
|
||||
void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId);
|
||||
void addPinnedMessage(MsgId messageId);
|
||||
void addPinnedSlice(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgRange noSkipRange,
|
||||
std::optional<int> count);
|
||||
void removePinnedMessage(MsgId messageId);
|
||||
void clearPinnedMessages();
|
||||
Data::PinnedMessages *currentPinnedMessages() const {
|
||||
return _pinnedMessages.get();
|
||||
}
|
||||
|
@ -7,94 +7,76 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_pinned_messages.h"
|
||||
|
||||
namespace Data {
|
||||
#include "data/data_peer.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
|
||||
PinnedMessages::PinnedMessages(ChannelId channelId) : _channelId(channelId) {
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto PinnedType = Storage::SharedMediaType::Pinned;
|
||||
|
||||
using Storage::SharedMediaQuery;
|
||||
using Storage::SharedMediaKey;
|
||||
using Storage::SharedMediaResult;
|
||||
|
||||
} // namespace
|
||||
|
||||
PinnedMessages::PinnedMessages(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _storage(_peer->session().storage()) {
|
||||
}
|
||||
|
||||
bool PinnedMessages::empty() const {
|
||||
return _list.empty();
|
||||
return _storage.empty(SharedMediaKey(_peer->id, PinnedType, 0));
|
||||
}
|
||||
|
||||
MsgId PinnedMessages::topId() const {
|
||||
const auto slice = _list.snapshot(MessagesQuery{
|
||||
.aroundId = MaxMessagePosition,
|
||||
.limitBefore = 1,
|
||||
.limitAfter = 1
|
||||
});
|
||||
return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg;
|
||||
const auto slice = _storage.snapshot(
|
||||
SharedMediaQuery(
|
||||
SharedMediaKey(_peer->id, PinnedType, ServerMaxMsgId),
|
||||
1,
|
||||
1));
|
||||
return slice.messageIds.empty() ? 0 : slice.messageIds.back();
|
||||
}
|
||||
|
||||
rpl::producer<PinnedAroundId> PinnedMessages::viewer(
|
||||
MsgId aroundId,
|
||||
int limit) const {
|
||||
return _list.viewer(MessagesQuery{
|
||||
.aroundId = position(aroundId),
|
||||
.limitBefore = limit,
|
||||
.limitAfter = limit
|
||||
}) | rpl::map([](const MessagesResult &result) {
|
||||
return _storage.query(
|
||||
SharedMediaQuery(
|
||||
SharedMediaKey(_peer->id, PinnedType, aroundId),
|
||||
limit,
|
||||
limit)
|
||||
) | rpl::map([](const SharedMediaResult &result) {
|
||||
auto data = PinnedAroundId();
|
||||
data.fullCount = result.count;
|
||||
data.skippedBefore = result.skippedBefore;
|
||||
data.skippedAfter = result.skippedAfter;
|
||||
data.ids = result.messageIds | ranges::view::transform(
|
||||
[](MessagePosition position) { return position.fullId.msg; }
|
||||
) | ranges::to_vector;
|
||||
data.ids = result.messageIds | ranges::to_vector;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
MessagePosition PinnedMessages::position(MsgId id) const {
|
||||
return MessagePosition{
|
||||
.fullId = FullMsgId(_channelId, id),
|
||||
};
|
||||
}
|
||||
|
||||
void PinnedMessages::add(MsgId messageId) {
|
||||
_list.addOne(position(messageId));
|
||||
}
|
||||
|
||||
void PinnedMessages::add(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgRange range,
|
||||
std::optional<int> count) {
|
||||
auto positions = ids | ranges::view::transform([&](MsgId id) {
|
||||
return position(id);
|
||||
}) | ranges::to_vector;
|
||||
|
||||
_list.addSlice(
|
||||
std::move(positions),
|
||||
MessagesRange{
|
||||
.from = range.from ? position(range.from) : MinMessagePosition,
|
||||
.till = position(range.till)
|
||||
},
|
||||
count);
|
||||
}
|
||||
|
||||
void PinnedMessages::remove(MsgId messageId) {
|
||||
_list.removeOne(position(messageId));
|
||||
}
|
||||
|
||||
void PinnedMessages::setTopId(MsgId messageId) {
|
||||
while (true) {
|
||||
auto top = topId();
|
||||
if (top > messageId) {
|
||||
remove(top);
|
||||
_storage.remove(Storage::SharedMediaRemoveOne(
|
||||
_peer->id,
|
||||
PinnedType,
|
||||
top));
|
||||
} else if (top == messageId) {
|
||||
return;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto wrapped = position(messageId);
|
||||
_list.addSlice(
|
||||
{ wrapped },
|
||||
{ .from = wrapped, .till = MaxMessagePosition },
|
||||
std::nullopt);
|
||||
}
|
||||
|
||||
void PinnedMessages::clearLessThanId(MsgId messageId) {
|
||||
_list.removeLessThan(position(messageId));
|
||||
_storage.add(Storage::SharedMediaAddNew(
|
||||
_peer->id,
|
||||
PinnedType,
|
||||
messageId));
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_messages.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
namespace Storage {
|
||||
class Facade;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct PinnedAroundId {
|
||||
@ -21,30 +25,18 @@ struct PinnedAroundId {
|
||||
|
||||
class PinnedMessages final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit PinnedMessages(ChannelId channelId);
|
||||
explicit PinnedMessages(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] MsgId topId() const;
|
||||
[[nodiscard]] rpl::producer<PinnedAroundId> viewer(
|
||||
MsgId aroundId,
|
||||
int limit) const;
|
||||
|
||||
void add(MsgId messageId);
|
||||
void add(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgRange range,
|
||||
std::optional<int> count);
|
||||
void remove(MsgId messageId);
|
||||
|
||||
void setTopId(MsgId messageId);
|
||||
|
||||
void clearLessThanId(MsgId messageId);
|
||||
|
||||
private:
|
||||
[[nodiscard]] MessagePosition position(MsgId id) const;
|
||||
|
||||
MessagesList _list;
|
||||
ChannelId _channelId = 0;
|
||||
const not_null<PeerData*> _peer;
|
||||
Storage::Facade &_storage;
|
||||
|
||||
};
|
||||
|
||||
|
@ -55,6 +55,8 @@ std::optional<MTPmessages_Search> PrepareSearchRequest(
|
||||
return MTP_inputMessagesFilterUrl();
|
||||
case Type::ChatPhoto:
|
||||
return MTP_inputMessagesFilterChatPhotos();
|
||||
case Type::Pinned:
|
||||
return MTP_inputMessagesFilterPinned();
|
||||
}
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
|
@ -123,7 +123,8 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
using AllRemoved = Storage::SharedMediaRemoveAll;
|
||||
session->storage().sharedMediaAllRemoved(
|
||||
) | rpl::filter([=](const AllRemoved &update) {
|
||||
return (update.peerId == key.peerId);
|
||||
return (update.peerId == key.peerId)
|
||||
&& (update.types.test(key.type));
|
||||
}) | rpl::filter([=] {
|
||||
return builder->removeAll();
|
||||
}) | rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
@ -177,7 +177,6 @@ void History::itemVanished(not_null<HistoryItem*> item) {
|
||||
&& unreadCount() > 0) {
|
||||
setUnreadCount(unreadCount() - 1);
|
||||
}
|
||||
peer->removePinnedMessage(item->id);
|
||||
}
|
||||
|
||||
void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
|
||||
@ -707,9 +706,6 @@ not_null<HistoryItem*> History::addNewToBack(
|
||||
item->id,
|
||||
{ from, till }));
|
||||
}
|
||||
if (item->isPinned()) {
|
||||
item->history()->peer->addPinnedMessage(item->id);
|
||||
}
|
||||
}
|
||||
if (item->from()->id) {
|
||||
if (auto user = item->from()->asUser()) {
|
||||
@ -1007,10 +1003,11 @@ void History::applyServiceChanges(
|
||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||
const auto id = data.vreply_to_msg_id().v;
|
||||
if (item) {
|
||||
item->history()->peer->addPinnedSlice(
|
||||
session().storage().add(Storage::SharedMediaAddSlice(
|
||||
peer->id,
|
||||
Storage::SharedMediaType::Pinned,
|
||||
{ id },
|
||||
{ id, ServerMaxMsgId },
|
||||
std::nullopt);
|
||||
{ id, ServerMaxMsgId }));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1164,7 +1161,6 @@ void History::addEdgesToSharedMedia() {
|
||||
{},
|
||||
{ from, till }));
|
||||
}
|
||||
peer->addPinnedSlice({}, { from, till }, std::nullopt);
|
||||
}
|
||||
|
||||
void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
@ -1328,7 +1324,6 @@ void History::checkAddAllToUnreadMentions() {
|
||||
|
||||
void History::addToSharedMedia(
|
||||
const std::vector<not_null<HistoryItem*>> &items) {
|
||||
auto pinned = std::vector<MsgId>();
|
||||
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
|
||||
for (const auto item : items) {
|
||||
if (const auto types = item->sharedMediaTypes()) {
|
||||
@ -1342,12 +1337,6 @@ void History::addToSharedMedia(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item->isPinned()) {
|
||||
if (pinned.empty()) {
|
||||
pinned.reserve(items.size());
|
||||
}
|
||||
pinned.push_back(item->id);
|
||||
}
|
||||
}
|
||||
const auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
@ -1361,7 +1350,6 @@ void History::addToSharedMedia(
|
||||
{ from, till }));
|
||||
}
|
||||
}
|
||||
peer->addPinnedSlice(std::move(pinned), { from, till }, std::nullopt);
|
||||
}
|
||||
|
||||
void History::calculateFirstUnreadMessage() {
|
||||
@ -3029,7 +3017,6 @@ void History::clear(ClearType type) {
|
||||
clearSharedMedia();
|
||||
clearLastKeyboard();
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
channel->clearPinnedMessages();
|
||||
//if (const auto feed = channel->feed()) { // #feed
|
||||
// // Should be after resetting the _lastMessage.
|
||||
// feed->historyCleared(this);
|
||||
|
@ -350,10 +350,17 @@ void HistoryItem::markMediaRead() {
|
||||
void HistoryItem::setIsPinned(bool pinned) {
|
||||
if (pinned) {
|
||||
_flags |= MTPDmessage::Flag::f_pinned;
|
||||
history()->peer->addPinnedMessage(id);
|
||||
history()->session().storage().add(Storage::SharedMediaAddExisting(
|
||||
history()->peer->id,
|
||||
Storage::SharedMediaType::Pinned,
|
||||
id,
|
||||
{ id, id }));
|
||||
} else {
|
||||
_flags &= ~MTPDmessage::Flag::f_pinned;
|
||||
history()->peer->removePinnedMessage(id);
|
||||
history()->session().storage().remove(Storage::SharedMediaRemoveOne(
|
||||
history()->peer->id,
|
||||
Storage::SharedMediaType::Pinned,
|
||||
id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1409,6 +1409,9 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
|
||||
if (hasTextLinks()) {
|
||||
result.set(Storage::SharedMediaType::Link);
|
||||
}
|
||||
if (isPinned()) {
|
||||
result.set(Storage::SharedMediaType::Pinned);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_top_bar_widget.h"
|
||||
#include "history/view/history_view_contact_status.h"
|
||||
#include "history/view/history_view_pinned_tracker.h"
|
||||
#include "history/view/history_view_pinned_section.h"
|
||||
#include "history/view/history_view_pinned_bar.h"
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "profile/profile_block_group_members.h"
|
||||
@ -108,6 +109,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_slide_animation.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "inline_bots/inline_results_widget.h"
|
||||
#include "info/profile/info_profile_values.h" // SharedMediaCountValue.
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "core/crash_reports.h"
|
||||
#include "core/shortcuts.h"
|
||||
@ -3219,6 +3221,9 @@ void HistoryWidget::doneShow() {
|
||||
}
|
||||
preloadHistoryIfNeeded();
|
||||
updatePinnedViewer();
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
checkHistoryActivation();
|
||||
App::wnd()->setInnerFocus();
|
||||
}
|
||||
@ -5177,10 +5182,9 @@ void HistoryWidget::updatePinnedViewer() {
|
||||
}
|
||||
return std::pair(item, offset);
|
||||
}();
|
||||
const auto last = _history->peer->topPinnedMessageId();
|
||||
const auto lessThanId = item
|
||||
? (item->data()->id + (offset > 0 ? 1 : 0))
|
||||
: (last + 1);
|
||||
: (_history->peer->topPinnedMessageId() + 1);
|
||||
_pinnedTracker->trackAround(lessThanId);
|
||||
}
|
||||
|
||||
@ -5189,7 +5193,13 @@ void HistoryWidget::setupPinnedTracker() {
|
||||
|
||||
_pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history);
|
||||
_pinnedBar = nullptr;
|
||||
checkPinnedBarState();
|
||||
Info::Profile::SharedMediaCountValue(
|
||||
_history->peer,
|
||||
_migrated ? _migrated->peer.get() : nullptr,
|
||||
Storage::SharedMediaType::Pinned
|
||||
) | rpl::start_with_next([=] {
|
||||
checkPinnedBarState();
|
||||
}, _pinnedTracker->lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::checkPinnedBarState() {
|
||||
@ -5258,7 +5268,9 @@ void HistoryWidget::checkPinnedBarState() {
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto id = _pinnedTracker->currentMessageId();
|
||||
if (id.message) {
|
||||
Ui::showPeerHistory(_peer, id.message);
|
||||
controller()->showSection(
|
||||
HistoryView::PinnedMemento(_history, id.message));
|
||||
//Ui::showPeerHistory(_peer, id.message);
|
||||
}
|
||||
}, _pinnedBar->lifetime());
|
||||
|
||||
@ -5543,8 +5555,6 @@ void HistoryWidget::UnpinMessage(not_null<PeerData*> peer, MsgId msgId) {
|
||||
|
||||
const auto session = &peer->session();
|
||||
Ui::show(Box<ConfirmBox>(tr::lng_pinned_unpin_sure(tr::now), tr::lng_pinned_unpin(tr::now), crl::guard(session, [=] {
|
||||
peer->removePinnedMessage(msgId);
|
||||
|
||||
Ui::hideLayer();
|
||||
session->api().request(MTPmessages_UpdatePinnedMessage(
|
||||
MTP_flags(MTPmessages_UpdatePinnedMessage::Flag::f_unpin),
|
||||
|
@ -0,0 +1,594 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "history/view/history_view_pinned_section.h"
|
||||
|
||||
#include "history/view/history_view_top_bar_widget.h"
|
||||
#include "history/view/history_view_list_widget.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/item_text_options.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "apiwrap.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_pinned_messages.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_sparse_ids.h"
|
||||
#include "data/data_shared_media.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
object_ptr<Window::SectionWidget> PinnedMemento::createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::Column column,
|
||||
const QRect &geometry) {
|
||||
if (column == Window::Column::Third) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = object_ptr<PinnedWidget>(
|
||||
parent,
|
||||
controller,
|
||||
_history);
|
||||
result->setInternalState(geometry, this);
|
||||
return result;
|
||||
}
|
||||
|
||||
PinnedWidget::PinnedWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> history)
|
||||
: Window::SectionWidget(parent, controller)
|
||||
, _history(history)
|
||||
, _topBar(this, controller)
|
||||
, _topBarShadow(this)
|
||||
, _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false))
|
||||
, _scrollDown(_scroll.get(), st::historyToDown) {
|
||||
_topBar->setActiveChat(
|
||||
_history,
|
||||
TopBarWidget::Section::Pinned,
|
||||
nullptr);
|
||||
|
||||
_topBar->move(0, 0);
|
||||
_topBar->resizeToWidth(width());
|
||||
_topBar->show();
|
||||
|
||||
_topBar->deleteSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
confirmDeleteSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->forwardSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
confirmForwardSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->clearSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
clearSelected();
|
||||
}, _topBar->lifetime());
|
||||
|
||||
_topBarShadow->raise();
|
||||
updateAdaptiveLayout();
|
||||
subscribe(Adaptive::Changed(), [=] { updateAdaptiveLayout(); });
|
||||
|
||||
_inner = _scroll->setOwnedWidget(object_ptr<ListWidget>(
|
||||
this,
|
||||
controller,
|
||||
static_cast<ListDelegate*>(this)));
|
||||
_scroll->move(0, _topBar->height());
|
||||
_scroll->show();
|
||||
connect(_scroll.get(), &Ui::ScrollArea::scrolled, [=] { onScroll(); });
|
||||
|
||||
setupScrollDownButton();
|
||||
}
|
||||
|
||||
PinnedWidget::~PinnedWidget() = default;
|
||||
|
||||
void PinnedWidget::setupScrollDownButton() {
|
||||
_scrollDown->setClickedCallback([=] {
|
||||
scrollDownClicked();
|
||||
});
|
||||
base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) {
|
||||
if (event->type() != QEvent::Wheel) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
return _scroll->viewportEvent(event)
|
||||
? base::EventFilterResult::Cancel
|
||||
: base::EventFilterResult::Continue;
|
||||
});
|
||||
updateScrollDownVisibility();
|
||||
}
|
||||
|
||||
void PinnedWidget::scrollDownClicked() {
|
||||
if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
|
||||
showAtEnd();
|
||||
//} else if (_replyReturn) {
|
||||
// showAtPosition(_replyReturn->position());
|
||||
} else {
|
||||
showAtEnd();
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::showAtStart() {
|
||||
showAtPosition(Data::MinMessagePosition);
|
||||
}
|
||||
|
||||
void PinnedWidget::showAtEnd() {
|
||||
showAtPosition(Data::MaxMessagePosition);
|
||||
}
|
||||
|
||||
void PinnedWidget::showAtPosition(
|
||||
Data::MessagePosition position,
|
||||
HistoryItem *originItem) {
|
||||
if (!showAtPositionNow(position, originItem)) {
|
||||
_inner->showAroundPosition(position, [=] {
|
||||
return showAtPositionNow(position, originItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool PinnedWidget::showAtPositionNow(
|
||||
Data::MessagePosition position,
|
||||
HistoryItem *originItem) {
|
||||
const auto item = position.fullId
|
||||
? _history->owner().message(position.fullId)
|
||||
: nullptr;
|
||||
const auto use = item ? item->position() : position;
|
||||
if (const auto scrollTop = _inner->scrollTopForPosition(use)) {
|
||||
const auto currentScrollTop = _scroll->scrollTop();
|
||||
const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax());
|
||||
const auto fullDelta = (wanted - currentScrollTop);
|
||||
const auto limit = _scroll->height();
|
||||
const auto scrollDelta = snap(fullDelta, -limit, limit);
|
||||
_inner->animatedScrollTo(
|
||||
wanted,
|
||||
use,
|
||||
scrollDelta,
|
||||
(std::abs(fullDelta) > limit
|
||||
? HistoryView::ListWidget::AnimatedScroll::Part
|
||||
: HistoryView::ListWidget::AnimatedScroll::Full));
|
||||
if (use != Data::MaxMessagePosition
|
||||
&& use != Data::UnreadMessagePosition) {
|
||||
_inner->highlightMessage(use.fullId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PinnedWidget::updateScrollDownVisibility() {
|
||||
if (animating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scrollDownIsVisible = [&]() -> std::optional<bool> {
|
||||
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
|
||||
if (top < _scroll->scrollTopMax()) {
|
||||
return true;
|
||||
} else if (_inner->loadedAtBottomKnown()) {
|
||||
return !_inner->loadedAtBottom();
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
const auto scrollDownIsShown = scrollDownIsVisible();
|
||||
if (!scrollDownIsShown) {
|
||||
return;
|
||||
}
|
||||
if (_scrollDownIsShown != *scrollDownIsShown) {
|
||||
_scrollDownIsShown = *scrollDownIsShown;
|
||||
_scrollDownShown.start(
|
||||
[=] { updateScrollDownPosition(); },
|
||||
_scrollDownIsShown ? 0. : 1.,
|
||||
_scrollDownIsShown ? 1. : 0.,
|
||||
st::historyToDownDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::updateScrollDownPosition() {
|
||||
// _scrollDown is a child widget of _scroll, not me.
|
||||
auto top = anim::interpolate(
|
||||
0,
|
||||
_scrollDown->height() + st::historyToDownPosition.y(),
|
||||
_scrollDownShown.value(_scrollDownIsShown ? 1. : 0.));
|
||||
_scrollDown->moveToRight(
|
||||
st::historyToDownPosition.x(),
|
||||
_scroll->height() - top);
|
||||
auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating();
|
||||
if (shouldBeHidden != _scrollDown->isHidden()) {
|
||||
_scrollDown->setVisible(!shouldBeHidden);
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::scrollDownAnimationFinish() {
|
||||
_scrollDownShown.stop();
|
||||
updateScrollDownPosition();
|
||||
}
|
||||
|
||||
void PinnedWidget::updateAdaptiveLayout() {
|
||||
_topBarShadow->moveToLeft(
|
||||
Adaptive::OneColumn() ? 0 : st::lineWidth,
|
||||
_topBar->height());
|
||||
}
|
||||
|
||||
not_null<History*> PinnedWidget::history() const {
|
||||
return _history;
|
||||
}
|
||||
|
||||
Dialogs::RowDescriptor PinnedWidget::activeChat() const {
|
||||
return {
|
||||
_history,
|
||||
FullMsgId(_history->channelId(), ShowAtUnreadMsgId)
|
||||
};
|
||||
}
|
||||
|
||||
QPixmap PinnedWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) {
|
||||
_topBar->updateControlsVisibility();
|
||||
if (params.withTopBarShadow) _topBarShadow->hide();
|
||||
auto result = Ui::GrabWidget(this);
|
||||
if (params.withTopBarShadow) _topBarShadow->show();
|
||||
return result;
|
||||
}
|
||||
|
||||
void PinnedWidget::doSetInnerFocus() {
|
||||
if (!_inner->getSelectedText().rich.text.isEmpty()
|
||||
|| !_inner->getSelectedItems().empty()) {
|
||||
_inner->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool PinnedWidget::showInternal(
|
||||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms) {
|
||||
if (auto logMemento = dynamic_cast<PinnedMemento*>(memento.get())) {
|
||||
if (logMemento->getHistory() == history()) {
|
||||
restoreState(logMemento);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PinnedWidget::setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<PinnedMemento*> memento) {
|
||||
setGeometry(geometry);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
restoreState(memento);
|
||||
}
|
||||
|
||||
std::unique_ptr<Window::SectionMemento> PinnedWidget::createMemento() {
|
||||
auto result = std::make_unique<PinnedMemento>(history());
|
||||
saveState(result.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PinnedWidget::showMessage(
|
||||
PeerId peerId,
|
||||
const Window::SectionShow ¶ms,
|
||||
MsgId messageId) {
|
||||
if (peerId != _history->peer->id) {
|
||||
return false;
|
||||
}
|
||||
const auto id = FullMsgId{
|
||||
_history->channelId(),
|
||||
messageId
|
||||
};
|
||||
const auto message = _history->owner().message(id);
|
||||
if (!message || !message->isPinned()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto originItem = [&]() -> HistoryItem* {
|
||||
using OriginMessage = Window::SectionShow::OriginMessage;
|
||||
if (const auto origin = std::get_if<OriginMessage>(¶ms.origin)) {
|
||||
if (const auto returnTo = session().data().message(origin->id)) {
|
||||
if (returnTo->history() == _history && returnTo->isPinned()) {
|
||||
return returnTo;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
showAtPosition(
|
||||
Data::MessagePosition{ .fullId = id, .date = message->date() },
|
||||
originItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PinnedWidget::saveState(not_null<PinnedMemento*> memento) {
|
||||
_inner->saveState(memento->list());
|
||||
}
|
||||
|
||||
void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) {
|
||||
_inner->restoreState(memento->list());
|
||||
if (const auto highlight = memento->getHighlightId()) {
|
||||
const auto position = Data::MessagePosition{
|
||||
.fullId = FullMsgId(_history->channelId(), highlight),
|
||||
.date = TimeId(0),
|
||||
};
|
||||
_inner->showAroundPosition(position, [=] {
|
||||
return showAtPositionNow(position, nullptr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::resizeEvent(QResizeEvent *e) {
|
||||
if (!width() || !height()) {
|
||||
return;
|
||||
}
|
||||
recountChatWidth();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
||||
void PinnedWidget::recountChatWidth() {
|
||||
auto layout = (width() < st::adaptiveChatWideWidth)
|
||||
? Adaptive::ChatLayout::Normal
|
||||
: Adaptive::ChatLayout::Wide;
|
||||
if (layout != Global::AdaptiveChatLayout()) {
|
||||
Global::SetAdaptiveChatLayout(layout);
|
||||
Adaptive::Changed().notify(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::updateControlsGeometry() {
|
||||
const auto contentWidth = width();
|
||||
|
||||
const auto newScrollTop = _scroll->isHidden()
|
||||
? std::nullopt
|
||||
: base::make_optional(_scroll->scrollTop() + topDelta());
|
||||
_topBar->resizeToWidth(contentWidth);
|
||||
_topBarShadow->resize(contentWidth, st::lineWidth);
|
||||
|
||||
const auto bottom = height();
|
||||
const auto controlsHeight = 0;
|
||||
const auto scrollY = _topBar->height();
|
||||
const auto scrollHeight = bottom - scrollY - controlsHeight;
|
||||
const auto scrollSize = QSize(contentWidth, scrollHeight);
|
||||
if (_scroll->size() != scrollSize) {
|
||||
_skipScrollEvent = true;
|
||||
_scroll->resize(scrollSize);
|
||||
_inner->resizeToWidth(scrollSize.width(), _scroll->height());
|
||||
_skipScrollEvent = false;
|
||||
}
|
||||
_scroll->move(0, scrollY);
|
||||
if (!_scroll->isHidden()) {
|
||||
if (newScrollTop) {
|
||||
_scroll->scrollToY(*newScrollTop);
|
||||
}
|
||||
updateInnerVisibleArea();
|
||||
}
|
||||
updateScrollDownPosition();
|
||||
}
|
||||
|
||||
void PinnedWidget::paintEvent(QPaintEvent *e) {
|
||||
if (animating()) {
|
||||
SectionWidget::paintEvent(e);
|
||||
return;
|
||||
} else if (Ui::skipPaintEvent(this, e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto aboveHeight = _topBar->height();
|
||||
const auto bg = e->rect().intersected(
|
||||
QRect(0, aboveHeight, width(), height() - aboveHeight));
|
||||
SectionWidget::PaintBackground(controller(), this, bg);
|
||||
}
|
||||
|
||||
void PinnedWidget::onScroll() {
|
||||
if (_skipScrollEvent) {
|
||||
return;
|
||||
}
|
||||
updateInnerVisibleArea();
|
||||
}
|
||||
|
||||
void PinnedWidget::updateInnerVisibleArea() {
|
||||
const auto scrollTop = _scroll->scrollTop();
|
||||
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
|
||||
updateScrollDownVisibility();
|
||||
}
|
||||
|
||||
void PinnedWidget::showAnimatedHook(
|
||||
const Window::SectionSlideParams ¶ms) {
|
||||
_topBar->setAnimatingMode(true);
|
||||
if (params.withTopBarShadow) {
|
||||
_topBarShadow->show();
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::showFinishedHook() {
|
||||
_topBar->setAnimatingMode(false);
|
||||
}
|
||||
|
||||
bool PinnedWidget::floatPlayerHandleWheelEvent(QEvent *e) {
|
||||
return _scroll->viewportEvent(e);
|
||||
}
|
||||
|
||||
QRect PinnedWidget::floatPlayerAvailableRect() {
|
||||
return mapToGlobal(_scroll->geometry());
|
||||
}
|
||||
|
||||
Context PinnedWidget::listContext() {
|
||||
return Context::Replies;
|
||||
}
|
||||
|
||||
void PinnedWidget::listScrollTo(int top) {
|
||||
if (_scroll->scrollTop() != top) {
|
||||
_scroll->scrollToY(top);
|
||||
} else {
|
||||
updateInnerVisibleArea();
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedWidget::listCancelRequest() {
|
||||
if (_inner && !_inner->getSelectedItems().empty()) {
|
||||
clearSelected();
|
||||
return;
|
||||
}
|
||||
controller()->showBackFromStack();
|
||||
}
|
||||
|
||||
void PinnedWidget::listDeleteRequest() {
|
||||
confirmDeleteSelected();
|
||||
}
|
||||
|
||||
rpl::producer<Data::MessagesSlice> PinnedWidget::listSource(
|
||||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
const auto channelId = peerToChannel(_history->peer->id);
|
||||
const auto messageId = aroundId.fullId.msg
|
||||
? aroundId.fullId.msg
|
||||
: (ServerMaxMsgId - 1);
|
||||
return SharedMediaViewer(
|
||||
&_history->session(),
|
||||
Storage::SharedMediaKey(
|
||||
_history->peer->id,
|
||||
Storage::SharedMediaType::Pinned,
|
||||
messageId),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::map([=](SparseIdsSlice &&slice) {
|
||||
auto result = Data::MessagesSlice();
|
||||
result.fullCount = slice.fullCount();
|
||||
result.skippedAfter = slice.skippedAfter();
|
||||
result.skippedBefore = slice.skippedBefore();
|
||||
const auto count = slice.size();
|
||||
result.ids.reserve(count);
|
||||
if (const auto msgId = slice.nearest(messageId)) {
|
||||
result.nearestToAround = FullMsgId(channelId, *msgId);
|
||||
}
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
result.ids.emplace_back(channelId, slice[i]);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
bool PinnedWidget::listAllowsMultiSelect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PinnedWidget::listIsItemGoodForSelection(
|
||||
not_null<HistoryItem*> item) {
|
||||
return !item->isSending() && !item->hasFailed();
|
||||
}
|
||||
|
||||
bool PinnedWidget::listIsLessInOrder(
|
||||
not_null<HistoryItem*> first,
|
||||
not_null<HistoryItem*> second) {
|
||||
return first->position() < second->position();
|
||||
}
|
||||
|
||||
void PinnedWidget::listSelectionChanged(SelectedItems &&items) {
|
||||
HistoryView::TopBarWidget::SelectedState state;
|
||||
state.count = items.size();
|
||||
for (const auto item : items) {
|
||||
if (item.canDelete) {
|
||||
++state.canDeleteCount;
|
||||
}
|
||||
if (item.canForward) {
|
||||
++state.canForwardCount;
|
||||
}
|
||||
}
|
||||
_topBar->showSelected(state);
|
||||
}
|
||||
|
||||
void PinnedWidget::listVisibleItemsChanged(HistoryItemsList &&items) {
|
||||
}
|
||||
|
||||
MessagesBarData PinnedWidget::listMessagesBar(
|
||||
const std::vector<not_null<Element*>> &elements) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void PinnedWidget::listContentRefreshed() {
|
||||
}
|
||||
|
||||
ClickHandlerPtr PinnedWidget::listDateLink(not_null<Element*> view) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PinnedWidget::listElementHideReply(not_null<const Element*> view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PinnedWidget::listElementShownUnread(not_null<const Element*> view) {
|
||||
return view->data()->unread();
|
||||
}
|
||||
|
||||
bool PinnedWidget::listIsGoodForAroundPosition(
|
||||
not_null<const Element*> view) {
|
||||
return IsServerMsgId(view->data()->id);
|
||||
}
|
||||
|
||||
void PinnedWidget::confirmDeleteSelected() {
|
||||
auto items = _inner->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto box = Ui::show(Box<DeleteMessagesBox>(
|
||||
&_history->session(),
|
||||
std::move(items)));
|
||||
box->setDeleteConfirmedCallback([=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PinnedWidget::confirmForwardSelected() {
|
||||
auto items = _inner->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
Window::ShowForwardMessagesBox(controller(), std::move(items), [=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->clearSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PinnedWidget::clearSelected() {
|
||||
_inner->cancelSelection();
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
182
Telegram/SourceFiles/history/view/history_view_pinned_section.h
Normal file
182
Telegram/SourceFiles/history/view/history_view_pinned_section.h
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "window/section_widget.h"
|
||||
#include "window/section_memento.h"
|
||||
#include "history/view/history_view_list_widget.h"
|
||||
#include "data/data_messages.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace Ui {
|
||||
class ScrollArea;
|
||||
class PlainShadow;
|
||||
class FlatButton;
|
||||
class HistoryDownButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Profile {
|
||||
class BackButton;
|
||||
} // namespace Profile
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class Element;
|
||||
class TopBarWidget;
|
||||
class PinnedMemento;
|
||||
|
||||
class PinnedWidget final
|
||||
: public Window::SectionWidget
|
||||
, private ListDelegate {
|
||||
public:
|
||||
PinnedWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> history);
|
||||
~PinnedWidget();
|
||||
|
||||
[[nodiscard]] not_null<History*> history() const;
|
||||
Dialogs::RowDescriptor activeChat() const override;
|
||||
|
||||
bool hasTopBarShadow() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
QPixmap grabForShowAnimation(
|
||||
const Window::SectionSlideParams ¶ms) override;
|
||||
|
||||
bool showInternal(
|
||||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms) override;
|
||||
std::unique_ptr<Window::SectionMemento> createMemento() override;
|
||||
bool showMessage(
|
||||
PeerId peerId,
|
||||
const Window::SectionShow ¶ms,
|
||||
MsgId messageId) override;
|
||||
|
||||
void setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<PinnedMemento*> memento);
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e) override;
|
||||
QRect floatPlayerAvailableRect() override;
|
||||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
void listScrollTo(int top) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) override;
|
||||
bool listAllowsMultiSelect() override;
|
||||
bool listIsItemGoodForSelection(not_null<HistoryItem*> item) override;
|
||||
bool listIsLessInOrder(
|
||||
not_null<HistoryItem*> first,
|
||||
not_null<HistoryItem*> second) override;
|
||||
void listSelectionChanged(SelectedItems &&items) override;
|
||||
void listVisibleItemsChanged(HistoryItemsList &&items) override;
|
||||
MessagesBarData listMessagesBar(
|
||||
const std::vector<not_null<Element*>> &elements) override;
|
||||
void listContentRefreshed() override;
|
||||
ClickHandlerPtr listDateLink(not_null<Element*> view) override;
|
||||
bool listElementHideReply(not_null<const Element*> view) override;
|
||||
bool listElementShownUnread(not_null<const Element*> view) override;
|
||||
bool listIsGoodForAroundPosition(not_null<const Element*> view) override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void showAnimatedHook(
|
||||
const Window::SectionSlideParams ¶ms) override;
|
||||
void showFinishedHook() override;
|
||||
void doSetInnerFocus() override;
|
||||
|
||||
private:
|
||||
void onScroll();
|
||||
void updateInnerVisibleArea();
|
||||
void updateControlsGeometry();
|
||||
void updateAdaptiveLayout();
|
||||
void saveState(not_null<PinnedMemento*> memento);
|
||||
void restoreState(not_null<PinnedMemento*> memento);
|
||||
void showAtStart();
|
||||
void showAtEnd();
|
||||
void showAtPosition(
|
||||
Data::MessagePosition position,
|
||||
HistoryItem *originItem = nullptr);
|
||||
bool showAtPositionNow(
|
||||
Data::MessagePosition position,
|
||||
HistoryItem *originItem);
|
||||
|
||||
void setupScrollDownButton();
|
||||
void scrollDownClicked();
|
||||
void scrollDownAnimationFinish();
|
||||
void updateScrollDownVisibility();
|
||||
void updateScrollDownPosition();
|
||||
|
||||
void confirmDeleteSelected();
|
||||
void confirmForwardSelected();
|
||||
void clearSelected();
|
||||
void recountChatWidth();
|
||||
|
||||
const not_null<History*> _history;
|
||||
QPointer<ListWidget> _inner;
|
||||
object_ptr<TopBarWidget> _topBar;
|
||||
object_ptr<Ui::PlainShadow> _topBarShadow;
|
||||
|
||||
bool _skipScrollEvent = false;
|
||||
std::unique_ptr<Ui::ScrollArea> _scroll;
|
||||
|
||||
Ui::Animations::Simple _scrollDownShown;
|
||||
bool _scrollDownIsShown = false;
|
||||
object_ptr<Ui::HistoryDownButton> _scrollDown;
|
||||
|
||||
Data::MessagesSlice _lastSlice;
|
||||
|
||||
};
|
||||
|
||||
class PinnedMemento : public Window::SectionMemento {
|
||||
public:
|
||||
PinnedMemento(
|
||||
not_null<History*> history,
|
||||
MsgId highlightId = 0)
|
||||
: _history(history)
|
||||
, _highlightId(highlightId) {
|
||||
}
|
||||
|
||||
object_ptr<Window::SectionWidget> createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::Column column,
|
||||
const QRect &geometry) override;
|
||||
|
||||
[[nodiscard]] not_null<History*> getHistory() const {
|
||||
return _history;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<ListMemento*> list() {
|
||||
return &_list;
|
||||
}
|
||||
[[nodiscard]] MsgId getHighlightId() const {
|
||||
return _highlightId;
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
const MsgId _highlightId = 0;
|
||||
ListMemento _list;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "main/main_session.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "apiwrap.h"
|
||||
@ -88,16 +90,16 @@ void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
|
||||
const auto empty = snapshot.ids.empty();
|
||||
const auto before = (i - begin(snapshot.ids));
|
||||
const auto after = (end(snapshot.ids) - i);
|
||||
if (before < kLoadedLimit && !snapshot.skippedBefore) {
|
||||
load(
|
||||
Data::LoadDirection::Before,
|
||||
empty ? _aroundId : snapshot.ids.front());
|
||||
}
|
||||
if (after < kLoadedLimit && !snapshot.skippedAfter) {
|
||||
load(
|
||||
Data::LoadDirection::After,
|
||||
empty ? _aroundId : snapshot.ids.back());
|
||||
}
|
||||
//if (before < kLoadedLimit && !snapshot.skippedBefore) {
|
||||
// load(
|
||||
// Data::LoadDirection::Before,
|
||||
// empty ? _aroundId : snapshot.ids.front());
|
||||
//}
|
||||
//if (after < kLoadedLimit && !snapshot.skippedAfter) {
|
||||
// load(
|
||||
// Data::LoadDirection::After,
|
||||
// empty ? _aroundId : snapshot.ids.back());
|
||||
//}
|
||||
if (snapshot.ids.empty()) {
|
||||
_current = PinnedId();
|
||||
return;
|
||||
@ -117,151 +119,156 @@ void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
|
||||
}
|
||||
}, _dataLifetime);
|
||||
}
|
||||
|
||||
void PinnedTracker::load(Data::LoadDirection direction, MsgId id) {
|
||||
const auto requestId = (direction == Data::LoadDirection::Before)
|
||||
? &_beforeRequestId
|
||||
: &_afterRequestId;
|
||||
const auto aroundId = (direction == Data::LoadDirection::Before)
|
||||
? &_beforeId
|
||||
: &_afterId;
|
||||
if (*requestId) {
|
||||
if (*aroundId == id) {
|
||||
return;
|
||||
}
|
||||
_history->owner().histories().cancelRequest(*requestId);
|
||||
}
|
||||
*aroundId = id;
|
||||
const auto send = [=](Fn<void()> finish) {
|
||||
const auto offsetId = [&] {
|
||||
switch (direction) {
|
||||
case Data::LoadDirection::Before: return id;
|
||||
case Data::LoadDirection::After: return id + 1;
|
||||
}
|
||||
Unexpected("Direction in PinnedTracker::load");
|
||||
}();
|
||||
const auto addOffset = [&] {
|
||||
switch (direction) {
|
||||
case Data::LoadDirection::Before: return 0;
|
||||
case Data::LoadDirection::After: return -kPerPage;
|
||||
}
|
||||
Unexpected("Direction in PinnedTracker::load");
|
||||
}();
|
||||
return _history->session().api().request(MTPmessages_Search(
|
||||
MTP_flags(0),
|
||||
_history->peer->input,
|
||||
MTP_string(QString()),
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPint(), // top_msg_id
|
||||
MTP_inputMessagesFilterPinned(),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(kPerPage),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_int(0) // hash
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
*aroundId = 0;
|
||||
*requestId = 0;
|
||||
finish();
|
||||
|
||||
apply(direction, id, result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
*aroundId = 0;
|
||||
*requestId = 0;
|
||||
finish();
|
||||
}).send();
|
||||
};
|
||||
_beforeRequestId = _history->owner().histories().sendRequest(
|
||||
_history,
|
||||
Data::Histories::RequestType::History,
|
||||
send);
|
||||
}
|
||||
|
||||
void PinnedTracker::apply(
|
||||
Data::LoadDirection direction,
|
||||
MsgId aroundId,
|
||||
const MTPmessages_Messages &result) {
|
||||
auto noSkipRange = MsgRange{ aroundId, aroundId };
|
||||
auto fullCount = std::optional<int>();
|
||||
auto messages = [&] {
|
||||
switch (result.type()) {
|
||||
case mtpc_messages_messages: {
|
||||
auto &d = result.c_messages_messages();
|
||||
_history->owner().processUsers(d.vusers());
|
||||
_history->owner().processChats(d.vchats());
|
||||
fullCount = d.vmessages().v.size();
|
||||
return &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesSlice: {
|
||||
auto &d = result.c_messages_messagesSlice();
|
||||
_history->owner().processUsers(d.vusers());
|
||||
_history->owner().processChats(d.vchats());
|
||||
fullCount = d.vcount().v;
|
||||
return &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = result.c_messages_channelMessages();
|
||||
if (auto channel = _history->peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (PinnedTracker::apply)"));
|
||||
}
|
||||
_history->owner().processUsers(d.vusers());
|
||||
_history->owner().processChats(d.vchats());
|
||||
fullCount = d.vcount().v;
|
||||
return &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesNotModified: {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(PinnedTracker::apply)"));
|
||||
return (const QVector<MTPMessage>*)nullptr;
|
||||
} break;
|
||||
}
|
||||
Unexpected("messages.Messages type in PinnedTracker::apply.");
|
||||
}();
|
||||
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto addType = NewMessageType::Existing;
|
||||
auto list = std::vector<MsgId>();
|
||||
list.reserve(messages->size());
|
||||
for (const auto &message : *messages) {
|
||||
const auto item = _history->owner().addNewMessage(
|
||||
message,
|
||||
MTPDmessage_ClientFlags(),
|
||||
addType);
|
||||
if (item) {
|
||||
const auto itemId = item->id;
|
||||
if (item->isPinned()) {
|
||||
list.push_back(itemId);
|
||||
}
|
||||
accumulate_min(noSkipRange.from, itemId);
|
||||
accumulate_max(noSkipRange.till, itemId);
|
||||
}
|
||||
}
|
||||
if (aroundId && list.empty()) {
|
||||
noSkipRange = [&]() -> MsgRange {
|
||||
switch (direction) {
|
||||
case Data::LoadDirection::Before: // All old loaded.
|
||||
return { 0, noSkipRange.till };
|
||||
case Data::LoadDirection::Around: // All loaded.
|
||||
return { 0, ServerMaxMsgId };
|
||||
case Data::LoadDirection::After: // All new loaded.
|
||||
return { noSkipRange.from, ServerMaxMsgId };
|
||||
}
|
||||
Unexpected("Direction in PinnedTracker::apply.");
|
||||
}();
|
||||
}
|
||||
_history->peer->addPinnedSlice(std::move(list), noSkipRange, fullCount);
|
||||
}
|
||||
//
|
||||
//void PinnedTracker::load(Data::LoadDirection direction, MsgId id) {
|
||||
// const auto requestId = (direction == Data::LoadDirection::Before)
|
||||
// ? &_beforeRequestId
|
||||
// : &_afterRequestId;
|
||||
// const auto aroundId = (direction == Data::LoadDirection::Before)
|
||||
// ? &_beforeId
|
||||
// : &_afterId;
|
||||
// if (*requestId) {
|
||||
// if (*aroundId == id) {
|
||||
// return;
|
||||
// }
|
||||
// _history->owner().histories().cancelRequest(*requestId);
|
||||
// }
|
||||
// *aroundId = id;
|
||||
// const auto send = [=](Fn<void()> finish) {
|
||||
// const auto offsetId = [&] {
|
||||
// switch (direction) {
|
||||
// case Data::LoadDirection::Before: return id;
|
||||
// case Data::LoadDirection::After: return id + 1;
|
||||
// }
|
||||
// Unexpected("Direction in PinnedTracker::load");
|
||||
// }();
|
||||
// const auto addOffset = [&] {
|
||||
// switch (direction) {
|
||||
// case Data::LoadDirection::Before: return 0;
|
||||
// case Data::LoadDirection::After: return -kPerPage;
|
||||
// }
|
||||
// Unexpected("Direction in PinnedTracker::load");
|
||||
// }();
|
||||
// return _history->session().api().request(MTPmessages_Search(
|
||||
// MTP_flags(0),
|
||||
// _history->peer->input,
|
||||
// MTP_string(QString()),
|
||||
// MTP_inputPeerEmpty(),
|
||||
// MTPint(), // top_msg_id
|
||||
// MTP_inputMessagesFilterPinned(),
|
||||
// MTP_int(0),
|
||||
// MTP_int(0),
|
||||
// MTP_int(offsetId),
|
||||
// MTP_int(addOffset),
|
||||
// MTP_int(kPerPage),
|
||||
// MTP_int(0), // max_id
|
||||
// MTP_int(0), // min_id
|
||||
// MTP_int(0) // hash
|
||||
// )).done([=](const MTPmessages_Messages &result) {
|
||||
// *aroundId = 0;
|
||||
// *requestId = 0;
|
||||
// finish();
|
||||
//
|
||||
// apply(direction, id, result);
|
||||
// }).fail([=](const RPCError &error) {
|
||||
// *aroundId = 0;
|
||||
// *requestId = 0;
|
||||
// finish();
|
||||
// }).send();
|
||||
// };
|
||||
// _beforeRequestId = _history->owner().histories().sendRequest(
|
||||
// _history,
|
||||
// Data::Histories::RequestType::History,
|
||||
// send);
|
||||
//}
|
||||
//
|
||||
//void PinnedTracker::apply(
|
||||
// Data::LoadDirection direction,
|
||||
// MsgId aroundId,
|
||||
// const MTPmessages_Messages &result) {
|
||||
// auto noSkipRange = MsgRange{ aroundId, aroundId };
|
||||
// auto fullCount = std::optional<int>();
|
||||
// auto messages = [&] {
|
||||
// switch (result.type()) {
|
||||
// case mtpc_messages_messages: {
|
||||
// auto &d = result.c_messages_messages();
|
||||
// _history->owner().processUsers(d.vusers());
|
||||
// _history->owner().processChats(d.vchats());
|
||||
// fullCount = d.vmessages().v.size();
|
||||
// return &d.vmessages().v;
|
||||
// } break;
|
||||
//
|
||||
// case mtpc_messages_messagesSlice: {
|
||||
// auto &d = result.c_messages_messagesSlice();
|
||||
// _history->owner().processUsers(d.vusers());
|
||||
// _history->owner().processChats(d.vchats());
|
||||
// fullCount = d.vcount().v;
|
||||
// return &d.vmessages().v;
|
||||
// } break;
|
||||
//
|
||||
// case mtpc_messages_channelMessages: {
|
||||
// auto &d = result.c_messages_channelMessages();
|
||||
// if (auto channel = _history->peer->asChannel()) {
|
||||
// channel->ptsReceived(d.vpts().v);
|
||||
// } else {
|
||||
// LOG(("API Error: received messages.channelMessages when "
|
||||
// "no channel was passed! (PinnedTracker::apply)"));
|
||||
// }
|
||||
// _history->owner().processUsers(d.vusers());
|
||||
// _history->owner().processChats(d.vchats());
|
||||
// fullCount = d.vcount().v;
|
||||
// return &d.vmessages().v;
|
||||
// } break;
|
||||
//
|
||||
// case mtpc_messages_messagesNotModified: {
|
||||
// LOG(("API Error: received messages.messagesNotModified! "
|
||||
// "(PinnedTracker::apply)"));
|
||||
// return (const QVector<MTPMessage>*)nullptr;
|
||||
// } break;
|
||||
// }
|
||||
// Unexpected("messages.Messages type in PinnedTracker::apply.");
|
||||
// }();
|
||||
//
|
||||
// if (!messages) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const auto addType = NewMessageType::Existing;
|
||||
// auto list = std::vector<MsgId>();
|
||||
// list.reserve(messages->size());
|
||||
// for (const auto &message : *messages) {
|
||||
// const auto item = _history->owner().addNewMessage(
|
||||
// message,
|
||||
// MTPDmessage_ClientFlags(),
|
||||
// addType);
|
||||
// if (item) {
|
||||
// const auto itemId = item->id;
|
||||
// if (item->isPinned()) {
|
||||
// list.push_back(itemId);
|
||||
// }
|
||||
// accumulate_min(noSkipRange.from, itemId);
|
||||
// accumulate_max(noSkipRange.till, itemId);
|
||||
// }
|
||||
// }
|
||||
// if (aroundId && list.empty()) {
|
||||
// noSkipRange = [&]() -> MsgRange {
|
||||
// switch (direction) {
|
||||
// case Data::LoadDirection::Before: // All old loaded.
|
||||
// return { 0, noSkipRange.till };
|
||||
// case Data::LoadDirection::Around: // All loaded.
|
||||
// return { 0, ServerMaxMsgId };
|
||||
// case Data::LoadDirection::After: // All new loaded.
|
||||
// return { noSkipRange.from, ServerMaxMsgId };
|
||||
// }
|
||||
// Unexpected("Direction in PinnedTracker::apply.");
|
||||
// }();
|
||||
// }
|
||||
// _history->session().storage().add(Storage::SharedMediaAddSlice(
|
||||
// _history->peer->id,
|
||||
// Storage::SharedMediaType::Pinned,
|
||||
// std::move(list),
|
||||
// noSkipRange,
|
||||
// fullCount));
|
||||
//}
|
||||
|
||||
} // namespace HistoryView
|
||||
|
@ -41,14 +41,18 @@ public:
|
||||
void trackAround(MsgId messageId);
|
||||
void reset();
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
void refreshData();
|
||||
void setupViewer(not_null<Data::PinnedMessages*> data);
|
||||
void load(Data::LoadDirection direction, MsgId id);
|
||||
void apply(
|
||||
Data::LoadDirection direction,
|
||||
MsgId aroundId,
|
||||
const MTPmessages_Messages &result);
|
||||
//void load(Data::LoadDirection direction, MsgId id);
|
||||
//void apply(
|
||||
// Data::LoadDirection direction,
|
||||
// MsgId aroundId,
|
||||
// const MTPmessages_Messages &result);
|
||||
|
||||
const not_null<History*> _history;
|
||||
|
||||
|
@ -176,10 +176,6 @@ RepliesWidget::RepliesWidget(
|
||||
|
||||
_rootView->move(0, _topBar->height());
|
||||
|
||||
_topBar->sendNowSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
confirmSendNowSelected();
|
||||
}, _topBar->lifetime());
|
||||
_topBar->deleteSelectionRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
confirmDeleteSelected();
|
||||
@ -1736,19 +1732,6 @@ bool RepliesWidget::listIsGoodForAroundPosition(
|
||||
return IsServerMsgId(view->data()->id);
|
||||
}
|
||||
|
||||
void RepliesWidget::confirmSendNowSelected() {
|
||||
auto items = _inner->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto navigation = controller();
|
||||
Window::ShowSendNowMessagesBox(
|
||||
navigation,
|
||||
_history,
|
||||
std::move(items),
|
||||
[=] { navigation->showBackFromStack(); });
|
||||
}
|
||||
|
||||
void RepliesWidget::confirmDeleteSelected() {
|
||||
auto items = _inner->getSelectedItems();
|
||||
if (items.empty()) {
|
||||
|
@ -168,7 +168,6 @@ private:
|
||||
void updateScrollDownPosition();
|
||||
void updatePinnedVisibility();
|
||||
|
||||
void confirmSendNowSelected();
|
||||
void confirmDeleteSelected();
|
||||
void confirmForwardSelected();
|
||||
void clearSelected();
|
||||
|
@ -335,12 +335,15 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
||||
const auto folder = _activeChat.folder();
|
||||
if (folder
|
||||
|| history->peer->sharedMediaInfo()
|
||||
|| (_section == Section::Scheduled)) {
|
||||
|| (_section == Section::Scheduled)
|
||||
|| (_section == Section::Pinned)) {
|
||||
// #TODO feed name emoji.
|
||||
auto text = (_section == Section::Scheduled)
|
||||
? ((history && history->peer->isSelf())
|
||||
? tr::lng_reminder_messages(tr::now)
|
||||
: tr::lng_scheduled_messages(tr::now))
|
||||
: (_section == Section::Pinned)
|
||||
? "Pinned messages" // #TODO pinned
|
||||
: folder
|
||||
? folder->chatListName()
|
||||
: history->peer->isSelf()
|
||||
|
@ -46,6 +46,7 @@ public:
|
||||
enum class Section {
|
||||
History,
|
||||
Scheduled,
|
||||
Pinned,
|
||||
Replies,
|
||||
};
|
||||
|
||||
|
@ -22,6 +22,8 @@ public:
|
||||
void remove(SharedMediaRemoveAll &&query);
|
||||
void invalidate(SharedMediaInvalidateBottom &&query);
|
||||
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
|
||||
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
|
||||
bool empty(const SharedMediaKey &key) const;
|
||||
rpl::producer<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const;
|
||||
rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const;
|
||||
rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const;
|
||||
@ -83,6 +85,14 @@ rpl::producer<SharedMediaResult> Facade::Impl::query(SharedMediaQuery &&query) c
|
||||
return _sharedMedia.query(std::move(query));
|
||||
}
|
||||
|
||||
SharedMediaResult Facade::Impl::snapshot(const SharedMediaQuery &query) const {
|
||||
return _sharedMedia.snapshot(query);
|
||||
}
|
||||
|
||||
bool Facade::Impl::empty(const SharedMediaKey &key) const {
|
||||
return _sharedMedia.empty(key);
|
||||
}
|
||||
|
||||
rpl::producer<SharedMediaSliceUpdate> Facade::Impl::sharedMediaSliceUpdated() const {
|
||||
return _sharedMedia.sliceUpdated();
|
||||
}
|
||||
@ -203,6 +213,14 @@ rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const {
|
||||
return _impl->query(std::move(query));
|
||||
}
|
||||
|
||||
SharedMediaResult Facade::snapshot(const SharedMediaQuery &query) const {
|
||||
return _impl->snapshot(query);
|
||||
}
|
||||
|
||||
bool Facade::empty(const SharedMediaKey &key) const {
|
||||
return _impl->empty(key);
|
||||
}
|
||||
|
||||
rpl::producer<SharedMediaSliceUpdate> Facade::sharedMediaSliceUpdated() const {
|
||||
return _impl->sharedMediaSliceUpdated();
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ struct SharedMediaRemoveOne;
|
||||
struct SharedMediaRemoveAll;
|
||||
struct SharedMediaInvalidateBottom;
|
||||
struct SharedMediaQuery;
|
||||
struct SharedMediaKey;
|
||||
using SharedMediaResult = SparseIdsListResult;
|
||||
struct SharedMediaSliceUpdate;
|
||||
|
||||
@ -58,6 +59,8 @@ public:
|
||||
void invalidate(SharedMediaInvalidateBottom &&query);
|
||||
|
||||
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
|
||||
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
|
||||
bool empty(const SharedMediaKey &key) const;
|
||||
rpl::producer<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const;
|
||||
rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const;
|
||||
rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const;
|
||||
|
@ -82,7 +82,10 @@ void SharedMedia::remove(SharedMediaRemoveAll &&query) {
|
||||
auto peerIt = _lists.find(query.peerId);
|
||||
if (peerIt != _lists.end()) {
|
||||
for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
|
||||
peerIt->second[index].removeAll();
|
||||
auto type = static_cast<SharedMediaType>(index);
|
||||
if (query.types.test(type)) {
|
||||
peerIt->second[index].removeAll();
|
||||
}
|
||||
}
|
||||
_allRemoved.fire(std::move(query));
|
||||
}
|
||||
@ -100,6 +103,7 @@ void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) {
|
||||
|
||||
rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const {
|
||||
Expects(IsValidSharedMediaType(query.key.type));
|
||||
|
||||
auto peerIt = _lists.find(query.key.peerId);
|
||||
if (peerIt != _lists.end()) {
|
||||
auto index = static_cast<int>(query.key.type);
|
||||
@ -114,6 +118,31 @@ rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) co
|
||||
};
|
||||
}
|
||||
|
||||
SharedMediaResult SharedMedia::snapshot(const SharedMediaQuery &query) const {
|
||||
Expects(IsValidSharedMediaType(query.key.type));
|
||||
|
||||
auto peerIt = _lists.find(query.key.peerId);
|
||||
if (peerIt != _lists.end()) {
|
||||
auto index = static_cast<int>(query.key.type);
|
||||
return peerIt->second[index].snapshot(SparseIdsListQuery(
|
||||
query.key.messageId,
|
||||
query.limitBefore,
|
||||
query.limitAfter));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool SharedMedia::empty(const SharedMediaKey &key) const {
|
||||
Expects(IsValidSharedMediaType(key.type));
|
||||
|
||||
auto peerIt = _lists.find(key.peerId);
|
||||
if (peerIt != _lists.end()) {
|
||||
auto index = static_cast<int>(key.type);
|
||||
return peerIt->second[index].empty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<SharedMediaSliceUpdate> SharedMedia::sliceUpdated() const {
|
||||
return _sliceUpdated.events();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ enum class SharedMediaType : signed char {
|
||||
RoundVoiceFile,
|
||||
GIF,
|
||||
RoundFile,
|
||||
Pinned,
|
||||
|
||||
kCount,
|
||||
};
|
||||
@ -106,10 +107,15 @@ struct SharedMediaRemoveOne {
|
||||
};
|
||||
|
||||
struct SharedMediaRemoveAll {
|
||||
SharedMediaRemoveAll(PeerId peerId) : peerId(peerId) {
|
||||
SharedMediaRemoveAll(
|
||||
PeerId peerId,
|
||||
SharedMediaTypesMask types = SharedMediaTypesMask::All())
|
||||
: peerId(peerId)
|
||||
, types(types) {
|
||||
}
|
||||
|
||||
PeerId peerId = 0;
|
||||
SharedMediaTypesMask types;
|
||||
|
||||
};
|
||||
|
||||
@ -191,6 +197,8 @@ public:
|
||||
void invalidate(SharedMediaInvalidateBottom &&query);
|
||||
|
||||
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
|
||||
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
|
||||
bool empty(const SharedMediaKey &key) const;
|
||||
rpl::producer<SharedMediaSliceUpdate> sliceUpdated() const;
|
||||
rpl::producer<SharedMediaRemoveOne> oneRemoved() const;
|
||||
rpl::producer<SharedMediaRemoveAll> allRemoved() const;
|
||||
|
@ -200,6 +200,35 @@ rpl::producer<SparseIdsListResult> SparseIdsList::query(
|
||||
};
|
||||
}
|
||||
|
||||
SparseIdsListResult SparseIdsList::snapshot(
|
||||
const SparseIdsListQuery &query) const {
|
||||
auto slice = query.aroundId
|
||||
? ranges::lower_bound(
|
||||
_slices,
|
||||
query.aroundId,
|
||||
std::less<>(),
|
||||
[](const Slice &slice) { return slice.range.till; })
|
||||
: _slices.end();
|
||||
if (slice != _slices.end()
|
||||
&& slice->range.from <= query.aroundId) {
|
||||
return queryFromSlice(query, *slice);
|
||||
} else if (_count) {
|
||||
auto result = SparseIdsListResult{};
|
||||
result.count = _count;
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool SparseIdsList::empty() const {
|
||||
for (const auto &slice : _slices) {
|
||||
if (!slice.messages.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsSliceUpdate> SparseIdsList::sliceUpdated() const {
|
||||
return _sliceUpdated.events();
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ public:
|
||||
void invalidateBottom();
|
||||
rpl::producer<SparseIdsListResult> query(SparseIdsListQuery &&query) const;
|
||||
rpl::producer<SparseIdsSliceUpdate> sliceUpdated() const;
|
||||
SparseIdsListResult snapshot(const SparseIdsListQuery &query) const;
|
||||
bool empty() const;
|
||||
|
||||
private:
|
||||
struct Slice {
|
||||
|
Loading…
Reference in New Issue
Block a user