diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 0524380f6a..2956a33f25 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -688,6 +688,8 @@ PRIVATE data/data_photo_media.h data/data_poll.cpp data/data_poll.h + data/data_poll_messages.cpp + data/data_poll_messages.h data/data_premium_limits.cpp data/data_premium_limits.h data/data_pts_waiter.cpp @@ -1111,6 +1113,8 @@ PRIVATE info/peer_gifts/info_peer_gifts_common.h info/peer_gifts/info_peer_gifts_widget.cpp info/peer_gifts/info_peer_gifts_widget.h + info/polls/info_polls_list_widget.cpp + info/polls/info_polls_list_widget.h info/polls/info_polls_results_inner_widget.cpp info/polls/info_polls_results_inner_widget.h info/polls/info_polls_results_widget.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index eb142ecb65..8d9fb4a4de 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1621,6 +1621,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_photos#other" = "{count} photos"; "lng_profile_gifs#one" = "{count} GIF"; "lng_profile_gifs#other" = "{count} GIFs"; +"lng_profile_polls#one" = "{count} poll"; +"lng_profile_polls#other" = "{count} polls"; "lng_profile_videos#one" = "{count} video"; "lng_profile_videos#other" = "{count} videos"; "lng_profile_songs#one" = "{count} audio file"; @@ -1789,6 +1791,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_media_type_files" = "Files"; "lng_media_type_audios" = "Voice messages"; "lng_media_type_links" = "Shared links"; +"lng_media_type_polls" = "Polls"; "lng_media_type_rounds" = "Video messages"; "lng_media_saved_music_your" = "Your playlist"; "lng_media_saved_music_title" = "Playlist"; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index bfd73af21b..dc489b9ffd 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2318,6 +2318,11 @@ PollData *MediaPoll::poll() const { return _poll; } +Storage::SharedMediaTypesMask MediaPoll::sharedMediaTypes() const { + return Storage::SharedMediaTypesMask{} + .added(Storage::SharedMediaType::Poll); +} + TextWithEntities MediaPoll::notificationText() const { return TextWithEntities() .append(QChar(0xD83D)) diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 529fd120d5..6a061bb77e 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -633,6 +633,7 @@ public: std::unique_ptr clone(not_null parent) override; PollData *poll() const override; + Storage::SharedMediaTypesMask sharedMediaTypes() const override; TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; diff --git a/Telegram/SourceFiles/data/data_poll_messages.cpp b/Telegram/SourceFiles/data/data_poll_messages.cpp new file mode 100644 index 0000000000..a40eef8334 --- /dev/null +++ b/Telegram/SourceFiles/data/data_poll_messages.cpp @@ -0,0 +1,69 @@ +/* +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 "data/data_poll_messages.h" + +#include "data/data_messages.h" +#include "data/data_shared_media.h" +#include "data/data_sparse_ids.h" +#include "data/data_chat.h" +#include "history/history.h" +#include "main/main_session.h" +#include "storage/storage_shared_media.h" + +namespace Data { + +rpl::producer PollMessagesViewer( + not_null session, + not_null history, + MsgId topicRootId, + PeerId monoforumPeerId, + MessagePosition aroundId, + int limitBefore, + int limitAfter) { + const auto peerId = history->peer->id; + const auto migrateFrom = history->peer->migrateFrom(); + const auto migratedPeerId = migrateFrom ? migrateFrom->id : PeerId(0); + const auto messageId = ((aroundId.fullId.msg == ShowAtTheEndMsgId) + || (aroundId == MaxMessagePosition)) + ? (ServerMaxMsgId - 1) + : (aroundId.fullId.peer == peerId) + ? aroundId.fullId.msg + : (aroundId.fullId.msg + ? (aroundId.fullId.msg - ServerMaxMsgId) + : (ServerMaxMsgId - 1)); + const auto key = SharedMediaMergedKey( + SparseIdsMergedSlice::Key( + peerId, + topicRootId, + monoforumPeerId, + migratedPeerId, + messageId), + Storage::SharedMediaType::Poll); + return SharedMediaMergedViewer( + session, + key, + limitBefore, + limitAfter + ) | rpl::map([=](SparseIdsMergedSlice &&slice) { + auto result = 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 = *msgId; + } + for (auto i = 0; i != count; ++i) { + result.ids.push_back(slice[i]); + } + return result; + }); +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_poll_messages.h b/Telegram/SourceFiles/data/data_poll_messages.h new file mode 100644 index 0000000000..0febf13ef5 --- /dev/null +++ b/Telegram/SourceFiles/data/data_poll_messages.h @@ -0,0 +1,30 @@ +/* +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 + +class History; + +namespace Main { +class Session; +} // namespace Main + +namespace Data { + +struct MessagesSlice; +struct MessagePosition; + +[[nodiscard]] rpl::producer PollMessagesViewer( + not_null session, + not_null history, + MsgId topicRootId, + PeerId monoforumPeerId, + MessagePosition aroundId, + int limitBefore, + int limitAfter); + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 2d01942c56..5e7fdddd08 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -53,6 +53,8 @@ MTPMessagesFilter PrepareSearchFilter(Storage::SharedMediaType type) { return MTP_inputMessagesFilterChatPhotos(); case Type::Pinned: return MTP_inputMessagesFilterPinned(); + case Type::Poll: + return MTP_inputMessagesFilterPoll(); } return MTP_inputMessagesFilterEmpty(); } diff --git a/Telegram/SourceFiles/data/data_shared_media.cpp b/Telegram/SourceFiles/data/data_shared_media.cpp index 96d32eae4c..8bf0acbba2 100644 --- a/Telegram/SourceFiles/data/data_shared_media.cpp +++ b/Telegram/SourceFiles/data/data_shared_media.cpp @@ -79,7 +79,8 @@ std::optional SharedMediaOverviewType( case Type::MusicFile: case Type::File: case Type::RoundVoiceFile: - case Type::Link: return type; + case Type::Link: + case Type::Poll: return type; } return std::nullopt; } diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index 73dc510ac5..3b6fd68332 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -135,7 +135,9 @@ void ElementHighlighter::highlight(Highlight data) { } } _highlighted = data; - _animation.start((!data.part.empty() || data.todoListId) + _animation.start((!data.part.empty() + || data.todoListId + || !data.pollOption.isEmpty()) && !IsSubGroupSelection(data.part)); repaintHighlightedItem(view); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index f947c46cba..237ada8ebe 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -814,8 +814,9 @@ bool AddGoToMessageAction( const auto view = request.view; if (!view || !view->data()->isRegular() - || context != Context::Pinned - || !view->hasOutLayout()) { + || (context != Context::Pinned + && context != Context::ChatPreview) + || (context == Context::Pinned && !view->hasOutLayout())) { return false; } const auto itemId = view->data()->fullId(); @@ -1518,7 +1519,8 @@ void AddPollActions( if ((context != Context::History) && (context != Context::Replies) && (context != Context::Pinned) - && (context != Context::ScheduledTopic)) { + && (context != Context::ScheduledTopic) + && (context != Context::ChatPreview)) { return; } if (poll->closed()) { diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 30d94645bb..f50318db5f 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -661,6 +661,7 @@ infoIconMediaGroup: icon {{ "info/info_common_groups", infoIconFg }}; infoIconMediaChannel: icon {{ "menu/channel", infoIconFg, point(4px, 4px) }}; infoIconMediaBot: icon {{ "menu/bot", infoIconFg, point(4px, 4px) }}; infoIconMediaVoice: icon {{ "info/info_media_voice", infoIconFg }}; +infoIconMediaPoll: icon {{ "menu/create_poll", infoIconFg, point(4px, 4px) }}; infoIconMediaStories: icon {{ "info/info_media_stories", infoIconFg }}; infoIconMediaSaved: icon {{ "info/info_media_saved", infoIconFg }}; infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }}; diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index b60b739454..57a435b9e9 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -434,7 +434,8 @@ void Controller::updateSearchControllers( const auto hasMembersSearch = (type == Type::Members) || (type == Type::Profile); const auto searchQuery = memento->searchFieldQuery(); - if (type == Type::Media) { + if (type == Type::Media + && mediaType != Section::MediaType::Poll) { _searchController = std::make_unique(&session()); auto mediaMemento = dynamic_cast(memento.get()); diff --git a/Telegram/SourceFiles/info/info_memento.cpp b/Telegram/SourceFiles/info/info_memento.cpp index 51be6a159b..86ec371fe4 100644 --- a/Telegram/SourceFiles/info/info_memento.cpp +++ b/Telegram/SourceFiles/info/info_memento.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/reactions_list/info_reactions_list_widget.h" #include "info/requests_list/info_requests_list_widget.h" #include "info/peer_gifts/info_peer_gifts_widget.h" +#include "info/polls/info_polls_list_widget.h" #include "info/polls/info_polls_results_widget.h" #include "info/info_section_widget.h" #include "info/info_layer_widget.h" @@ -197,6 +198,11 @@ std::shared_ptr Memento::DefaultContent( peer, migratedPeerId); case Section::Type::Media: + if (section.mediaType() == Storage::SharedMediaType::Poll) { + return std::make_shared( + peer, + migratedPeerId); + } return std::make_shared( peer, migratedPeerId, @@ -231,6 +237,9 @@ std::shared_ptr Memento::DefaultContent( case Section::Type::Profile: return std::make_shared(topic); case Section::Type::Media: + if (section.mediaType() == Storage::SharedMediaType::Poll) { + return std::make_shared(topic); + } return std::make_shared(topic, section.mediaType()); case Section::Type::Members: return std::make_shared(peer, migratedPeerId); @@ -245,6 +254,9 @@ std::shared_ptr Memento::DefaultContent( case Section::Type::Profile: return std::make_shared(sublist); case Section::Type::Media: + if (section.mediaType() == Storage::SharedMediaType::Poll) { + return std::make_shared(sublist); + } return std::make_shared( sublist, section.mediaType()); diff --git a/Telegram/SourceFiles/info/media/info_media_buttons.cpp b/Telegram/SourceFiles/info/media/info_media_buttons.cpp index a0df1d0f79..c45a4716cc 100644 --- a/Telegram/SourceFiles/info/media/info_media_buttons.cpp +++ b/Telegram/SourceFiles/info/media/info_media_buttons.cpp @@ -51,7 +51,8 @@ namespace { || (type == Type::MusicFile) || (type == Type::Link) || (type == Type::RoundVoiceFile) - || (type == Type::GIF); + || (type == Type::GIF) + || (type == Type::Poll); } [[nodiscard]] Window::SeparateId SeparateId( @@ -111,6 +112,7 @@ tr::phrase MediaTextPhrase(Type type) { case Type::MusicFile: return tr::lng_profile_songs; case Type::Link: return tr::lng_profile_shared_links; case Type::RoundVoiceFile: return tr::lng_profile_audios; + case Type::Poll: return tr::lng_profile_polls; } Unexpected("Type in MediaTextPhrase()"); }; diff --git a/Telegram/SourceFiles/info/media/info_media_widget.cpp b/Telegram/SourceFiles/info/media/info_media_widget.cpp index 6b431296c3..e493ba8326 100644 --- a/Telegram/SourceFiles/info/media/info_media_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_widget.cpp @@ -64,6 +64,8 @@ tr::phrase<> SharedMediaTitle(Type type) { return tr::lng_media_type_links; case Type::RoundFile: return tr::lng_media_type_rounds; + case Type::Poll: + return tr::lng_media_type_polls; } Unexpected("Bad media type in Info::TitleValue()"); } diff --git a/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp b/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp new file mode 100644 index 0000000000..ef53f6b29a --- /dev/null +++ b/Telegram/SourceFiles/info/polls/info_polls_list_widget.cpp @@ -0,0 +1,708 @@ +/* +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 "info/polls/info_polls_list_widget.h" + +#include "data/data_poll_messages.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_forum_topic.h" +#include "data/data_peer_values.h" +#include "data/data_saved_sublist.h" +#include "history/history.h" +#include "history/history_item.h" +#include "history/view/history_view_list_widget.h" +#include "history/view/history_view_corner_buttons.h" +#include "history/view/history_view_element.h" +#include "history/view/reactions/history_view_reactions_button.h" +#include "info/info_controller.h" +#include "info/info_memento.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/chat/chat_style.h" +#include "ui/chat/chat_theme.h" +#include "ui/widgets/elastic_scroll.h" +#include "ui/widgets/scroll_area.h" +#include "ui/ui_utility.h" +#include "window/section_widget.h" +#include "window/themes/window_theme.h" +#include "window/window_session_controller.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" + +namespace Info::Polls { + +class ListWidget::Inner final + : private HistoryView::ListDelegate + , private HistoryView::CornerButtonsDelegate { +public: + Inner( + not_null parent, + not_null controller); + + [[nodiscard]] not_null scroll() const { + return _scroll.get(); + } + [[nodiscard]] HistoryView::ListWidget *list() const { + return _list; + } + + void updateGeometry(QRect rect); + + [[nodiscard]] int scrollTop() const; + void setScrollTop(int top); + + void paintBackground(QPainter &p, QRect clip); + +private: + void setupHistory(); + void updateInnerVisibleArea(); + + HistoryView::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( + HistoryView::SelectedItems &&items) override; + void listMarkReadTill(not_null item) override; + void listMarkContentsRead( + const base::flat_set> &items) override; + HistoryView::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 listSearch( + const QString &query, + const FullMsgId &context) override; + void listHandleViaClick(not_null bot) override; + not_null listChatTheme() override; + HistoryView::CopyRestrictionType listCopyRestrictionType( + HistoryItem *item) override; + HistoryView::CopyRestrictionType listCopyMediaRestrictionType( + not_null item) override; + HistoryView::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; + bool listElementHideTopicButton( + not_null view) override; + History *listTranslateHistory() override; + void listAddTranslatedItems( + not_null tracker) override; + not_null listWindow() override; + not_null listEmojiInteractionsParent() override; + not_null listChatStyle() override; + rpl::producer listChatWideValue() override; + std::unique_ptr + listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) override; + void listVisibleAreaUpdated() override; + std::shared_ptr listUiShow() override; + void listShowPollResults( + not_null poll, + FullMsgId context) override; + void listCancelUploadLayer(not_null item) override; + bool listAnimationsPaused() override; + auto listSendingAnimation() + -> Ui::MessageSendingAnimationController* override; + Ui::ChatPaintContext listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) override; + bool listMarkingContentRead() override; + bool listIgnorePaintEvent(QWidget *w, QPaintEvent *e) override; + bool listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) override; + base::unique_qptr listFillSenderUserpicMenu( + PeerId userpicPeerId) override; + void listWindowSetInnerFocus() override; + bool listAllowsDragForward() override; + void listLaunchDrag( + std::unique_ptr data, + Fn finished) override; + + 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(HistoryView::CornerButtonType type) override; + + const not_null _controller; + const not_null _session; + const not_null _history; + const MsgId _topicRootId = 0; + const PeerId _monoforumPeerId = 0; + const std::shared_ptr _theme; + const std::unique_ptr _chatStyle; + const std::unique_ptr _scroll; + + QPointer _list; + std::unique_ptr _cornerButtons; + bool _viewerRefreshed = false; + + QImage _bg; + +}; + +ListWidget::Inner::Inner( + not_null parent, + not_null controller) +: _controller(controller) +, _session(&controller->session()) +, _history(_session->data().history(controller->key().peer())) +, _topicRootId(controller->topic() + ? controller->topic()->rootId() + : MsgId()) +, _monoforumPeerId(controller->sublist() + ? controller->sublist()->sublistPeer()->id + : PeerId()) +, _theme(Window::Theme::DefaultChatThemeOn( + _controller->parentController()->lifetime())) +, _chatStyle( + std::make_unique(_session->colorIndicesValue())) +, _scroll(std::make_unique(parent)) { + _chatStyle->apply(_theme.get()); + setupHistory(); +} + +void ListWidget::Inner::setupHistory() { + _list = _scroll->setOwnedWidget( + object_ptr( + _scroll.get(), + _session, + static_cast(this))); + _cornerButtons = std::make_unique( + _scroll.get(), + _chatStyle.get(), + static_cast(this)); + + _scroll->scrolls( + ) | rpl::on_next([=] { + updateInnerVisibleArea(); + }, _scroll->lifetime()); + _scroll->setOverscrollBg(QColor(0, 0, 0, 0)); + using Type = Ui::ElasticScroll::OverscrollType; + _scroll->setOverscrollTypes(Type::Real, Type::Real); + + _list->scrollKeyEvents( + ) | rpl::on_next([=](not_null e) { + _scroll->keyPressEvent(e); + }, _scroll->lifetime()); +} + +void ListWidget::Inner::updateGeometry(QRect rect) { + _scroll->setGeometry(rect); + _cornerButtons->updatePositions(); + + if (rect.isEmpty()) { + return; + } + if (!_viewerRefreshed) { + _viewerRefreshed = true; + _list->resizeToWidth(_scroll->width(), _scroll->height()); + _list->refreshViewer(); + } + const auto ratio = style::DevicePixelRatio(); + _bg = QImage( + rect.size() * ratio, + QImage::Format_ARGB32_Premultiplied); + auto p = QPainter(&_bg); + Window::SectionWidget::PaintBackground( + p, + _theme.get(), + QSize(rect.width(), rect.height() * 2), + QRect(QPoint(), rect.size())); +} + +void ListWidget::Inner::paintBackground(QPainter &p, QRect clip) { + if (!_bg.isNull()) { + const auto scrollGeometry = _scroll->geometry(); + p.drawImage(scrollGeometry.topLeft(), _bg); + } +} + +void ListWidget::Inner::updateInnerVisibleArea() { + const auto scrollTop = _scroll->scrollTop(); + _list->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + _cornerButtons->updateJumpDownVisibility(); +} + +int ListWidget::Inner::scrollTop() const { + return _scroll->scrollTop(); +} + +void ListWidget::Inner::setScrollTop(int top) { + _scroll->scrollToY(top); +} + +HistoryView::Context ListWidget::Inner::listContext() { + return HistoryView::Context::ChatPreview; +} + +bool ListWidget::Inner::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 ListWidget::Inner::listCancelRequest() { +} + +void ListWidget::Inner::listDeleteRequest() { +} + +void ListWidget::Inner::listTryProcessKeyInput(not_null e) { +} + +rpl::producer ListWidget::Inner::listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) { + return Data::PollMessagesViewer( + _session, + _history, + _topicRootId, + _monoforumPeerId, + aroundId, + limitBefore, + limitAfter); +} + +bool ListWidget::Inner::listAllowsMultiSelect() { + return false; +} + +bool ListWidget::Inner::listIsItemGoodForSelection( + not_null item) { + return false; +} + +bool ListWidget::Inner::listIsLessInOrder( + not_null first, + not_null second) { + if (first->isRegular() && second->isRegular()) { + return first->id < second->id; + } else if (first->isRegular()) { + return true; + } else if (second->isRegular()) { + return false; + } + return first->id < second->id; +} + +void ListWidget::Inner::listSelectionChanged( + HistoryView::SelectedItems &&items) { +} + +void ListWidget::Inner::listMarkReadTill(not_null item) { +} + +void ListWidget::Inner::listMarkContentsRead( + const base::flat_set> &items) { +} + +HistoryView::MessagesBarData ListWidget::Inner::listMessagesBar( + const std::vector> &elements) { + return {}; +} + +void ListWidget::Inner::listContentRefreshed() { +} + +void ListWidget::Inner::listUpdateDateLink( + ClickHandlerPtr &link, + not_null view) { +} + +bool ListWidget::Inner::listElementHideReply( + not_null view) { + return false; +} + +bool ListWidget::Inner::listElementShownUnread( + not_null view) { + return false; +} + +bool ListWidget::Inner::listIsGoodForAroundPosition( + not_null view) { + return view->data()->isRegular(); +} + +void ListWidget::Inner::listSendBotCommand( + const QString &command, + const FullMsgId &context) { +} + +void ListWidget::Inner::listSearch( + const QString &query, + const FullMsgId &context) { +} + +void ListWidget::Inner::listHandleViaClick(not_null bot) { +} + +not_null ListWidget::Inner::listChatTheme() { + return _theme.get(); +} + +HistoryView::CopyRestrictionType +ListWidget::Inner::listCopyRestrictionType(HistoryItem *item) { + return HistoryView::CopyRestrictionType::None; +} + +HistoryView::CopyRestrictionType +ListWidget::Inner::listCopyMediaRestrictionType( + not_null item) { + return HistoryView::CopyRestrictionType::None; +} + +HistoryView::CopyRestrictionType +ListWidget::Inner::listSelectRestrictionType() { + return HistoryView::CopyRestrictionType::None; +} + +auto ListWidget::Inner::listAllowedReactionsValue() +-> rpl::producer { + return Data::PeerAllowedReactionsValue(_history->peer); +} + +void ListWidget::Inner::listShowPremiumToast( + not_null document) { +} + +void ListWidget::Inner::listOpenPhoto( + not_null photo, + FullMsgId context) { +} + +void ListWidget::Inner::listOpenDocument( + not_null document, + FullMsgId context, + bool showInMediaView) { +} + +void ListWidget::Inner::listPaintEmpty( + Painter &p, + const Ui::ChatPaintContext &context) { +} + +QString ListWidget::Inner::listElementAuthorRank( + not_null view) { + return {}; +} + +bool ListWidget::Inner::listElementHideTopicButton( + not_null view) { + return true; +} + +History *ListWidget::Inner::listTranslateHistory() { + return nullptr; +} + +void ListWidget::Inner::listAddTranslatedItems( + not_null tracker) { +} + +not_null ListWidget::Inner::listWindow() { + return _controller->parentController(); +} + +not_null ListWidget::Inner::listEmojiInteractionsParent() { + return _scroll.get(); +} + +not_null ListWidget::Inner::listChatStyle() { + return _chatStyle.get(); +} + +rpl::producer ListWidget::Inner::listChatWideValue() { + return rpl::single(false); +} + +std::unique_ptr +ListWidget::Inner::listMakeReactionsManager( + QWidget *wheelEventsTarget, + Fn update) { + return std::make_unique( + wheelEventsTarget, + std::move(update)); +} + +void ListWidget::Inner::listVisibleAreaUpdated() { +} + +std::shared_ptr ListWidget::Inner::listUiShow() { + return _controller->parentController()->uiShow(); +} + +void ListWidget::Inner::listShowPollResults( + not_null poll, + FullMsgId context) { + _controller->parentController()->showSection( + std::make_shared(poll, context)); +} + +void ListWidget::Inner::listCancelUploadLayer( + not_null item) { +} + +bool ListWidget::Inner::listAnimationsPaused() { + return false; +} + +auto ListWidget::Inner::listSendingAnimation() +-> Ui::MessageSendingAnimationController* { + return nullptr; +} + +Ui::ChatPaintContext ListWidget::Inner::listPreparePaintContext( + Ui::ChatPaintContextArgs &&args) { + const auto visibleAreaTopLocal = _scroll->mapFromGlobal( + args.visibleAreaPositionGlobal).y(); + const auto area = QRect( + 0, + args.visibleAreaTop, + args.visibleAreaWidth, + args.visibleAreaHeight); + const auto viewport = QRect( + 0, + args.visibleAreaTop - visibleAreaTopLocal, + args.visibleAreaWidth, + _scroll->height()); + return args.theme->preparePaintContext( + _chatStyle.get(), + viewport, + area, + args.clip, + false); +} + +bool ListWidget::Inner::listMarkingContentRead() { + return false; +} + +bool ListWidget::Inner::listIgnorePaintEvent(QWidget *w, QPaintEvent *e) { + return false; +} + +bool ListWidget::Inner::listShowReactPremiumError( + not_null item, + const Data::ReactionId &id) { + return Window::ShowReactPremiumError( + _controller->parentController(), + item, + id); +} + +base::unique_qptr +ListWidget::Inner::listFillSenderUserpicMenu(PeerId userpicPeerId) { + return nullptr; +} + +void ListWidget::Inner::listWindowSetInnerFocus() { +} + +bool ListWidget::Inner::listAllowsDragForward() { + return false; +} + +void ListWidget::Inner::listLaunchDrag( + std::unique_ptr data, + Fn finished) { +} + +void ListWidget::Inner::cornerButtonsShowAtPosition( + Data::MessagePosition position) { + if (position == Data::UnreadMessagePosition) { + position = Data::MaxMessagePosition; + } + _list->showAtPosition( + position, + {}, + _cornerButtons->doneJumpFrom(position.fullId, {}, true)); +} + +Data::Thread *ListWidget::Inner::cornerButtonsThread() { + return _history; +} + +FullMsgId ListWidget::Inner::cornerButtonsCurrentId() { + return {}; +} + +bool ListWidget::Inner::cornerButtonsIgnoreVisibility() { + return false; +} + +std::optional ListWidget::Inner::cornerButtonsDownShown() { + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax()) { + return true; + } else if (_list->loadedAtBottomKnown()) { + return !_list->loadedAtBottom(); + } + return std::nullopt; +} + +bool ListWidget::Inner::cornerButtonsUnreadMayBeShown() { + return false; +} + +bool ListWidget::Inner::cornerButtonsHas( + HistoryView::CornerButtonType type) { + return (type == HistoryView::CornerButtonType::Down); +} + +// --- ListMemento --- + +ListMemento::ListMemento( + not_null peer, + PeerId migratedPeerId) +: ContentMemento(peer, nullptr, nullptr, migratedPeerId) { +} + +ListMemento::ListMemento(not_null topic) +: ContentMemento(topic->peer(), topic, nullptr, PeerId()) { +} + +ListMemento::ListMemento(not_null sublist) +: ContentMemento( + sublist->owningHistory()->peer, + nullptr, + sublist, + PeerId()) { +} + +Section ListMemento::section() const { + return Section(Storage::SharedMediaType::Poll); +} + +object_ptr ListMemento::createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) { + auto result = object_ptr(parent, controller); + result->setInternalState(geometry, this); + return result; +} + +// --- ListWidget --- + +ListWidget::ListWidget( + QWidget *parent, + not_null controller) +: ContentWidget(parent, controller) +, _inner(std::make_unique(this, controller)) { + setInnerWidget(object_ptr(this)); + scroll()->hide(); +} + +ListWidget::~ListWidget() = default; + +rpl::producer ListWidget::title() { + return tr::lng_media_type_polls(); +} + +rpl::producer ListWidget::desiredHeightValue() const { + return sizeValue( + ) | rpl::map([=](QSize) { + return maxVisibleHeight(); + }) | rpl::distinct_until_changed(); +} + +bool ListWidget::showInternal(not_null memento) { + if (!controller()->validateMementoPeer(memento)) { + return false; + } + if (auto my = dynamic_cast(memento.get())) { + restoreState(my); + return true; + } + return false; +} + +void ListWidget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +std::shared_ptr ListWidget::doCreateMemento() { + auto result = std::make_shared( + controller()->key().peer(), + controller()->migratedPeerId()); + saveState(result.get()); + return result; +} + +void ListWidget::saveState(not_null memento) { + memento->setScrollTop(_inner->scrollTop()); +} + +void ListWidget::restoreState(not_null memento) { + _inner->setScrollTop(memento->scrollTop()); +} + +void ListWidget::resizeEvent(QResizeEvent *e) { + ContentWidget::resizeEvent(e); + _inner->updateGeometry(QRect(0, 0, width(), height())); +} + +void ListWidget::paintEvent(QPaintEvent *e) { + ContentWidget::paintEvent(e); + auto p = QPainter(this); + _inner->paintBackground(p, e->rect()); +} + +} // namespace Info::Polls diff --git a/Telegram/SourceFiles/info/polls/info_polls_list_widget.h b/Telegram/SourceFiles/info/polls/info_polls_list_widget.h new file mode 100644 index 0000000000..61b1446bac --- /dev/null +++ b/Telegram/SourceFiles/info/polls/info_polls_list_widget.h @@ -0,0 +1,88 @@ +/* +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 "info/info_content_widget.h" +#include "storage/storage_shared_media.h" + +namespace Ui { +class ElasticScroll; +class ChatStyle; +class ChatTheme; +} // namespace Ui + +namespace HistoryView { +class ListWidget; +} // namespace HistoryView + +namespace Data { +class ForumTopic; +class SavedSublist; +} // namespace Data + +namespace Info::Polls { + +class ListMemento final : public ContentMemento { +public: + ListMemento( + not_null peer, + PeerId migratedPeerId); + ListMemento(not_null topic); + ListMemento(not_null sublist); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + const QRect &geometry) override; + + [[nodiscard]] Section section() const override; + + void setScrollTop(int scrollTop) { + _scrollTop = scrollTop; + } + [[nodiscard]] int scrollTop() const { + return _scrollTop; + } + +private: + int _scrollTop = 0; + +}; + +class ListWidget final : public ContentWidget { +public: + ListWidget( + QWidget *parent, + not_null controller); + ~ListWidget(); + + bool showInternal( + not_null memento) override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + rpl::producer title() override; + rpl::producer desiredHeightValue() const override; + +private: + void saveState(not_null memento); + void restoreState(not_null memento); + + std::shared_ptr doCreateMemento() override; + + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + class Inner; + const std::unique_ptr _inner; + +}; + +} // namespace Info::Polls diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index 1cf9298335..6929a246e9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -353,6 +353,7 @@ object_ptr InnerWidget::setupSharedMedia( addMediaButton(MediaType::File, st::infoIconMediaFile); addMediaButton(MediaType::MusicFile, st::infoIconMediaAudio); addMediaButton(MediaType::Link, st::infoIconMediaLink); + addMediaButton(MediaType::Poll, st::infoIconMediaPoll); addMediaButton(MediaType::RoundVoiceFile, st::infoIconMediaVoice); addMediaButton(MediaType::GIF, st::infoIconMediaGif); if (const auto bot = peer->asBot()) { diff --git a/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp index 9fb177964d..46038bf060 100644 --- a/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp +++ b/Telegram/SourceFiles/info/saved/info_saved_sublists_widget.cpp @@ -128,6 +128,7 @@ void SublistsWidget::setupOtherTypes() { addMediaButton(Type::File, st::infoIconMediaFile); addMediaButton(Type::MusicFile, st::infoIconMediaAudio); addMediaButton(Type::Link, st::infoIconMediaLink); + addMediaButton(Type::Poll, st::infoIconMediaPoll); addMediaButton(Type::RoundVoiceFile, st::infoIconMediaVoice); addMediaButton(Type::GIF, st::infoIconMediaGif); diff --git a/Telegram/SourceFiles/storage/storage_shared_media.h b/Telegram/SourceFiles/storage/storage_shared_media.h index 09f5cce406..c04ccf96a1 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.h +++ b/Telegram/SourceFiles/storage/storage_shared_media.h @@ -27,6 +27,7 @@ enum class SharedMediaType : signed char { GIF, RoundFile, Pinned, + Poll, kCount, };