Add pinned messages section.

This commit is contained in:
John Preston 2020-10-20 19:29:24 +03:00
parent aefef948cd
commit fc92e3fadd
26 changed files with 1142 additions and 343 deletions

View File

@ -533,6 +533,8 @@ PRIVATE
history/view/history_view_object.h history/view/history_view_object.h
history/view/history_view_pinned_bar.cpp history/view/history_view_pinned_bar.cpp
history/view/history_view_pinned_bar.h 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.cpp
history/view/history_view_pinned_tracker.h history/view/history_view_pinned_tracker.h
history/view/history_view_replies_section.cpp history/view/history_view_replies_section.cpp

View File

@ -383,7 +383,6 @@ void ChannelData::setUnavailableReasons(
void ChannelData::setAvailableMinId(MsgId availableMinId) { void ChannelData::setAvailableMinId(MsgId availableMinId) {
if (_availableMinId != availableMinId) { if (_availableMinId != availableMinId) {
_availableMinId = availableMinId; _availableMinId = availableMinId;
clearPinnedMessages(_availableMinId + 1);
} }
} }

View File

@ -38,6 +38,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "storage/file_download.h" #include "storage/file_download.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "facades.h" // Ui::showPeerProfile #include "facades.h" // Ui::showPeerProfile
#include "app.h" #include "app.h"
@ -468,14 +470,15 @@ MsgId PeerData::topPinnedMessageId() const {
void PeerData::ensurePinnedMessagesCreated() { void PeerData::ensurePinnedMessagesCreated() {
if (!_pinnedMessages) { if (!_pinnedMessages) {
_pinnedMessages = std::make_unique<Data::PinnedMessages>( _pinnedMessages = std::make_unique<Data::PinnedMessages>(this);
peerToChannel(id)); session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
} }
} }
void PeerData::removeEmptyPinnedMessages() { void PeerData::removeEmptyPinnedMessages() {
if (_pinnedMessages && _pinnedMessages->empty()) { if (_pinnedMessages && _pinnedMessages->empty()) {
_pinnedMessages = nullptr; _pinnedMessages = nullptr;
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
} }
} }
@ -491,69 +494,24 @@ void PeerData::setTopPinnedMessageId(MsgId messageId) {
clearPinnedMessages(); clearPinnedMessages();
return; 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().settings().setHiddenPinnedMessageId(id, 0);
session().saveSettingsDelayed(); session().saveSettingsDelayed();
} }
ensurePinnedMessagesCreated(); ensurePinnedMessagesCreated();
_pinnedMessages->setTopId(messageId); _pinnedMessages->setTopId(messageId);
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
} }
void PeerData::clearPinnedMessages(MsgId lessThanId) { void PeerData::clearPinnedMessages() {
if (lessThanId == ServerMaxMsgId if (session().settings().hiddenPinnedMessageId(id) != 0) {
&& session().settings().hiddenPinnedMessageId(id) != 0) {
session().settings().setHiddenPinnedMessageId(id, 0); session().settings().setHiddenPinnedMessageId(id, 0);
session().saveSettingsDelayed(); session().saveSettingsDelayed();
} }
if (!_pinnedMessages) { session().storage().remove(Storage::SharedMediaRemoveAll(
return; id,
} Storage::SharedMediaType::Pinned));
_pinnedMessages->clearLessThanId(lessThanId);
removeEmptyPinnedMessages(); 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 { bool PeerData::canExportChatHistory() const {

View File

@ -330,13 +330,7 @@ public:
[[nodiscard]] bool canEditMessagesIndefinitely() const; [[nodiscard]] bool canEditMessagesIndefinitely() const;
[[nodiscard]] MsgId topPinnedMessageId() const; [[nodiscard]] MsgId topPinnedMessageId() const;
void setTopPinnedMessageId(MsgId messageId); void setTopPinnedMessageId(MsgId messageId);
void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId); void clearPinnedMessages();
void addPinnedMessage(MsgId messageId);
void addPinnedSlice(
std::vector<MsgId> &&ids,
MsgRange noSkipRange,
std::optional<int> count);
void removePinnedMessage(MsgId messageId);
Data::PinnedMessages *currentPinnedMessages() const { Data::PinnedMessages *currentPinnedMessages() const {
return _pinnedMessages.get(); return _pinnedMessages.get();
} }

View File

@ -7,94 +7,76 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "data/data_pinned_messages.h" #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 { bool PinnedMessages::empty() const {
return _list.empty(); return _storage.empty(SharedMediaKey(_peer->id, PinnedType, 0));
} }
MsgId PinnedMessages::topId() const { MsgId PinnedMessages::topId() const {
const auto slice = _list.snapshot(MessagesQuery{ const auto slice = _storage.snapshot(
.aroundId = MaxMessagePosition, SharedMediaQuery(
.limitBefore = 1, SharedMediaKey(_peer->id, PinnedType, ServerMaxMsgId),
.limitAfter = 1 1,
}); 1));
return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg; return slice.messageIds.empty() ? 0 : slice.messageIds.back();
} }
rpl::producer<PinnedAroundId> PinnedMessages::viewer( rpl::producer<PinnedAroundId> PinnedMessages::viewer(
MsgId aroundId, MsgId aroundId,
int limit) const { int limit) const {
return _list.viewer(MessagesQuery{ return _storage.query(
.aroundId = position(aroundId), SharedMediaQuery(
.limitBefore = limit, SharedMediaKey(_peer->id, PinnedType, aroundId),
.limitAfter = limit limit,
}) | rpl::map([](const MessagesResult &result) { limit)
) | rpl::map([](const SharedMediaResult &result) {
auto data = PinnedAroundId(); auto data = PinnedAroundId();
data.fullCount = result.count; data.fullCount = result.count;
data.skippedBefore = result.skippedBefore; data.skippedBefore = result.skippedBefore;
data.skippedAfter = result.skippedAfter; data.skippedAfter = result.skippedAfter;
data.ids = result.messageIds | ranges::view::transform( data.ids = result.messageIds | ranges::to_vector;
[](MessagePosition position) { return position.fullId.msg; }
) | ranges::to_vector;
return data; 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) { void PinnedMessages::setTopId(MsgId messageId) {
while (true) { while (true) {
auto top = topId(); auto top = topId();
if (top > messageId) { if (top > messageId) {
remove(top); _storage.remove(Storage::SharedMediaRemoveOne(
_peer->id,
PinnedType,
top));
} else if (top == messageId) { } else if (top == messageId) {
return; return;
} else { } else {
break; break;
} }
} }
const auto wrapped = position(messageId); _storage.add(Storage::SharedMediaAddNew(
_list.addSlice( _peer->id,
{ wrapped }, PinnedType,
{ .from = wrapped, .till = MaxMessagePosition }, messageId));
std::nullopt);
}
void PinnedMessages::clearLessThanId(MsgId messageId) {
_list.removeLessThan(position(messageId));
} }
} // namespace Data } // namespace Data

View File

@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_messages.h" #include "data/data_messages.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
namespace Storage {
class Facade;
} // namespace Storage
namespace Data { namespace Data {
struct PinnedAroundId { struct PinnedAroundId {
@ -21,30 +25,18 @@ struct PinnedAroundId {
class PinnedMessages final : public base::has_weak_ptr { class PinnedMessages final : public base::has_weak_ptr {
public: public:
explicit PinnedMessages(ChannelId channelId); explicit PinnedMessages(not_null<PeerData*> peer);
[[nodiscard]] bool empty() const; [[nodiscard]] bool empty() const;
[[nodiscard]] MsgId topId() const; [[nodiscard]] MsgId topId() const;
[[nodiscard]] rpl::producer<PinnedAroundId> viewer( [[nodiscard]] rpl::producer<PinnedAroundId> viewer(
MsgId aroundId, MsgId aroundId,
int limit) const; 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 setTopId(MsgId messageId);
void clearLessThanId(MsgId messageId);
private: private:
[[nodiscard]] MessagePosition position(MsgId id) const; const not_null<PeerData*> _peer;
Storage::Facade &_storage;
MessagesList _list;
ChannelId _channelId = 0;
}; };

View File

@ -55,6 +55,8 @@ std::optional<MTPmessages_Search> PrepareSearchRequest(
return MTP_inputMessagesFilterUrl(); return MTP_inputMessagesFilterUrl();
case Type::ChatPhoto: case Type::ChatPhoto:
return MTP_inputMessagesFilterChatPhotos(); return MTP_inputMessagesFilterChatPhotos();
case Type::Pinned:
return MTP_inputMessagesFilterPinned();
} }
return MTP_inputMessagesFilterEmpty(); return MTP_inputMessagesFilterEmpty();
}(); }();

View File

@ -123,7 +123,8 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
using AllRemoved = Storage::SharedMediaRemoveAll; using AllRemoved = Storage::SharedMediaRemoveAll;
session->storage().sharedMediaAllRemoved( session->storage().sharedMediaAllRemoved(
) | rpl::filter([=](const AllRemoved &update) { ) | rpl::filter([=](const AllRemoved &update) {
return (update.peerId == key.peerId); return (update.peerId == key.peerId)
&& (update.types.test(key.type));
}) | rpl::filter([=] { }) | rpl::filter([=] {
return builder->removeAll(); return builder->removeAll();
}) | rpl::start_with_next(pushNextSnapshot, lifetime); }) | rpl::start_with_next(pushNextSnapshot, lifetime);

View File

@ -177,7 +177,6 @@ void History::itemVanished(not_null<HistoryItem*> item) {
&& unreadCount() > 0) { && unreadCount() > 0) {
setUnreadCount(unreadCount() - 1); setUnreadCount(unreadCount() - 1);
} }
peer->removePinnedMessage(item->id);
} }
void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) { void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
@ -707,9 +706,6 @@ not_null<HistoryItem*> History::addNewToBack(
item->id, item->id,
{ from, till })); { from, till }));
} }
if (item->isPinned()) {
item->history()->peer->addPinnedMessage(item->id);
}
} }
if (item->from()->id) { if (item->from()->id) {
if (auto user = item->from()->asUser()) { if (auto user = item->from()->asUser()) {
@ -1007,10 +1003,11 @@ void History::applyServiceChanges(
replyTo->match([&](const MTPDmessageReplyHeader &data) { replyTo->match([&](const MTPDmessageReplyHeader &data) {
const auto id = data.vreply_to_msg_id().v; const auto id = data.vreply_to_msg_id().v;
if (item) { if (item) {
item->history()->peer->addPinnedSlice( session().storage().add(Storage::SharedMediaAddSlice(
peer->id,
Storage::SharedMediaType::Pinned,
{ id }, { id },
{ id, ServerMaxMsgId }, { id, ServerMaxMsgId }));
std::nullopt);
} }
}); });
} }
@ -1164,7 +1161,6 @@ void History::addEdgesToSharedMedia() {
{}, {},
{ from, till })); { from, till }));
} }
peer->addPinnedSlice({}, { from, till }, std::nullopt);
} }
void History::addOlderSlice(const QVector<MTPMessage> &slice) { void History::addOlderSlice(const QVector<MTPMessage> &slice) {
@ -1328,7 +1324,6 @@ void History::checkAddAllToUnreadMentions() {
void History::addToSharedMedia( void History::addToSharedMedia(
const std::vector<not_null<HistoryItem*>> &items) { const std::vector<not_null<HistoryItem*>> &items) {
auto pinned = std::vector<MsgId>();
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount]; std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
for (const auto item : items) { for (const auto item : items) {
if (const auto types = item->sharedMediaTypes()) { 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 from = loadedAtTop() ? 0 : minMsgId();
const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
@ -1361,7 +1350,6 @@ void History::addToSharedMedia(
{ from, till })); { from, till }));
} }
} }
peer->addPinnedSlice(std::move(pinned), { from, till }, std::nullopt);
} }
void History::calculateFirstUnreadMessage() { void History::calculateFirstUnreadMessage() {
@ -3029,7 +3017,6 @@ void History::clear(ClearType type) {
clearSharedMedia(); clearSharedMedia();
clearLastKeyboard(); clearLastKeyboard();
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
channel->clearPinnedMessages();
//if (const auto feed = channel->feed()) { // #feed //if (const auto feed = channel->feed()) { // #feed
// // Should be after resetting the _lastMessage. // // Should be after resetting the _lastMessage.
// feed->historyCleared(this); // feed->historyCleared(this);

View File

@ -350,10 +350,17 @@ void HistoryItem::markMediaRead() {
void HistoryItem::setIsPinned(bool pinned) { void HistoryItem::setIsPinned(bool pinned) {
if (pinned) { if (pinned) {
_flags |= MTPDmessage::Flag::f_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 { } else {
_flags &= ~MTPDmessage::Flag::f_pinned; _flags &= ~MTPDmessage::Flag::f_pinned;
history()->peer->removePinnedMessage(id); history()->session().storage().remove(Storage::SharedMediaRemoveOne(
history()->peer->id,
Storage::SharedMediaType::Pinned,
id));
} }
} }

View File

@ -1409,6 +1409,9 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
if (hasTextLinks()) { if (hasTextLinks()) {
result.set(Storage::SharedMediaType::Link); result.set(Storage::SharedMediaType::Link);
} }
if (isPinned()) {
result.set(Storage::SharedMediaType::Pinned);
}
return result; return result;
} }

View File

@ -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_top_bar_widget.h"
#include "history/view/history_view_contact_status.h" #include "history/view/history_view_contact_status.h"
#include "history/view/history_view_pinned_tracker.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/history_view_pinned_bar.h"
#include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media.h"
#include "profile/profile_block_group_members.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_slide_animation.h"
#include "window/window_peer_menu.h" #include "window/window_peer_menu.h"
#include "inline_bots/inline_results_widget.h" #include "inline_bots/inline_results_widget.h"
#include "info/profile/info_profile_values.h" // SharedMediaCountValue.
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "core/crash_reports.h" #include "core/crash_reports.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
@ -3219,6 +3221,9 @@ void HistoryWidget::doneShow() {
} }
preloadHistoryIfNeeded(); preloadHistoryIfNeeded();
updatePinnedViewer(); updatePinnedViewer();
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
checkHistoryActivation(); checkHistoryActivation();
App::wnd()->setInnerFocus(); App::wnd()->setInnerFocus();
} }
@ -5177,10 +5182,9 @@ void HistoryWidget::updatePinnedViewer() {
} }
return std::pair(item, offset); return std::pair(item, offset);
}(); }();
const auto last = _history->peer->topPinnedMessageId();
const auto lessThanId = item const auto lessThanId = item
? (item->data()->id + (offset > 0 ? 1 : 0)) ? (item->data()->id + (offset > 0 ? 1 : 0))
: (last + 1); : (_history->peer->topPinnedMessageId() + 1);
_pinnedTracker->trackAround(lessThanId); _pinnedTracker->trackAround(lessThanId);
} }
@ -5189,7 +5193,13 @@ void HistoryWidget::setupPinnedTracker() {
_pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history); _pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history);
_pinnedBar = nullptr; _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() { void HistoryWidget::checkPinnedBarState() {
@ -5258,7 +5268,9 @@ void HistoryWidget::checkPinnedBarState() {
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto id = _pinnedTracker->currentMessageId(); const auto id = _pinnedTracker->currentMessageId();
if (id.message) { if (id.message) {
Ui::showPeerHistory(_peer, id.message); controller()->showSection(
HistoryView::PinnedMemento(_history, id.message));
//Ui::showPeerHistory(_peer, id.message);
} }
}, _pinnedBar->lifetime()); }, _pinnedBar->lifetime());
@ -5543,8 +5555,6 @@ void HistoryWidget::UnpinMessage(not_null<PeerData*> peer, MsgId msgId) {
const auto session = &peer->session(); 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, [=] { 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(); Ui::hideLayer();
session->api().request(MTPmessages_UpdatePinnedMessage( session->api().request(MTPmessages_UpdatePinnedMessage(
MTP_flags(MTPmessages_UpdatePinnedMessage::Flag::f_unpin), MTP_flags(MTPmessages_UpdatePinnedMessage::Flag::f_unpin),

View File

@ -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 &params) {
_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 &params) {
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 &params,
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>(&params.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 &params) {
_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

View 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 &params) override;
bool showInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) override;
std::unique_ptr<Window::SectionMemento> createMemento() override;
bool showMessage(
PeerId peerId,
const Window::SectionShow &params,
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 &params) 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

View File

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -88,16 +90,16 @@ void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
const auto empty = snapshot.ids.empty(); const auto empty = snapshot.ids.empty();
const auto before = (i - begin(snapshot.ids)); const auto before = (i - begin(snapshot.ids));
const auto after = (end(snapshot.ids) - i); const auto after = (end(snapshot.ids) - i);
if (before < kLoadedLimit && !snapshot.skippedBefore) { //if (before < kLoadedLimit && !snapshot.skippedBefore) {
load( // load(
Data::LoadDirection::Before, // Data::LoadDirection::Before,
empty ? _aroundId : snapshot.ids.front()); // empty ? _aroundId : snapshot.ids.front());
} //}
if (after < kLoadedLimit && !snapshot.skippedAfter) { //if (after < kLoadedLimit && !snapshot.skippedAfter) {
load( // load(
Data::LoadDirection::After, // Data::LoadDirection::After,
empty ? _aroundId : snapshot.ids.back()); // empty ? _aroundId : snapshot.ids.back());
} //}
if (snapshot.ids.empty()) { if (snapshot.ids.empty()) {
_current = PinnedId(); _current = PinnedId();
return; return;
@ -117,151 +119,156 @@ void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
} }
}, _dataLifetime); }, _dataLifetime);
} }
//
void PinnedTracker::load(Data::LoadDirection direction, MsgId id) { //void PinnedTracker::load(Data::LoadDirection direction, MsgId id) {
const auto requestId = (direction == Data::LoadDirection::Before) // const auto requestId = (direction == Data::LoadDirection::Before)
? &_beforeRequestId // ? &_beforeRequestId
: &_afterRequestId; // : &_afterRequestId;
const auto aroundId = (direction == Data::LoadDirection::Before) // const auto aroundId = (direction == Data::LoadDirection::Before)
? &_beforeId // ? &_beforeId
: &_afterId; // : &_afterId;
if (*requestId) { // if (*requestId) {
if (*aroundId == id) { // if (*aroundId == id) {
return; // return;
} // }
_history->owner().histories().cancelRequest(*requestId); // _history->owner().histories().cancelRequest(*requestId);
} // }
*aroundId = id; // *aroundId = id;
const auto send = [=](Fn<void()> finish) { // const auto send = [=](Fn<void()> finish) {
const auto offsetId = [&] { // const auto offsetId = [&] {
switch (direction) { // switch (direction) {
case Data::LoadDirection::Before: return id; // case Data::LoadDirection::Before: return id;
case Data::LoadDirection::After: return id + 1; // case Data::LoadDirection::After: return id + 1;
} // }
Unexpected("Direction in PinnedTracker::load"); // Unexpected("Direction in PinnedTracker::load");
}(); // }();
const auto addOffset = [&] { // const auto addOffset = [&] {
switch (direction) { // switch (direction) {
case Data::LoadDirection::Before: return 0; // case Data::LoadDirection::Before: return 0;
case Data::LoadDirection::After: return -kPerPage; // case Data::LoadDirection::After: return -kPerPage;
} // }
Unexpected("Direction in PinnedTracker::load"); // Unexpected("Direction in PinnedTracker::load");
}(); // }();
return _history->session().api().request(MTPmessages_Search( // return _history->session().api().request(MTPmessages_Search(
MTP_flags(0), // MTP_flags(0),
_history->peer->input, // _history->peer->input,
MTP_string(QString()), // MTP_string(QString()),
MTP_inputPeerEmpty(), // MTP_inputPeerEmpty(),
MTPint(), // top_msg_id // MTPint(), // top_msg_id
MTP_inputMessagesFilterPinned(), // MTP_inputMessagesFilterPinned(),
MTP_int(0), // MTP_int(0),
MTP_int(0), // MTP_int(0),
MTP_int(offsetId), // MTP_int(offsetId),
MTP_int(addOffset), // MTP_int(addOffset),
MTP_int(kPerPage), // MTP_int(kPerPage),
MTP_int(0), // max_id // MTP_int(0), // max_id
MTP_int(0), // min_id // MTP_int(0), // min_id
MTP_int(0) // hash // MTP_int(0) // hash
)).done([=](const MTPmessages_Messages &result) { // )).done([=](const MTPmessages_Messages &result) {
*aroundId = 0; // *aroundId = 0;
*requestId = 0; // *requestId = 0;
finish(); // finish();
//
apply(direction, id, result); // apply(direction, id, result);
}).fail([=](const RPCError &error) { // }).fail([=](const RPCError &error) {
*aroundId = 0; // *aroundId = 0;
*requestId = 0; // *requestId = 0;
finish(); // finish();
}).send(); // }).send();
}; // };
_beforeRequestId = _history->owner().histories().sendRequest( // _beforeRequestId = _history->owner().histories().sendRequest(
_history, // _history,
Data::Histories::RequestType::History, // Data::Histories::RequestType::History,
send); // send);
} //}
//
void PinnedTracker::apply( //void PinnedTracker::apply(
Data::LoadDirection direction, // Data::LoadDirection direction,
MsgId aroundId, // MsgId aroundId,
const MTPmessages_Messages &result) { // const MTPmessages_Messages &result) {
auto noSkipRange = MsgRange{ aroundId, aroundId }; // auto noSkipRange = MsgRange{ aroundId, aroundId };
auto fullCount = std::optional<int>(); // auto fullCount = std::optional<int>();
auto messages = [&] { // auto messages = [&] {
switch (result.type()) { // switch (result.type()) {
case mtpc_messages_messages: { // case mtpc_messages_messages: {
auto &d = result.c_messages_messages(); // auto &d = result.c_messages_messages();
_history->owner().processUsers(d.vusers()); // _history->owner().processUsers(d.vusers());
_history->owner().processChats(d.vchats()); // _history->owner().processChats(d.vchats());
fullCount = d.vmessages().v.size(); // fullCount = d.vmessages().v.size();
return &d.vmessages().v; // return &d.vmessages().v;
} break; // } break;
//
case mtpc_messages_messagesSlice: { // case mtpc_messages_messagesSlice: {
auto &d = result.c_messages_messagesSlice(); // auto &d = result.c_messages_messagesSlice();
_history->owner().processUsers(d.vusers()); // _history->owner().processUsers(d.vusers());
_history->owner().processChats(d.vchats()); // _history->owner().processChats(d.vchats());
fullCount = d.vcount().v; // fullCount = d.vcount().v;
return &d.vmessages().v; // return &d.vmessages().v;
} break; // } break;
//
case mtpc_messages_channelMessages: { // case mtpc_messages_channelMessages: {
auto &d = result.c_messages_channelMessages(); // auto &d = result.c_messages_channelMessages();
if (auto channel = _history->peer->asChannel()) { // if (auto channel = _history->peer->asChannel()) {
channel->ptsReceived(d.vpts().v); // channel->ptsReceived(d.vpts().v);
} else { // } else {
LOG(("API Error: received messages.channelMessages when " // LOG(("API Error: received messages.channelMessages when "
"no channel was passed! (PinnedTracker::apply)")); // "no channel was passed! (PinnedTracker::apply)"));
} // }
_history->owner().processUsers(d.vusers()); // _history->owner().processUsers(d.vusers());
_history->owner().processChats(d.vchats()); // _history->owner().processChats(d.vchats());
fullCount = d.vcount().v; // fullCount = d.vcount().v;
return &d.vmessages().v; // return &d.vmessages().v;
} break; // } break;
//
case mtpc_messages_messagesNotModified: { // case mtpc_messages_messagesNotModified: {
LOG(("API Error: received messages.messagesNotModified! " // LOG(("API Error: received messages.messagesNotModified! "
"(PinnedTracker::apply)")); // "(PinnedTracker::apply)"));
return (const QVector<MTPMessage>*)nullptr; // return (const QVector<MTPMessage>*)nullptr;
} break; // } break;
} // }
Unexpected("messages.Messages type in PinnedTracker::apply."); // Unexpected("messages.Messages type in PinnedTracker::apply.");
}(); // }();
//
if (!messages) { // if (!messages) {
return; // return;
} // }
//
const auto addType = NewMessageType::Existing; // const auto addType = NewMessageType::Existing;
auto list = std::vector<MsgId>(); // auto list = std::vector<MsgId>();
list.reserve(messages->size()); // list.reserve(messages->size());
for (const auto &message : *messages) { // for (const auto &message : *messages) {
const auto item = _history->owner().addNewMessage( // const auto item = _history->owner().addNewMessage(
message, // message,
MTPDmessage_ClientFlags(), // MTPDmessage_ClientFlags(),
addType); // addType);
if (item) { // if (item) {
const auto itemId = item->id; // const auto itemId = item->id;
if (item->isPinned()) { // if (item->isPinned()) {
list.push_back(itemId); // list.push_back(itemId);
} // }
accumulate_min(noSkipRange.from, itemId); // accumulate_min(noSkipRange.from, itemId);
accumulate_max(noSkipRange.till, itemId); // accumulate_max(noSkipRange.till, itemId);
} // }
} // }
if (aroundId && list.empty()) { // if (aroundId && list.empty()) {
noSkipRange = [&]() -> MsgRange { // noSkipRange = [&]() -> MsgRange {
switch (direction) { // switch (direction) {
case Data::LoadDirection::Before: // All old loaded. // case Data::LoadDirection::Before: // All old loaded.
return { 0, noSkipRange.till }; // return { 0, noSkipRange.till };
case Data::LoadDirection::Around: // All loaded. // case Data::LoadDirection::Around: // All loaded.
return { 0, ServerMaxMsgId }; // return { 0, ServerMaxMsgId };
case Data::LoadDirection::After: // All new loaded. // case Data::LoadDirection::After: // All new loaded.
return { noSkipRange.from, ServerMaxMsgId }; // return { noSkipRange.from, ServerMaxMsgId };
} // }
Unexpected("Direction in PinnedTracker::apply."); // Unexpected("Direction in PinnedTracker::apply.");
}(); // }();
} // }
_history->peer->addPinnedSlice(std::move(list), noSkipRange, fullCount); // _history->session().storage().add(Storage::SharedMediaAddSlice(
} // _history->peer->id,
// Storage::SharedMediaType::Pinned,
// std::move(list),
// noSkipRange,
// fullCount));
//}
} // namespace HistoryView } // namespace HistoryView

