From 3e0b2f5553455adfe6020ba566fc8afe81b978ad Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 Jan 2020 14:55:18 +0300 Subject: [PATCH] Add PollResultsBox. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 5 + Telegram/Resources/tl/api.tl | 12 +- Telegram/SourceFiles/boxes/boxes.style | 14 + .../SourceFiles/boxes/poll_results_box.cpp | 322 ++++++++++++++++++ Telegram/SourceFiles/boxes/poll_results_box.h | 23 ++ Telegram/SourceFiles/data/data_poll.cpp | 5 +- .../admin_log/history_admin_log_inner.cpp | 5 + .../admin_log/history_admin_log_inner.h | 3 + .../history/history_inner_widget.cpp | 16 + .../history/history_inner_widget.h | 3 + .../history/view/history_view_element.cpp | 5 + .../history/view/history_view_element.h | 6 + .../history/view/history_view_list_widget.cpp | 5 + .../history/view/history_view_list_widget.h | 3 + .../history/view/media/history_view_poll.cpp | 8 +- 16 files changed, 427 insertions(+), 10 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/poll_results_box.cpp create mode 100644 Telegram/SourceFiles/boxes/poll_results_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8971007827..a6ce204ffa 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -188,6 +188,8 @@ PRIVATE boxes/passcode_box.h boxes/photo_crop_box.cpp boxes/photo_crop_box.h + boxes/poll_results_box.cpp + boxes/poll_results_box.h boxes/rate_call_box.cpp boxes/rate_call_box.h boxes/report_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e7251931ff..29f070cfa9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2200,6 +2200,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_create_quiz_mode" = "Quiz Mode"; "lng_polls_create_button" = "Create"; +"lng_polls_poll_results_title" = "Poll results"; +"lng_polls_quiz_results_title" = "Quiz results"; +"lng_polls_show_more#one" = "Show {count} more voter"; +"lng_polls_show_more#other" = "Show {count} more voters"; + "lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM."; "lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}."; "lng_outdated_now" = "So that Telegram Desktop can update to newer versions."; diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index cd71877ea9..d4e1991d0d 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -351,6 +351,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector = Update; updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; +updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1082,10 +1083,6 @@ theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_h account.themesNotModified#f41eb622 = account.Themes; account.themes#7f676421 hash:int themes:Vector = account.Themes; -wallet.liteResponse#764386d7 response:bytes = wallet.LiteResponse; - -wallet.secretSalt#dd484d64 salt:bytes = wallet.KeySecretSalt; - auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken; auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken; auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken; @@ -1106,7 +1103,9 @@ themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector settings:flags.1?ThemeSettings = WebPageAttribute; -messageUserVote#f212f56d user_id:int option:bytes = MessageUserVote; +messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote; +messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote; +messageUserVoteMultiple#e8fe0de user_id:int options:Vector date:int = MessageUserVote; messages.votesList#823f649 flags:# count:int votes:Vector users:Vector next_offset:flags.0?string = messages.VotesList; @@ -1455,7 +1454,4 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -wallet.sendLiteRequest#e2c9d33e body:bytes = wallet.LiteResponse; -wallet.getKeySecretSalt#b57f346 revoke:Bool = wallet.KeySecretSalt; - // LAYER 109 diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index a49e54f57b..43a2fa2c01 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -923,3 +923,17 @@ customBadgeField: InputField(defaultInputField) { heightMin: 32px; } + +pollResultsQuestion: FlatLabel(defaultFlatLabel) { + minWidth: 320px; + textFg: windowBoldFg; + style: TextStyle(defaultTextStyle) { + font: font(16px semibold); + linkFont: font(16px semibold); + linkFontOver: font(16px semibold underline); + } +} +pollResultsVotesCount: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} +pollResultsHeaderPadding: margins(22px, 8px, 22px, 8px); diff --git a/Telegram/SourceFiles/boxes/poll_results_box.cpp b/Telegram/SourceFiles/boxes/poll_results_box.cpp new file mode 100644 index 0000000000..5e326d6041 --- /dev/null +++ b/Telegram/SourceFiles/boxes/poll_results_box.cpp @@ -0,0 +1,322 @@ +/* +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 "boxes/poll_results_box.h" + +#include "lang/lang_keys.h" +#include "data/data_poll.h" +#include "data/data_peer.h" +#include "data/data_user.h" +#include "data/data_session.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/text/text_utilities.h" +#include "boxes/peer_list_box.h" +#include "window/window_session_controller.h" +#include "main/main_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "apiwrap.h" +#include "styles/style_layers.h" +#include "styles/style_boxes.h" +#include "styles/style_info.h" + +namespace { + +constexpr auto kFirstPage = 10; +constexpr auto kPerPage = 100; + +class Delegate final : public PeerListContentDelegate { +public: + void peerListSetTitle(rpl::producer title) override; + void peerListSetAdditionalTitle(rpl::producer title) override; + bool peerListIsRowSelected(not_null peer) override; + int peerListSelectedRowsCount() override; + std::vector> peerListCollectSelectedRows() override; + void peerListScrollToTop() override; + void peerListAddSelectedRowInBunch( + not_null peer) override; + void peerListFinishSelectedRowsBunch() override; + void peerListSetDescription( + object_ptr description) override; + +}; + +class Controller final : public PeerListController { +public: + Controller( + not_null window, + not_null poll, + FullMsgId context, + QByteArray option); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + void loadMoreRows() override; + + void allowLoadAll(); + +private: + bool appendRow(not_null user); + std::unique_ptr createRow(not_null user) const; + + const not_null _window; + const not_null _poll; + const FullMsgId _context; + const QByteArray _option; + + MTP::Sender _api; + + QString _offset; + mtpRequestId _loadRequestId = 0; + int _fullCount = 0; + bool _allLoaded = false; + bool _loadingAll = false; + +}; + +void Delegate::peerListSetTitle(rpl::producer title) { +} + +void Delegate::peerListSetAdditionalTitle(rpl::producer title) { +} + +bool Delegate::peerListIsRowSelected(not_null peer) { + return false; +} + +int Delegate::peerListSelectedRowsCount() { + return 0; +} + +std::vector> Delegate::peerListCollectSelectedRows() { + return {}; +} + +void Delegate::peerListScrollToTop() { +} + +void Delegate::peerListAddSelectedRowInBunch(not_null peer) { + Unexpected("Item selection in Info::Profile::Members."); +} + +void Delegate::peerListFinishSelectedRowsBunch() { +} + +void Delegate::peerListSetDescription( + object_ptr description) { + description.destroy(); +} + +Controller::Controller( + not_null window, + not_null poll, + FullMsgId context, + QByteArray option) +: _window(window) +, _poll(poll) +, _context(context) +, _option(option) +, _api(_window->session().api().instance()) { +} + +Main::Session &Controller::session() const { + return _window->session(); +} + +void Controller::prepare() { + delegate()->peerListRefreshRows(); +} + +void Controller::loadMoreRows() { + if (_loadRequestId + || _allLoaded + || (!_loadingAll && !_offset.isEmpty())) { + return; + } + const auto item = session().data().message(_context); + if (!item || !IsServerMsgId(item->id)) { + _allLoaded = true; + return; + } + + using Flag = MTPmessages_GetPollVotes::Flag; + const auto flags = Flag::f_option + | (_offset.isEmpty() ? Flag(0) : Flag::f_offset); + _loadRequestId = _api.request(MTPmessages_GetPollVotes( + MTP_flags(flags), + item->history()->peer->input, + MTP_int(item->id), + MTP_bytes(_option), + MTP_string(_offset), + MTP_int(_offset.isEmpty() ? kFirstPage : kPerPage) + )).done([=](const MTPmessages_VotesList &result) { + _loadRequestId = 0; + result.match([&](const MTPDmessages_votesList &data) { + _fullCount = data.vcount().v; + _offset = data.vnext_offset().value_or_empty(); + auto &owner = session().data(); + owner.processUsers(data.vusers()); + for (const auto &vote : data.vvotes().v) { + vote.match([&](const auto &data) { + const auto user = owner.user(data.vuser_id().v); + if (user->loadedStatus != PeerData::NotLoaded) { + appendRow(user); + } + }); + } + }); + _allLoaded = _offset.isEmpty(); + delegate()->peerListRefreshRows(); + }).fail([=](const RPCError &error) { + _loadRequestId = 0; + }).send(); +} + +void Controller::allowLoadAll() { + _loadingAll = true; + loadMoreRows(); +} + +void Controller::rowClicked(not_null row) { + _window->showPeerHistory(row->peer(), Window::SectionShow::Way::Forward); +} + +bool Controller::appendRow(not_null user) { + if (delegate()->peerListFindRow(user->id)) { + return false; + } + delegate()->peerListAppendRow(createRow(user)); + return true; +} + +std::unique_ptr Controller::createRow( + not_null user) const { + auto row = std::make_unique(user); + row->setCustomStatus(QString()); + return row; +} + +void CreateAnswerRows( + not_null box, + not_null window, + not_null poll, + FullMsgId context, + const PollAnswer &answer) { + if (!answer.votes) { + return; + } + + const auto percent = answer.votes * 100 / poll->totalVoters; + const auto rightText = (poll->quiz() + ? tr::lng_polls_answers_count + : tr::lng_polls_votes_count)( + tr::now, + lt_count_decimal, + answer.votes); + const auto &font = st::boxDividerLabel.style.font; + const auto rightWidth = font->width(rightText); + const auto rightSkip = rightWidth + font->spacew * 4; + const auto header = box->addRow( + object_ptr( + box, + object_ptr( + box, + (answer.text.repeated(20) + + QString::fromUtf8(" \xe2\x80\x94 ") + + QString::number(percent) + + "%"), + st::boxDividerLabel), + style::margins( + st::pollResultsHeaderPadding.left(), + st::pollResultsHeaderPadding.top(), + st::pollResultsHeaderPadding.right() + rightSkip, + st::pollResultsHeaderPadding.bottom())), + style::margins()); + const auto votes = Ui::CreateChild( + header, + rightText, + st::pollResultsVotesCount); + header->widthValue( + ) | rpl::start_with_next([=](int width) { + votes->moveToRight( + st::pollResultsHeaderPadding.right(), + st::pollResultsHeaderPadding.top(), + width); + }, votes->lifetime()); + box->addRow(object_ptr(box, st::boxLittleSkip)); + + const auto delegate = box->lifetime().make_state(); + const auto controller = box->lifetime().make_state( + window, + poll, + context, + answer.option); + const auto content = box->addRow( + object_ptr( + box, + controller, + st::infoCommonGroupsList), + style::margins()); + delegate->setContent(content); + controller->setDelegate(delegate); + + const auto more = box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_polls_show_more( + lt_count_decimal, + rpl::single(answer.votes + 0.), + Ui::Text::Upper), + st::infoMainButton)), + style::margins()); + more->toggle(answer.votes > kFirstPage, anim::type::instant); + more->entity()->setClickedCallback([=] { + controller->allowLoadAll(); + more->hide(anim::type::instant); + }); + + box->addRow(object_ptr(box, st::boxLittleSkip)); +} + +} // namespace + +void PollResultsBox( + not_null box, + not_null window, + not_null poll, + FullMsgId context) { + const auto quiz = poll->quiz(); + box->setWidth(st::boxWideWidth); + box->setTitle(quiz + ? tr::lng_polls_quiz_results_title() + : tr::lng_polls_poll_results_title()); + box->setAdditionalTitle((quiz + ? tr::lng_polls_answers_count + : tr::lng_polls_votes_count)( + lt_count_decimal, + rpl::single(poll->totalVoters * 1.))); + box->addRow( + object_ptr( + box, + poll->question, + st::pollResultsQuestion), + style::margins{ + st::boxRowPadding.left(), + 0, + st::boxRowPadding.right(), + st::boxMediumSkip }); + for (const auto &answer : poll->answers) { + CreateAnswerRows(box, window, poll, context, answer); + } + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); +} diff --git a/Telegram/SourceFiles/boxes/poll_results_box.h b/Telegram/SourceFiles/boxes/poll_results_box.h new file mode 100644 index 0000000000..7128258ca2 --- /dev/null +++ b/Telegram/SourceFiles/boxes/poll_results_box.h @@ -0,0 +1,23 @@ +/* +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 "ui/layers/generic_box.h" + +class PollData; +class HistoryItem; + +namespace Window { +class SessionController; +} // namespace Window + +void PollResultsBox( + not_null box, + not_null window, + not_null poll, + FullMsgId context); diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index 167a94c837..f16a118409 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -112,7 +112,10 @@ bool PollData::applyResults(const MTPPollResults &results) { recentVoters = ranges::view::all( recent->v ) | ranges::view::transform([&](MTPint userId) { - return _owner->userLoaded(userId.v); + const auto user = _owner->user(userId.v); + return (user->loadedStatus != PeerData::NotLoaded) + ? user.get() + : nullptr; }) | ranges::view::filter([](UserData *user) { return user != nullptr; }) | ranges::view::transform([](UserData *user) { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index ede2c77555..7e84e17481 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -577,6 +577,11 @@ bool InnerWidget::elementIntersectsRange( void InnerWidget::elementStartStickerLoop(not_null view) { } +void InnerWidget::elementShowPollResults( + not_null poll, + FullMsgId context) { +} + void InnerWidget::saveState(not_null memento) { memento->setFilter(std::move(_filter)); memento->setAdmins(std::move(_admins)); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index c9405ea052..01bacd75d7 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -101,6 +101,9 @@ public: int till) override; void elementStartStickerLoop( not_null view) override; + void elementShowPollResults( + not_null poll, + FullMsgId context) override; ~InnerWidget(); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 281696be9f..60dc565876 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -30,9 +30,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/inactive_press.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" +#include "window/window_controller.h" #include "boxes/confirm_box.h" #include "boxes/report_box.h" #include "boxes/sticker_set_box.h" +#include "boxes/poll_results_box.h" #include "chat_helpers/message_field.h" #include "chat_helpers/stickers.h" #include "history/history_widget.h" @@ -2418,6 +2420,13 @@ void HistoryInner::elementStartStickerLoop( _animatedStickersPlayed.emplace(view->data()); } +void HistoryInner::elementShowPollResults( + not_null poll, + FullMsgId context) { + _controller->window().show( + Box(PollResultsBox, _controller, poll, context)); +} + auto HistoryInner::getSelectionState() const -> HistoryView::TopBarWidget::SelectedState { auto result = HistoryView::TopBarWidget::SelectedState {}; @@ -3278,6 +3287,13 @@ not_null HistoryInner::ElementDelegate() { Instance->elementStartStickerLoop(view); } } + void elementShowPollResults( + not_null poll, + FullMsgId context) override { + if (Instance) { + Instance->elementShowPollResults(poll, context); + } + } }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index b26d5e7de4..4a4773bcf9 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -81,6 +81,9 @@ public: int from, int till) const; void elementStartStickerLoop(not_null view); + void elementShowPollResults( + not_null poll, + FullMsgId context); void updateBotInfo(bool recount = true); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 3c42a9768d..f1e5fda69f 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -94,6 +94,11 @@ void SimpleElementDelegate::elementStartStickerLoop( not_null view) { } +void SimpleElementDelegate::elementShowPollResults( + not_null poll, + FullMsgId context) { +} + TextSelection UnshiftItemSelection( TextSelection selection, uint16 byLength) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 6d58d56045..c538137103 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -51,6 +51,9 @@ public: int from, int till) = 0; virtual void elementStartStickerLoop(not_null view) = 0; + virtual void elementShowPollResults( + not_null poll, + FullMsgId context) = 0; }; @@ -71,6 +74,9 @@ public: int from, int till) override; void elementStartStickerLoop(not_null view) override; + void elementShowPollResults( + not_null poll, + FullMsgId context) override; }; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index c93461e178..dec0bf1246 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1153,6 +1153,11 @@ bool ListWidget::elementIntersectsRange( void ListWidget::elementStartStickerLoop(not_null view) { } +void ListWidget::elementShowPollResults( + not_null poll, + FullMsgId context) { +} + void ListWidget::saveState(not_null memento) { memento->setAroundPosition(_aroundPosition); auto state = countScrollState(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index d801a24f91..b821cb127b 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -197,6 +197,9 @@ public: int from, int till) override; void elementStartStickerLoop(not_null view) override; + void elementShowPollResults( + not_null poll, + FullMsgId context) override; ~ListWidget(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index b298236c7f..6547ab95a5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/effects/radial_animation.h" #include "ui/effects/ripple_animation.h" +#include "boxes/poll_results_box.h" #include "data/data_media_types.h" #include "data/data_poll.h" #include "data/data_user.h" @@ -255,6 +256,9 @@ bool Poll::canSendVotes() const { } bool Poll::showVotersCount() const { + if (!_totalVotes) { + return true; + } return showVotes() ? !(_flags & PollData::Flag::PublicVotes) : !(_flags & PollData::Flag::MultiChoice); @@ -475,7 +479,9 @@ void Poll::sendMultiOptions() { } void Poll::showResults() { - // #TODO polls + _parent->delegate()->elementShowPollResults( + _poll, + _parent->data()->fullId()); } void Poll::updateVotes() {