diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6841fda9ff..4f9db79398 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -800,6 +800,8 @@ PRIVATE history/view/history_view_sponsored_click_handler.h history/view/history_view_sticker_toast.cpp history/view/history_view_sticker_toast.h + history/view/history_view_sublist_section.cpp + history/view/history_view_sublist_section.h history/view/history_view_transcribe_button.cpp history/view/history_view_transcribe_button.h history/view/history_view_translate_bar.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 430a356920..d1fcaf6b53 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2493,6 +2493,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_saved_short" = "Save"; "lng_saved_forward_here" = "Forward messages here for quick access"; "lng_saved_quote_here" = "Quote here to save"; +"lng_saved_open_chat" = "Open Chat"; +"lng_saved_open_channel" = "Open Channel"; +"lng_saved_open_group" = "Open Group"; +"lng_saved_about_hidden" = "Senders of this messages restricted to link their name when forwarding."; "lng_scheduled_messages" = "Scheduled Messages"; "lng_scheduled_messages_empty" = "No scheduled messages here yet..."; diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp index d35beeab70..9c67d41593 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.cpp +++ b/Telegram/SourceFiles/data/data_saved_messages.cpp @@ -138,6 +138,7 @@ void SavedMessages::loadMore(not_null sublist) { MTP_int(0), // min_id MTP_long(0)) // hash ).done([=](const MTPmessages_Messages &result) { + auto count = 0; auto list = (const QVector*)nullptr; result.match([](const MTPDmessages_channelMessages &) { LOG(("API Error: messages.channelMessages in sublist.")); @@ -147,6 +148,11 @@ void SavedMessages::loadMore(not_null sublist) { owner().processUsers(data.vusers()); owner().processChats(data.vchats()); list = &data.vmessages().v; + if constexpr (MTPDmessages_messages::Is()) { + count = int(list->size()); + } else { + count = data.vcount().v; + } }); _loadMoreRequests.remove(sublist); @@ -165,7 +171,7 @@ void SavedMessages::loadMore(not_null sublist) { items.push_back(item); } } - sublist->append(std::move(items)); + sublist->append(std::move(items), count); if (result.type() == mtpc_messages_messages) { sublist->setFullLoaded(); } diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index 98624d6b0a..1f2a00b114 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -45,7 +45,7 @@ auto SavedSublist::messages() const return _items; } -void SavedSublist::applyMaybeLast(not_null item) { +void SavedSublist::applyMaybeLast(not_null item, bool added) { const auto before = []( not_null a, not_null b) { @@ -58,22 +58,28 @@ void SavedSublist::applyMaybeLast(not_null item) { _items.push_back(item); } else if (_items.front() == item) { return; - } else if (_items.size() == 1 && before(_items.front(), item)) { + } else if (!isFullLoaded() + && _items.size() == 1 + && before(_items.front(), item)) { _items[0] = item; } else if (before(_items.back(), item)) { for (auto i = begin(_items); i != end(_items); ++i) { if (item == *i) { - break; + return; } else if (before(*i, item)) { _items.insert(i, item); break; } } } + if (added && _fullCount) { + ++*_fullCount; + } if (_items.front() == item) { setChatListTimeId(item->date()); resolveChatListMessageGroup(); } + _changed.fire({}); } void SavedSublist::removeOne(not_null item) { @@ -81,7 +87,14 @@ void SavedSublist::removeOne(not_null item) { return; } const auto last = (_items.front() == item); - _items.erase(ranges::remove(_items, item), end(_items)); + const auto from = ranges::remove(_items, item); + const auto removed = end(_items) - from; + if (removed) { + _items.erase(from, end(_items)); + } + if (_fullCount) { + --*_fullCount; + } if (last) { if (_items.empty()) { if (isFullLoaded()) { @@ -96,16 +109,38 @@ void SavedSublist::removeOne(not_null item) { setChatListTimeId(_items.front()->date()); } } + if (removed || _fullCount) { + _changed.fire({}); + } } -void SavedSublist::append(std::vector> &&items) { +rpl::producer<> SavedSublist::changes() const { + return _changed.events(); +} + +std::optional SavedSublist::fullCount() const { + return isFullLoaded() ? int(_items.size()) : _fullCount; +} + +rpl::producer SavedSublist::fullCountValue() const { + return _changed.events_starting_with({}) | rpl::map([=] { + return fullCount(); + }) | rpl::filter_optional(); +} + +void SavedSublist::append( + std::vector> &&items, + int fullCount) { + _fullCount = fullCount; if (items.empty()) { setFullLoaded(); } else if (!_items.empty()) { _items.insert(end(_items), begin(items), end(items)); + _changed.fire({}); } else { _items = std::move(items); setChatListTimeId(_items.front()->date()); + _changed.fire({}); } } @@ -119,6 +154,7 @@ void SavedSublist::setFullLoaded(bool loaded) { } else { _flags &= ~Flag::FullLoaded; } + _changed.fire({}); } } diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index 4f37b11e4d..15c3428fff 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -29,11 +29,15 @@ public: [[nodiscard]] auto messages() const -> const std::vector> &; - void applyMaybeLast(not_null item); + void applyMaybeLast(not_null item, bool added = false); void removeOne(not_null item); - void append(std::vector> &&items); + void append(std::vector> &&items, int fullCount); void setFullLoaded(bool loaded = true); + [[nodiscard]] rpl::producer<> changes() const; + [[nodiscard]] std::optional fullCount() const; + [[nodiscard]] rpl::producer fullCountValue() const; + [[nodiscard]] Dialogs::Ui::MessageView &lastItemDialogsView() { return _lastItemDialogsView; } @@ -71,6 +75,8 @@ private: const not_null _history; std::vector> _items; + std::optional _fullCount; + rpl::event_stream<> _changed; Dialogs::Ui::MessageView _lastItemDialogsView; Flags _flags; diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 7ee381f4b3..2396b216f5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -106,6 +106,7 @@ struct EntryState { Scheduled, Pinned, Replies, + SavedSublist, ContextMenu, }; diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 665024a110..bdc0d659c3 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -612,7 +612,7 @@ not_null History::addNewItem( } if (const auto sublist = item->savedSublist()) { - sublist->applyMaybeLast(item); + sublist->applyMaybeLast(item, unread); } return item; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 68f95177bf..a4bfdabb65 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -374,7 +374,6 @@ private: }; - class RepliesMemento final : public Window::SectionMemento { public: RepliesMemento( diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp new file mode 100644 index 0000000000..b92711acfe --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.cpp @@ -0,0 +1,644 @@ +/* +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_sublist_section.h" + +//#include "base/timer_rpl.h" +//#include "apiwrap.h" +//#include "base/event_filter.h" +//#include "base/call_delayed.h" +//#include "base/qt/qt_key_modifiers.h" +//#include "core/file_utilities.h" +#include "main/main_session.h" +//#include "data/data_chat.h" +//#include "data/data_channel.h" +//#include "data/data_changes.h" +#include "data/data_saved_messages.h" +#include "data/data_saved_sublist.h" +#include "data/data_session.h" +//#include "data/data_sparse_ids.h" +//#include "data/data_shared_media.h" +#include "data/data_peer_values.h" +#include "data/data_user.h" +#include "history/view/history_view_top_bar_widget.h" +#include "history/view/history_view_translate_bar.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 "storage/storage_account.h" +//#include "platform/platform_specific.h" +#include "lang/lang_keys.h" +//#include "ui/boxes/confirm_box.h" +//#include "ui/layers/generic_box.h" +//#include "ui/item_text_options.h" +#include "ui/chat/chat_style.h" +//#include "ui/toast/toast.h" +//#include "ui/text/format_values.h" +//#include "ui/text/text_utilities.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/shadow.h" +//#include "ui/ui_utility.h" +//#include "window/window_adaptive.h" +#include "window/window_session_controller.h" +//#include "window/window_peer_menu.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_window.h" +//#include "styles/style_info.h" +//#include "styles/style_boxes.h" +// +//#include + +namespace HistoryView { +namespace { + +} // namespace + +SublistMemento::SublistMemento(not_null sublist) +: _sublist(sublist) { + const auto selfId = sublist->session().userPeerId(); + _list.setAroundPosition({ + .fullId = FullMsgId(selfId, ShowAtUnreadMsgId), + .date = TimeId(0), + }); +} + +object_ptr SublistMemento::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, + _sublist); + result->setInternalState(geometry, this); + return result; +} + +SublistWidget::SublistWidget( + QWidget *parent, + not_null controller, + not_null sublist) +: Window::SectionWidget(parent, controller, sublist->peer()) +, _sublist(sublist) +, _history(sublist->owner().history(sublist->session().user())) +, _topBar(this, controller) +, _topBarShadow(this) +, _translateBar(std::make_unique(this, controller, _history)) +, _scroll(std::make_unique( + this, + controller->chatStyle()->value(lifetime(), st::historyScroll), + false)) +, _openChatButton(std::make_unique( + this, + (_sublist->peer()->isBroadcast() + ? tr::lng_saved_open_channel(tr::now) + : _sublist->peer()->isUser() + ? tr::lng_saved_open_chat(tr::now) + : tr::lng_saved_open_group(tr::now)), + st::historyComposeButton)) +, _cornerButtons( + _scroll.get(), + controller->chatStyle(), + static_cast(this)) { + controller->chatStyle()->paletteChanged( + ) | rpl::start_with_next([=] { + _scroll->updateBars(); + }, _scroll->lifetime()); + + Window::ChatThemeValueFromPeer( + controller, + sublist->peer() + ) | rpl::start_with_next([=](std::shared_ptr &&theme) { + _theme = std::move(theme); + controller->setChatStyleTheme(_theme); + }, lifetime()); + + _topBar->setActiveChat( + TopBarWidget::ActiveChat{ + .key = sublist, + .section = Dialogs::EntryState::Section::SavedSublist, + }, + nullptr); + + _topBar->move(0, 0); + _topBar->resizeToWidth(width()); + _topBar->show(); + _topBar->setCustomTitle(tr::lng_contacts_loading(tr::now)); + + _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()); + + _translateBar->raise(); + _topBarShadow->raise(); + controller->adaptive().value( + ) | rpl::start_with_next([=] { + updateAdaptiveLayout(); + }, lifetime()); + + _inner = _scroll->setOwnedWidget(object_ptr( + this, + controller, + static_cast(this))); + _scroll->move(0, _topBar->height()); + _scroll->show(); + _scroll->scrolls( + ) | rpl::start_with_next([=] { + onScroll(); + }, lifetime()); + + setupOpenChatButton(); + setupTranslateBar(); +} + +SublistWidget::~SublistWidget() = default; + +void SublistWidget::setupOpenChatButton() { + _openChatButton->setClickedCallback([=] { + controller()->showPeerHistory( + _sublist->peer(), + Window::SectionShow::Way::Forward); + }); +} + +void SublistWidget::setupTranslateBar() { + controller()->adaptive().oneColumnValue( + ) | rpl::start_with_next([=, raw = _translateBar.get()](bool one) { + raw->setShadowGeometryPostprocess([=](QRect geometry) { + if (!one) { + geometry.setLeft(geometry.left() + st::lineWidth); + } + return geometry; + }); + }, _translateBar->lifetime()); + + _translateBarHeight = 0; + _translateBar->heightValue( + ) | rpl::start_with_next([=](int height) { + if (const auto delta = height - _translateBarHeight) { + _translateBarHeight = height; + setGeometryWithTopMoved(geometry(), delta); + } + }, _translateBar->lifetime()); + + _translateBar->finishAnimating(); +} + +void SublistWidget::cornerButtonsShowAtPosition( + Data::MessagePosition position) { + showAtPosition(position); +} + +Data::Thread *SublistWidget::cornerButtonsThread() { + return nullptr; +} + +FullMsgId SublistWidget::cornerButtonsCurrentId() { + return {}; +} + +bool SublistWidget::cornerButtonsIgnoreVisibility() { + return animatingShow(); +} + +std::optional SublistWidget::cornerButtonsDownShown() { + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax() || _cornerButtons.replyReturn()) { + return true; + } else if (_inner->loadedAtBottomKnown()) { + return !_inner->loadedAtBottom(); + } + return std::nullopt; +} + +bool SublistWidget::cornerButtonsUnreadMayBeShown() { + return _inner->loadedAtBottomKnown(); +} + +bool SublistWidget::cornerButtonsHas(CornerButtonType type) { + return (type == CornerButtonType::Down); +} + +void SublistWidget::showAtPosition( + Data::MessagePosition position, + FullMsgId originId) { + _inner->showAtPosition( + position, + {}, + _cornerButtons.doneJumpFrom(position.fullId, originId)); +} + +void SublistWidget::updateAdaptiveLayout() { + _topBarShadow->moveToLeft( + controller()->adaptive().isOneColumn() ? 0 : st::lineWidth, + _topBar->height()); +} + +not_null SublistWidget::sublist() const { + return _sublist; +} + +Dialogs::RowDescriptor SublistWidget::activeChat() const { + return { + _history, + FullMsgId(_history->peer->id, ShowAtUnreadMsgId) + }; +} + +QPixmap SublistWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + _topBar->updateControlsVisibility(); + if (params.withTopBarShadow) _topBarShadow->hide(); + auto result = Ui::GrabWidget(this); + if (params.withTopBarShadow) _topBarShadow->show(); + _translateBar->hide(); + return result; +} + +void SublistWidget::checkActivation() { + _inner->checkActivation(); +} + +void SublistWidget::doSetInnerFocus() { + _inner->setFocus(); +} + +bool SublistWidget::showInternal( + not_null memento, + const Window::SectionShow ¶ms) { + if (auto logMemento = dynamic_cast(memento.get())) { + if (logMemento->getSublist() == sublist()) { + restoreState(logMemento); + return true; + } + } + return false; +} + +void SublistWidget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::shared_ptr SublistWidget::createMemento() { + auto result = std::make_shared(sublist()); + saveState(result.get()); + return result; +} + +bool SublistWidget::showMessage( + PeerId peerId, + const Window::SectionShow ¶ms, + MsgId messageId) { + return false; // We want 'Go to original' to work. +} + +void SublistWidget::saveState(not_null memento) { + _inner->saveState(memento->list()); +} + +void SublistWidget::restoreState(not_null memento) { + _inner->restoreState(memento->list()); +} + +void SublistWidget::resizeEvent(QResizeEvent *e) { + if (!width() || !height()) { + return; + } + recountChatWidth(); + updateControlsGeometry(); +} + +void SublistWidget::recountChatWidth() { + auto layout = (width() < st::adaptiveChatWideWidth) + ? Window::Adaptive::ChatLayout::Normal + : Window::Adaptive::ChatLayout::Wide; + controller()->adaptive().setChatLayout(layout); +} + +void SublistWidget::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() - _openChatButton->height(); + _openChatButton->resizeToWidth(width()); + _openChatButton->move(0, bottom); + const auto controlsHeight = 0; + auto top = _topBar->height(); + _translateBar->move(0, top); + _translateBar->resizeToWidth(contentWidth); + top += _translateBarHeight; + const auto scrollHeight = bottom - top - 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, top); + if (!_scroll->isHidden()) { + if (newScrollTop) { + _scroll->scrollToY(*newScrollTop); + } + updateInnerVisibleArea(); + } + + _cornerButtons.updatePositions(); +} + +void SublistWidget::paintEvent(QPaintEvent *e) { + if (animatingShow()) { + SectionWidget::paintEvent(e); + return; + } else if (controller()->contentOverlapped(this, e)) { + return; + } + + const auto aboveHeight = _topBar->height(); + const auto bg = e->rect().intersected( + QRect(0, aboveHeight, width(), height() - aboveHeight)); + SectionWidget::PaintBackground(controller(), _theme.get(), this, bg); +} + +void SublistWidget::onScroll() { + if (_skipScrollEvent) { + return; + } + updateInnerVisibleArea(); +} + +void SublistWidget::updateInnerVisibleArea() { + const auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + _cornerButtons.updateJumpDownVisibility(); + _cornerButtons.updateUnreadThingsVisibility(); +} + +void SublistWidget::showAnimatedHook( + const Window::SectionSlideParams ¶ms) { + _topBar->setAnimatingMode(true); + if (params.withTopBarShadow) { + _topBarShadow->show(); + } +} + +void SublistWidget::showFinishedHook() { + _topBar->setAnimatingMode(false); + _inner->showFinished(); + _translateBar->show(); +} + +bool SublistWidget::floatPlayerHandleWheelEvent(QEvent *e) { + return _scroll->viewportEvent(e); +} + +QRect SublistWidget::floatPlayerAvailableRect() { + return mapToGlobal(_scroll->geometry()); +} + +Context SublistWidget::listContext() { + return Context::Pinned; +} + +bool SublistWidget::listScrollTo(int top, bool syntetic) { + top = std::clamp(top, 0, _scroll->scrollTopMax()); + if (_scroll->scrollTop() == top) { + updateInnerVisibleArea(); + return false; + } + _scroll->scrollToY(top); + return true; +} + +void SublistWidget::listCancelRequest() { + if (_inner && !_inner->getSelectedIds().empty()) { + clearSelected(); + return; + } + controller()->showBackFromStack(); +} + +void SublistWidget::listDeleteRequest() { + confirmDeleteSelected(); +} + +void SublistWidget::listTryProcessKeyInput(not_null e) { +} + +rpl::producer SublistWidget::listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) { + const auto messageId = aroundId.fullId.msg + ? aroundId.fullId.msg + : (ServerMaxMsgId - 1); + return [=](auto consumer) { + const auto pushSlice = [=] { + auto result = Data::MessagesSlice(); + result.fullCount = _sublist->fullCount(); + const auto &messages = _sublist->messages(); + const auto i = ranges::lower_bound( + messages, + messageId, + ranges::greater(), + [](not_null item) { return item->id; }); + const auto before = int(end(messages) - i); + const auto useBefore = std::min(before, limitBefore); + const auto after = int(i - begin(messages)); + const auto useAfter = std::min(after, limitAfter); + const auto from = i - useAfter; + const auto till = i + useBefore; + auto nearestDistance = std::numeric_limits::max(); + result.ids.reserve(useAfter + useBefore); + for (auto j = till; j != from;) { + const auto item = *--j; + result.ids.push_back(item->fullId()); + const auto distance = std::abs((messageId - item->id).bare); + if (nearestDistance > distance) { + nearestDistance = distance; + result.nearestToAround = result.ids.back(); + } + } + result.skippedAfter = after - useAfter; + result.skippedBefore = result.fullCount + ? (*result.fullCount - after - useBefore) + : std::optional(); + if (!result.fullCount || useBefore < limitBefore) { + _sublist->owner().savedMessages().loadMore(_sublist); + } + consumer.put_next(std::move(result)); + }; + auto lifetime = rpl::lifetime(); + _sublist->changes() | rpl::start_with_next(pushSlice, lifetime); + pushSlice(); + return lifetime; + }; +} + +bool SublistWidget::listAllowsMultiSelect() { + return true; +} + +bool SublistWidget::listIsItemGoodForSelection( + not_null item) { + return item->isRegular() && !item->isService(); +} + +bool SublistWidget::listIsLessInOrder( + not_null first, + not_null second) { + return first->id < second->id; +} + +void SublistWidget::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 SublistWidget::listMarkReadTill(not_null item) { +} + +void SublistWidget::listMarkContentsRead( + const base::flat_set> &items) { +} + +MessagesBarData SublistWidget::listMessagesBar( + const std::vector> &elements) { + return {}; +} + +void SublistWidget::listContentRefreshed() { +} + +void SublistWidget::listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) { +} + +bool SublistWidget::listElementHideReply(not_null view) { + return false; +} + +bool SublistWidget::listElementShownUnread(not_null view) { + return view->data()->unread(view->data()->history()); +} + +bool SublistWidget::listIsGoodForAroundPosition( + not_null view) { + return view->data()->isRegular(); +} + +void SublistWidget::listSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + +void SublistWidget::listHandleViaClick(not_null bot) { +} + +not_null SublistWidget::listChatTheme() { + return _theme.get(); +} + +CopyRestrictionType SublistWidget::listCopyRestrictionType( + HistoryItem *item) { + return CopyRestrictionTypeFor(_history->peer, item); +} + +CopyRestrictionType SublistWidget::listCopyMediaRestrictionType( + not_null item) { + return CopyMediaRestrictionTypeFor(_history->peer, item); +} + +CopyRestrictionType SublistWidget::listSelectRestrictionType() { + return SelectRestrictionTypeFor(_history->peer); +} + +auto SublistWidget::listAllowedReactionsValue() +-> rpl::producer { + return Data::PeerAllowedReactionsValue(_history->peer); +} + +void SublistWidget::listShowPremiumToast(not_null document) { +} + +void SublistWidget::listOpenPhoto( + not_null photo, + FullMsgId context) { + controller()->openPhoto(photo, { context }); +} + +void SublistWidget::listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) { + controller()->openDocument(document, showInMediaView, { context }); +} + +void SublistWidget::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { +} + +QString SublistWidget::listElementAuthorRank(not_null view) { + return {}; +} + +History *SublistWidget::listTranslateHistory() { + return _history; +} + +void SublistWidget::listAddTranslatedItems( + not_null tracker) { +} + +void SublistWidget::confirmDeleteSelected() { + ConfirmDeleteSelectedItems(_inner); +} + +void SublistWidget::confirmForwardSelected() { + ConfirmForwardSelectedItems(_inner); +} + +void SublistWidget::clearSelected() { + _inner->cancelSelection(); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_sublist_section.h b/Telegram/SourceFiles/history/view/history_view_sublist_section.h new file mode 100644 index 0000000000..751784db29 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_sublist_section.h @@ -0,0 +1,215 @@ +/* +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 "history/view/history_view_corner_buttons.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; +} // namespace Ui + +namespace Profile { +class BackButton; +} // namespace Profile + +namespace HistoryView { + +class Element; +class TopBarWidget; +class SublistMemento; +class TranslateBar; + +class SublistWidget final + : public Window::SectionWidget + , private ListDelegate + , private CornerButtonsDelegate { +public: + SublistWidget( + QWidget *parent, + not_null controller, + not_null sublist); + ~SublistWidget(); + + [[nodiscard]] not_null sublist() 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::shared_ptr createMemento() override; + bool showMessage( + PeerId peerId, + const Window::SectionShow ¶ms, + MsgId messageId) override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + Window::SectionActionResult sendBotCommand( + Bot::SendCommandRequest request) override { + return Window::SectionActionResult::Fallback; + } + + // Float player interface. + bool floatPlayerHandleWheelEvent(QEvent *e) override; + QRect floatPlayerAvailableRect() override; + + // ListDelegate interface. + Context listContext() override; + bool listScrollTo(int top, bool syntetic = true) override; + void listCancelRequest() override; + void listDeleteRequest() override; + void listTryProcessKeyInput(not_null e) 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 listMarkReadTill(not_null item) override; + void listMarkContentsRead( + const base::flat_set> &items) override; + MessagesBarData listMessagesBar( + const std::vector> &elements) override; + void listContentRefreshed() override; + void listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) override; + bool listElementHideReply(not_null view) override; + bool listElementShownUnread(not_null view) override; + bool listIsGoodForAroundPosition(not_null view) override; + void listSendBotCommand( + const QString &command, + const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; + not_null listChatTheme() override; + CopyRestrictionType listCopyRestrictionType(HistoryItem *item) override; + CopyRestrictionType listCopyMediaRestrictionType( + not_null item) override; + CopyRestrictionType listSelectRestrictionType() override; + auto listAllowedReactionsValue() + -> rpl::producer override; + void listShowPremiumToast(not_null document) override; + void listOpenPhoto( + not_null photo, + FullMsgId context) override; + void listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) override; + void listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) override; + QString listElementAuthorRank(not_null view) override; + History *listTranslateHistory() override; + void listAddTranslatedItems( + not_null tracker) override; + + // CornerButtonsDelegate delegate. + void cornerButtonsShowAtPosition( + Data::MessagePosition position) override; + Data::Thread *cornerButtonsThread() override; + FullMsgId cornerButtonsCurrentId() override; + bool cornerButtonsIgnoreVisibility() override; + std::optional cornerButtonsDownShown() override; + bool cornerButtonsUnreadMayBeShown() override; + bool cornerButtonsHas(CornerButtonType type) override; + +private: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook( + const Window::SectionSlideParams ¶ms) override; + void showFinishedHook() override; + void doSetInnerFocus() override; + void checkActivation() override; + + void onScroll(); + void updateInnerVisibleArea(); + void updateControlsGeometry(); + void updateAdaptiveLayout(); + void saveState(not_null memento); + void restoreState(not_null memento); + void showAtPosition( + Data::MessagePosition position, + FullMsgId originId = {}); + + void setupOpenChatButton(); + void setupTranslateBar(); + + void confirmDeleteSelected(); + void confirmForwardSelected(); + void clearSelected(); + void recountChatWidth(); + + const not_null _sublist; + const not_null _history; + std::shared_ptr _theme; + QPointer _inner; + object_ptr _topBar; + object_ptr _topBarShadow; + + std::unique_ptr _translateBar; + int _translateBarHeight = 0; + + bool _skipScrollEvent = false; + std::unique_ptr _scroll; + std::unique_ptr _openChatButton; + + CornerButtons _cornerButtons; + +}; + +class SublistMemento : public Window::SectionMemento { +public: + explicit SublistMemento(not_null sublist); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) override; + + [[nodiscard]] not_null getSublist() const { + return _sublist; + } + + [[nodiscard]] not_null list() { + return &_list; + } + +private: + const not_null _sublist; + ListMemento _list; + +}; + +} // namespace HistoryView 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 ec687a9e85..d357079a14 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -469,6 +469,7 @@ void TopBarWidget::paintTopBar(Painter &p) { const auto now = crl::now(); const auto history = _activeChat.key.owningHistory(); const auto folder = _activeChat.key.folder(); + const auto sublist = _activeChat.key.sublist(); const auto topic = _activeChat.key.topic(); if (topic && _activeChat.section == Section::Replies) { p.setPen(st::dialogsNameFg); @@ -491,8 +492,9 @@ void TopBarWidget::paintTopBar(Painter &p) { p.setPen(st::historyStatusFg); p.drawTextLeft(nameleft, statustop, width(), _customTitleText); } + } else if (sublist) { } else if (folder - || history->peer->sharedMediaInfo() + || (history && history->peer->sharedMediaInfo()) || (_activeChat.section == Section::Scheduled) || (_activeChat.section == Section::Pinned)) { auto text = (_activeChat.section == Section::Scheduled) @@ -689,6 +691,10 @@ void TopBarWidget::infoClicked() { return; } else if (const auto topic = key.topic()) { _controller->showSection(std::make_shared(topic)); + } else if (const auto sublist = key.sublist()) { + _controller->showSection(std::make_shared( + _controller->session().user(), + Info::Section(Storage::SharedMediaType::Photo))); } else if (key.peer()->savedSublistsInfo()) { _controller->showSection(std::make_shared( key.peer(), diff --git a/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp index 28014e8a3e..e846977802 100644 --- a/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp +++ b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_user.h" #include "dialogs/dialogs_inner_widget.h" +#include "history/view/history_view_sublist_section.h" #include "info/info_controller.h" #include "info/info_memento.h" #include "main/main_session.h" @@ -48,6 +49,14 @@ SublistsWidget::SublistsWidget( _inner->showSavedSublists(); _inner->setNarrowRatio(0.); + _inner->chosenRow() | rpl::start_with_next([=](Dialogs::ChosenRow row) { + if (const auto sublist = row.key.sublist()) { + controller->showSection( + std::make_shared(sublist), + Window::SectionShow::Way::Forward); + } + }, _inner->lifetime()); + const auto saved = &controller->session().data().savedMessages(); _inner->heightValue() | rpl::start_with_next([=] { if (!saved->supported()) {