View File

@ -41,14 +41,18 @@ public:
void trackAround(MsgId messageId); void trackAround(MsgId messageId);
void reset(); void reset();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
private: private:
void refreshData(); void refreshData();
void setupViewer(not_null<Data::PinnedMessages*> data); void setupViewer(not_null<Data::PinnedMessages*> data);
void load(Data::LoadDirection direction, MsgId id); //void load(Data::LoadDirection direction, MsgId id);
void apply( //void apply(
Data::LoadDirection direction, // Data::LoadDirection direction,
MsgId aroundId, // MsgId aroundId,
const MTPmessages_Messages &result); // const MTPmessages_Messages &result);
const not_null<History*> _history; const not_null<History*> _history;

View File

@ -176,10 +176,6 @@ RepliesWidget::RepliesWidget(
_rootView->move(0, _topBar->height()); _rootView->move(0, _topBar->height());
_topBar->sendNowSelectionRequest(
) | rpl::start_with_next([=] {
confirmSendNowSelected();
}, _topBar->lifetime());
_topBar->deleteSelectionRequest( _topBar->deleteSelectionRequest(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
confirmDeleteSelected(); confirmDeleteSelected();
@ -1736,19 +1732,6 @@ bool RepliesWidget::listIsGoodForAroundPosition(
return IsServerMsgId(view->data()->id); 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() { void RepliesWidget::confirmDeleteSelected() {
auto items = _inner->getSelectedItems(); auto items = _inner->getSelectedItems();
if (items.empty()) { if (items.empty()) {

View File

@ -168,7 +168,6 @@ private:
void updateScrollDownPosition(); void updateScrollDownPosition();
void updatePinnedVisibility(); void updatePinnedVisibility();
void confirmSendNowSelected();
void confirmDeleteSelected(); void confirmDeleteSelected();
void confirmForwardSelected(); void confirmForwardSelected();
void clearSelected(); void clearSelected();

View File

@ -335,12 +335,15 @@ void TopBarWidget::paintTopBar(Painter &p) {
const auto folder = _activeChat.folder(); const auto folder = _activeChat.folder();
if (folder if (folder
|| history->peer->sharedMediaInfo() || history->peer->sharedMediaInfo()
|| (_section == Section::Scheduled)) { || (_section == Section::Scheduled)
|| (_section == Section::Pinned)) {
// #TODO feed name emoji. // #TODO feed name emoji.
auto text = (_section == Section::Scheduled) auto text = (_section == Section::Scheduled)
? ((history && history->peer->isSelf()) ? ((history && history->peer->isSelf())
? tr::lng_reminder_messages(tr::now) ? tr::lng_reminder_messages(tr::now)
: tr::lng_scheduled_messages(tr::now)) : tr::lng_scheduled_messages(tr::now))
: (_section == Section::Pinned)
? "Pinned messages" // #TODO pinned
: folder : folder
? folder->chatListName() ? folder->chatListName()
: history->peer->isSelf() : history->peer->isSelf()

View File

@ -46,6 +46,7 @@ public:
enum class Section { enum class Section {
History, History,
Scheduled, Scheduled,
Pinned,
Replies, Replies,
}; };

View File

@ -22,6 +22,8 @@ public:
void remove(SharedMediaRemoveAll &&query); void remove(SharedMediaRemoveAll &&query);
void invalidate(SharedMediaInvalidateBottom &&query); void invalidate(SharedMediaInvalidateBottom &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; 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<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const;
rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const; rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const;
rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() 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)); 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 { rpl::producer<SharedMediaSliceUpdate> Facade::Impl::sharedMediaSliceUpdated() const {
return _sharedMedia.sliceUpdated(); return _sharedMedia.sliceUpdated();
} }
@ -203,6 +213,14 @@ rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const {
return _impl->query(std::move(query)); 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 { rpl::producer<SharedMediaSliceUpdate> Facade::sharedMediaSliceUpdated() const {
return _impl->sharedMediaSliceUpdated(); return _impl->sharedMediaSliceUpdated();
} }

View File

@ -25,6 +25,7 @@ struct SharedMediaRemoveOne;
struct SharedMediaRemoveAll; struct SharedMediaRemoveAll;
struct SharedMediaInvalidateBottom; struct SharedMediaInvalidateBottom;
struct SharedMediaQuery; struct SharedMediaQuery;
struct SharedMediaKey;
using SharedMediaResult = SparseIdsListResult; using SharedMediaResult = SparseIdsListResult;
struct SharedMediaSliceUpdate; struct SharedMediaSliceUpdate;
@ -58,6 +59,8 @@ public:
void invalidate(SharedMediaInvalidateBottom &&query); void invalidate(SharedMediaInvalidateBottom &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; 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<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const;
rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const; rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const;
rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const; rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const;

View File

@ -82,7 +82,10 @@ void SharedMedia::remove(SharedMediaRemoveAll &&query) {
auto peerIt = _lists.find(query.peerId); auto peerIt = _lists.find(query.peerId);
if (peerIt != _lists.end()) { if (peerIt != _lists.end()) {
for (auto index = 0; index != kSharedMediaTypeCount; ++index) { 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)); _allRemoved.fire(std::move(query));
} }
@ -100,6 +103,7 @@ void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) {
rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const { rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const {
Expects(IsValidSharedMediaType(query.key.type)); Expects(IsValidSharedMediaType(query.key.type));
auto peerIt = _lists.find(query.key.peerId); auto peerIt = _lists.find(query.key.peerId);
if (peerIt != _lists.end()) { if (peerIt != _lists.end()) {
auto index = static_cast<int>(query.key.type); 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 { rpl::producer<SharedMediaSliceUpdate> SharedMedia::sliceUpdated() const {
return _sliceUpdated.events(); return _sliceUpdated.events();
} }

View File

@ -26,6 +26,7 @@ enum class SharedMediaType : signed char {
RoundVoiceFile, RoundVoiceFile,
GIF, GIF,
RoundFile, RoundFile,
Pinned,
kCount, kCount,
}; };
@ -106,10 +107,15 @@ struct SharedMediaRemoveOne {
}; };
struct SharedMediaRemoveAll { struct SharedMediaRemoveAll {
SharedMediaRemoveAll(PeerId peerId) : peerId(peerId) { SharedMediaRemoveAll(
PeerId peerId,
SharedMediaTypesMask types = SharedMediaTypesMask::All())
: peerId(peerId)
, types(types) {
} }
PeerId peerId = 0; PeerId peerId = 0;
SharedMediaTypesMask types;
}; };
@ -191,6 +197,8 @@ public:
void invalidate(SharedMediaInvalidateBottom &&query); void invalidate(SharedMediaInvalidateBottom &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; 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<SharedMediaSliceUpdate> sliceUpdated() const;
rpl::producer<SharedMediaRemoveOne> oneRemoved() const; rpl::producer<SharedMediaRemoveOne> oneRemoved() const;
rpl::producer<SharedMediaRemoveAll> allRemoved() const; rpl::producer<SharedMediaRemoveAll> allRemoved() const;

View File

@ -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 { rpl::producer<SparseIdsSliceUpdate> SparseIdsList::sliceUpdated() const {
return _sliceUpdated.events(); return _sliceUpdated.events();
} }

View File

@ -51,6 +51,8 @@ public:
void invalidateBottom(); void invalidateBottom();
rpl::producer<SparseIdsListResult> query(SparseIdsListQuery &&query) const; rpl::producer<SparseIdsListResult> query(SparseIdsListQuery &&query) const;
rpl::producer<SparseIdsSliceUpdate> sliceUpdated() const; rpl::producer<SparseIdsSliceUpdate> sliceUpdated() const;
SparseIdsListResult snapshot(const SparseIdsListQuery &query) const;
bool empty() const;
private: private:
struct Slice { struct Slice {