diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f27aeb137e..a04fd853a3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 1af259a670..e2b6b8c9bd 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -383,7 +383,6 @@ void ChannelData::setUnavailableReasons( void ChannelData::setAvailableMinId(MsgId availableMinId) { if (_availableMinId != availableMinId) { _availableMinId = availableMinId; - clearPinnedMessages(_availableMinId + 1); } } diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index c8cea7dcc7..25227cc24a 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -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( - peerToChannel(id)); + _pinnedMessages = std::make_unique(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 &&ids, - MsgRange noSkipRange, - std::optional 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 { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index c8bf6eac16..dbdf8d0941 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -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 &&ids, - MsgRange noSkipRange, - std::optional count); - void removePinnedMessage(MsgId messageId); + void clearPinnedMessages(); Data::PinnedMessages *currentPinnedMessages() const { return _pinnedMessages.get(); } diff --git a/Telegram/SourceFiles/data/data_pinned_messages.cpp b/Telegram/SourceFiles/data/data_pinned_messages.cpp index e9fc9a110a..c1121df8a6 100644 --- a/Telegram/SourceFiles/data/data_pinned_messages.cpp +++ b/Telegram/SourceFiles/data/data_pinned_messages.cpp @@ -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 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 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 &&ids, - MsgRange range, - std::optional 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 diff --git a/Telegram/SourceFiles/data/data_pinned_messages.h b/Telegram/SourceFiles/data/data_pinned_messages.h index 33cf120e53..5edcd29ec9 100644 --- a/Telegram/SourceFiles/data/data_pinned_messages.h +++ b/Telegram/SourceFiles/data/data_pinned_messages.h @@ -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 peer); [[nodiscard]] bool empty() const; [[nodiscard]] MsgId topId() const; [[nodiscard]] rpl::producer viewer( MsgId aroundId, int limit) const; - - void add(MsgId messageId); - void add( - std::vector &&ids, - MsgRange range, - std::optional 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 _peer; + Storage::Facade &_storage; }; diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 367ed2e979..b12f7fbc54 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -55,6 +55,8 @@ std::optional PrepareSearchRequest( return MTP_inputMessagesFilterUrl(); case Type::ChatPhoto: return MTP_inputMessagesFilterChatPhotos(); + case Type::Pinned: + return MTP_inputMessagesFilterPinned(); } return MTP_inputMessagesFilterEmpty(); }(); diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp index 849fac0e33..8497ce8f79 100644 --- a/Telegram/SourceFiles/data/data_shared_media.cpp +++ b/Telegram/SourceFiles/data/data_shared_media.cpp @@ -123,7 +123,8 @@ rpl::producer 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); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 847d512a9e..1459ce95f6 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -177,7 +177,6 @@ void History::itemVanished(not_null item) { && unreadCount() > 0) { setUnreadCount(unreadCount() - 1); } - peer->removePinnedMessage(item->id); } void History::setLocalDraft(std::unique_ptr &&draft) { @@ -707,9 +706,6 @@ not_null 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 &slice) { @@ -1328,7 +1324,6 @@ void History::checkAddAllToUnreadMentions() { void History::addToSharedMedia( const std::vector> &items) { - auto pinned = std::vector(); std::vector 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); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 0bae6ffd7b..71d4acb53f 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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)); } } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index dba278fe5e..f11299973a 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -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; } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 233a027af5..44d0fe9915 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -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(_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 peer, MsgId msgId) { const auto session = &peer->session(); Ui::show(Box(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), diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp new file mode 100644 index 0000000000..5c9e86e6e6 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -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 +#include + +namespace HistoryView { +namespace { + +} // namespace + +object_ptr PinnedMemento::createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) { + if (column == Window::Column::Third) { + return nullptr; + } + auto result = object_ptr( + parent, + controller, + _history); + result->setInternalState(geometry, this); + return result; +} + +PinnedWidget::PinnedWidget( + QWidget *parent, + not_null controller, + not_null history) +: Window::SectionWidget(parent, controller) +, _history(history) +, _topBar(this, controller) +, _topBarShadow(this) +, _scroll(std::make_unique(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( + this, + controller, + static_cast(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 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 { + 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 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 memento, + const Window::SectionShow ¶ms) { + if (auto logMemento = dynamic_cast(memento.get())) { + if (logMemento->getHistory() == history()) { + restoreState(logMemento); + return true; + } + } + return false; +} + +void PinnedWidget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::unique_ptr PinnedWidget::createMemento() { + auto result = std::make_unique(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(¶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 memento) { + _inner->saveState(memento->list()); +} + +void PinnedWidget::restoreState(not_null 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 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 item) { + return !item->isSending() && !item->hasFailed(); +} + +bool PinnedWidget::listIsLessInOrder( + not_null first, + not_null 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> &elements) { + return {}; +} + +void PinnedWidget::listContentRefreshed() { +} + +ClickHandlerPtr PinnedWidget::listDateLink(not_null view) { + return nullptr; +} + +bool PinnedWidget::listElementHideReply(not_null view) { + return false; +} + +bool PinnedWidget::listElementShownUnread(not_null view) { + return view->data()->unread(); +} + +bool PinnedWidget::listIsGoodForAroundPosition( + not_null 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( + &_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 diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.h b/Telegram/SourceFiles/history/view/history_view_pinned_section.h new file mode 100644 index 0000000000..b355e77887 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.h @@ -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 controller, + not_null history); + ~PinnedWidget(); + + [[nodiscard]] not_null history() const; + Dialogs::RowDescriptor activeChat() const override; + + bool hasTopBarShadow() const override { + return true; + } + + QPixmap grabForShowAnimation( + const Window::SectionSlideParams ¶ms) override; + + bool showInternal( + not_null memento, + const Window::SectionShow ¶ms) override; + std::unique_ptr createMemento() override; + bool showMessage( + PeerId peerId, + const Window::SectionShow ¶ms, + MsgId messageId) override; + + void setInternalState( + const QRect &geometry, + not_null 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 listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) override; + bool listAllowsMultiSelect() override; + bool listIsItemGoodForSelection(not_null item) override; + bool listIsLessInOrder( + not_null first, + not_null second) override; + void listSelectionChanged(SelectedItems &&items) override; + void listVisibleItemsChanged(HistoryItemsList &&items) override; + MessagesBarData listMessagesBar( + const std::vector> &elements) override; + void listContentRefreshed() override; + ClickHandlerPtr listDateLink(not_null view) override; + bool listElementHideReply(not_null view) override; + bool listElementShownUnread(not_null view) override; + bool listIsGoodForAroundPosition(not_null 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 memento); + void restoreState(not_null 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; + QPointer _inner; + object_ptr _topBar; + object_ptr _topBarShadow; + + bool _skipScrollEvent = false; + std::unique_ptr _scroll; + + Ui::Animations::Simple _scrollDownShown; + bool _scrollDownIsShown = false; + object_ptr _scrollDown; + + Data::MessagesSlice _lastSlice; + +}; + +class PinnedMemento : public Window::SectionMemento { +public: + PinnedMemento( + not_null history, + MsgId highlightId = 0) + : _history(history) + , _highlightId(highlightId) { + } + + object_ptr createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) override; + + [[nodiscard]] not_null getHistory() const { + return _history; + } + + [[nodiscard]] not_null list() { + return &_list; + } + [[nodiscard]] MsgId getHighlightId() const { + return _highlightId; + } + +private: + const not_null _history; + const MsgId _highlightId = 0; + ListMemento _list; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp index 6962a49030..7600c5bfd6 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.cpp @@ -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) { 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) { } }, _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 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(); - 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*)nullptr; - } break; - } - Unexpected("messages.Messages type in PinnedTracker::apply."); - }(); - - if (!messages) { - return; - } - - const auto addType = NewMessageType::Existing; - auto list = std::vector(); - 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 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(); +// 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*)nullptr; +// } break; +// } +// Unexpected("messages.Messages type in PinnedTracker::apply."); +// }(); +// +// if (!messages) { +// return; +// } +// +// const auto addType = NewMessageType::Existing; +// auto list = std::vector(); +// 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 diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h index ac762d996c..916d9ada79 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_tracker.h @@ -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); - 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; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 430225ba6c..d2d4a9b9ba 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -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()) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index cde6be8f29..127c18dc4e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -168,7 +168,6 @@ private: void updateScrollDownPosition(); void updatePinnedVisibility(); - void confirmSendNowSelected(); void confirmDeleteSelected(); void confirmForwardSelected(); void clearSelected(); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 289eca0e33..d6bb60e7de 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -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() diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index cd77fbf169..770929d0e3 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -46,6 +46,7 @@ public: enum class Section { History, Scheduled, + Pinned, Replies, }; diff --git a/Telegram/SourceFiles/storage/storage_facade.cpp b/Telegram/SourceFiles/storage/storage_facade.cpp index d6360d11d3..5f87aa46e9 100644 --- a/Telegram/SourceFiles/storage/storage_facade.cpp +++ b/Telegram/SourceFiles/storage/storage_facade.cpp @@ -22,6 +22,8 @@ public: void remove(SharedMediaRemoveAll &&query); void invalidate(SharedMediaInvalidateBottom &&query); rpl::producer query(SharedMediaQuery &&query) const; + SharedMediaResult snapshot(const SharedMediaQuery &query) const; + bool empty(const SharedMediaKey &key) const; rpl::producer sharedMediaSliceUpdated() const; rpl::producer sharedMediaOneRemoved() const; rpl::producer sharedMediaAllRemoved() const; @@ -83,6 +85,14 @@ rpl::producer 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 Facade::Impl::sharedMediaSliceUpdated() const { return _sharedMedia.sliceUpdated(); } @@ -203,6 +213,14 @@ rpl::producer 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 Facade::sharedMediaSliceUpdated() const { return _impl->sharedMediaSliceUpdated(); } diff --git a/Telegram/SourceFiles/storage/storage_facade.h b/Telegram/SourceFiles/storage/storage_facade.h index 02ac610dfa..6b176c7d81 100644 --- a/Telegram/SourceFiles/storage/storage_facade.h +++ b/Telegram/SourceFiles/storage/storage_facade.h @@ -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 query(SharedMediaQuery &&query) const; + SharedMediaResult snapshot(const SharedMediaQuery &query) const; + bool empty(const SharedMediaKey &key) const; rpl::producer sharedMediaSliceUpdated() const; rpl::producer sharedMediaOneRemoved() const; rpl::producer sharedMediaAllRemoved() const; diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp index 14d9d5c19d..225af5e391 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.cpp +++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp @@ -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(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 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(query.key.type); @@ -114,6 +118,31 @@ rpl::producer 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(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(key.type); + return peerIt->second[index].empty(); + } + return true; +} + rpl::producer SharedMedia::sliceUpdated() const { return _sliceUpdated.events(); } diff --git a/Telegram/SourceFiles/storage/storage_shared_media.h b/Telegram/SourceFiles/storage/storage_shared_media.h index e7bd5cbf92..b2cd80929a 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.h +++ b/Telegram/SourceFiles/storage/storage_shared_media.h @@ -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 query(SharedMediaQuery &&query) const; + SharedMediaResult snapshot(const SharedMediaQuery &query) const; + bool empty(const SharedMediaKey &key) const; rpl::producer sliceUpdated() const; rpl::producer oneRemoved() const; rpl::producer allRemoved() const; diff --git a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp index dce3846e50..9cc01c70c9 100644 --- a/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp +++ b/Telegram/SourceFiles/storage/storage_sparse_ids_list.cpp @@ -200,6 +200,35 @@ rpl::producer 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 SparseIdsList::sliceUpdated() const { return _sliceUpdated.events(); } diff --git a/Telegram/SourceFiles/storage/storage_sparse_ids_list.h b/Telegram/SourceFiles/storage/storage_sparse_ids_list.h index 1081c48c7f..88635f71a3 100644 --- a/Telegram/SourceFiles/storage/storage_sparse_ids_list.h +++ b/Telegram/SourceFiles/storage/storage_sparse_ids_list.h @@ -51,6 +51,8 @@ public: void invalidateBottom(); rpl::producer query(SparseIdsListQuery &&query) const; rpl::producer sliceUpdated() const; + SparseIdsListResult snapshot(const SparseIdsListQuery &query) const; + bool empty() const; private: struct Slice {