init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
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_results_inner_widget.h"
|
||||
|
||||
#include "info/polls/info_polls_results_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_session.h"
|
||||
#include "ui/controls/peer_list_dummy.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::Polls {
|
||||
namespace {
|
||||
|
||||
constexpr auto kFirstPage = 15;
|
||||
constexpr auto kPerPage = 50;
|
||||
constexpr auto kLeavePreloaded = 5;
|
||||
|
||||
class ListDelegate final : public PeerListContentDelegate {
|
||||
public:
|
||||
void peerListSetTitle(rpl::producer<QString> title) override;
|
||||
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
|
||||
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
|
||||
int peerListSelectedRowsCount() override;
|
||||
void peerListScrollToTop() override;
|
||||
void peerListAddSelectedPeerInBunch(
|
||||
not_null<PeerData*> peer) override;
|
||||
void peerListAddSelectedRowInBunch(
|
||||
not_null<PeerListRow*> row) override;
|
||||
void peerListFinishSelectedRowsBunch() override;
|
||||
void peerListSetDescription(
|
||||
object_ptr<Ui::FlatLabel> description) override;
|
||||
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
|
||||
|
||||
};
|
||||
|
||||
void ListDelegate::peerListSetTitle(rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
void ListDelegate::peerListSetAdditionalTitle(rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
bool ListDelegate::peerListIsRowChecked(not_null<PeerListRow*> row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ListDelegate::peerListSelectedRowsCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ListDelegate::peerListScrollToTop() {
|
||||
}
|
||||
|
||||
void ListDelegate::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
|
||||
Unexpected("Item selection in Info::Profile::Members.");
|
||||
}
|
||||
|
||||
void ListDelegate::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
|
||||
Unexpected("Item selection in Info::Profile::Members.");
|
||||
}
|
||||
|
||||
void ListDelegate::peerListFinishSelectedRowsBunch() {
|
||||
}
|
||||
|
||||
void ListDelegate::peerListSetDescription(
|
||||
object_ptr<Ui::FlatLabel> description) {
|
||||
description.destroy();
|
||||
}
|
||||
|
||||
std::shared_ptr<Main::SessionShow> ListDelegate::peerListUiShow() {
|
||||
Unexpected("...ListDelegate::peerListUiShow");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ListController final : public PeerListController {
|
||||
public:
|
||||
ListController(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QByteArray option);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
void allowLoadMore();
|
||||
void collapse();
|
||||
|
||||
[[nodiscard]] auto showPeerInfoRequests() const
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
[[nodiscard]] rpl::producer<int> scrollToRequests() const;
|
||||
[[nodiscard]] rpl::producer<int> count() const;
|
||||
[[nodiscard]] rpl::producer<int> fullCount() const;
|
||||
[[nodiscard]] rpl::producer<int> loadMoreCount() const;
|
||||
|
||||
std::unique_ptr<PeerListState> saveState() const override;
|
||||
void restoreState(std::unique_ptr<PeerListState> state) override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createRestoredRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
|
||||
void scrollTo(int y);
|
||||
|
||||
private:
|
||||
struct SavedState : SavedStateBase {
|
||||
QString offset;
|
||||
QString loadForOffset;
|
||||
int leftToLoad = 0;
|
||||
int fullCount = 0;
|
||||
std::vector<not_null<PeerData*>> preloaded;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
|
||||
bool appendRow(not_null<PeerData*> peer);
|
||||
std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer) const;
|
||||
void addPreloaded();
|
||||
bool addPreloadedPage();
|
||||
void preloadedAdded();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
const not_null<PollData*> _poll;
|
||||
const FullMsgId _context;
|
||||
const QByteArray _option;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
QString _offset;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
QString _loadForOffset;
|
||||
std::vector<not_null<PeerData*>> _preloaded;
|
||||
rpl::variable<int> _count = 0;
|
||||
rpl::variable<int> _fullCount;
|
||||
rpl::variable<int> _leftToLoad;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _showPeerInfoRequests;
|
||||
rpl::event_stream<int> _scrollToRequests;
|
||||
|
||||
};
|
||||
|
||||
ListController::ListController(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QByteArray option)
|
||||
: _session(session)
|
||||
, _poll(poll)
|
||||
, _context(context)
|
||||
, _option(option)
|
||||
, _api(&_session->mtp()) {
|
||||
const auto i = ranges::find(poll->answers, option, &PollAnswer::option);
|
||||
Assert(i != poll->answers.end());
|
||||
_fullCount = i->votes;
|
||||
_leftToLoad = i->votes;
|
||||
}
|
||||
|
||||
Main::Session &ListController::session() const {
|
||||
return *_session;
|
||||
}
|
||||
|
||||
void ListController::prepare() {
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void ListController::loadMoreRows() {
|
||||
if (_loadRequestId
|
||||
|| !_leftToLoad.current()
|
||||
|| (!_offset.isEmpty() && _loadForOffset != _offset)
|
||||
|| !_preloaded.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto item = session().data().message(_context);
|
||||
if (!item || !item->isRegular()) {
|
||||
_leftToLoad = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_GetPollVotes::Flag;
|
||||
const auto flags = Flag::f_option
|
||||
| (_offset.isEmpty() ? Flag(0) : Flag::f_offset);
|
||||
const auto limit = _offset.isEmpty() ? kFirstPage : kPerPage;
|
||||
_loadRequestId = _api.request(MTPmessages_GetPollVotes(
|
||||
MTP_flags(flags),
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id),
|
||||
MTP_bytes(_option),
|
||||
MTP_string(_offset),
|
||||
MTP_int(limit)
|
||||
)).done([=](const MTPmessages_VotesList &result) {
|
||||
const auto count = result.match([&](
|
||||
const MTPDmessages_votesList &data) {
|
||||
_offset = data.vnext_offset().value_or_empty();
|
||||
auto &owner = session().data();
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
auto add = limit - kLeavePreloaded;
|
||||
for (const auto &vote : data.vvotes().v) {
|
||||
vote.match([&](const auto &data) {
|
||||
const auto peer = owner.peer(peerFromMTP(data.vpeer()));
|
||||
if (peer->isMinimalLoaded()) {
|
||||
if (add) {
|
||||
appendRow(peer);
|
||||
--add;
|
||||
} else {
|
||||
_preloaded.push_back(peer);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return data.vcount().v;
|
||||
});
|
||||
if (_offset.isEmpty()) {
|
||||
addPreloaded();
|
||||
_fullCount = delegate()->peerListFullRowsCount();
|
||||
_leftToLoad = 0;
|
||||
} else {
|
||||
_count = delegate()->peerListFullRowsCount();
|
||||
_fullCount = count;
|
||||
_leftToLoad = count - delegate()->peerListFullRowsCount();
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
_loadRequestId = 0;
|
||||
}).fail([=] {
|
||||
_loadRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ListController::allowLoadMore() {
|
||||
if (!addPreloadedPage()) {
|
||||
_loadForOffset = _offset;
|
||||
addPreloaded();
|
||||
loadMoreRows();
|
||||
}
|
||||
}
|
||||
|
||||
void ListController::collapse() {
|
||||
const auto count = delegate()->peerListFullRowsCount();
|
||||
if (count <= kFirstPage) {
|
||||
return;
|
||||
}
|
||||
const auto remove = count - (kFirstPage - kLeavePreloaded);
|
||||
ranges::actions::reverse(_preloaded);
|
||||
_preloaded.reserve(_preloaded.size() + remove);
|
||||
for (auto i = 0; i != remove; ++i) {
|
||||
const auto row = delegate()->peerListRowAt(count - i - 1);
|
||||
_preloaded.push_back(row->peer());
|
||||
delegate()->peerListRemoveRow(row);
|
||||
}
|
||||
ranges::actions::reverse(_preloaded);
|
||||
|
||||
delegate()->peerListRefreshRows();
|
||||
const auto now = count - remove;
|
||||
_count = now;
|
||||
_leftToLoad = _fullCount.current() - now;
|
||||
}
|
||||
|
||||
void ListController::addPreloaded() {
|
||||
for (const auto peer : base::take(_preloaded)) {
|
||||
appendRow(peer);
|
||||
}
|
||||
preloadedAdded();
|
||||
}
|
||||
|
||||
bool ListController::addPreloadedPage() {
|
||||
if (_preloaded.size() < kPerPage + kLeavePreloaded) {
|
||||
return false;
|
||||
}
|
||||
const auto from = begin(_preloaded);
|
||||
const auto till = from + kPerPage;
|
||||
for (auto i = from; i != till; ++i) {
|
||||
appendRow(*i);
|
||||
}
|
||||
_preloaded.erase(from, till);
|
||||
preloadedAdded();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ListController::preloadedAdded() {
|
||||
_count = delegate()->peerListFullRowsCount();
|
||||
_leftToLoad = _fullCount.current() - _count.current();
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
auto ListController::showPeerInfoRequests() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _showPeerInfoRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<int> ListController::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<int> ListController::count() const {
|
||||
return _count.value();
|
||||
}
|
||||
|
||||
rpl::producer<int> ListController::fullCount() const {
|
||||
return _fullCount.value();
|
||||
}
|
||||
|
||||
rpl::producer<int> ListController::loadMoreCount() const {
|
||||
const auto initial = (_fullCount.current() <= kFirstPage)
|
||||
? _fullCount.current()
|
||||
: (kFirstPage - kLeavePreloaded);
|
||||
return rpl::combine(
|
||||
_count.value(),
|
||||
_leftToLoad.value()
|
||||
) | rpl::map([=](int count, int leftToLoad) {
|
||||
return (count > 0) ? leftToLoad : (leftToLoad - initial);
|
||||
});
|
||||
}
|
||||
|
||||
auto ListController::saveState() const -> std::unique_ptr<PeerListState> {
|
||||
auto result = PeerListController::saveState();
|
||||
|
||||
auto my = std::make_unique<SavedState>();
|
||||
my->offset = _offset;
|
||||
my->fullCount = _fullCount.current();
|
||||
my->leftToLoad = _leftToLoad.current();
|
||||
my->preloaded = _preloaded;
|
||||
my->wasLoading = (_loadRequestId != 0);
|
||||
my->loadForOffset = _loadForOffset;
|
||||
result->controllerState = std::move(my);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ListController::restoreState(std::unique_ptr<PeerListState> state) {
|
||||
auto typeErasedState = state
|
||||
? state->controllerState.get()
|
||||
: nullptr;
|
||||
if (const auto my = dynamic_cast<SavedState*>(typeErasedState)) {
|
||||
if (const auto requestId = base::take(_loadRequestId)) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
|
||||
_offset = my->offset;
|
||||
_loadForOffset = my->loadForOffset;
|
||||
_preloaded = std::move(my->preloaded);
|
||||
_count = int(state->list.size());
|
||||
_fullCount = my->fullCount;
|
||||
_leftToLoad = my->leftToLoad;
|
||||
if (my->wasLoading) {
|
||||
loadMoreRows();
|
||||
}
|
||||
PeerListController::restoreState(std::move(state));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ListController::createRestoredRow(
|
||||
not_null<PeerData*> peer) {
|
||||
return createRow(peer);
|
||||
}
|
||||
|
||||
void ListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_showPeerInfoRequests.fire(row->peer());
|
||||
}
|
||||
|
||||
bool ListController::appendRow(not_null<PeerData*> peer) {
|
||||
if (delegate()->peerListFindRow(peer->id.value)) {
|
||||
return false;
|
||||
}
|
||||
delegate()->peerListAppendRow(createRow(peer));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> ListController::createRow(
|
||||
not_null<PeerData*> peer) const {
|
||||
auto row = std::make_unique<PeerListRow>(peer);
|
||||
row->setCustomStatus(QString());
|
||||
return row;
|
||||
}
|
||||
|
||||
void ListController::scrollTo(int y) {
|
||||
_scrollToRequests.fire_copy(y);
|
||||
}
|
||||
|
||||
ListController *CreateAnswerRows(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
rpl::producer<int> visibleTop,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
const PollAnswer &answer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
if (!answer.votes) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto delegate = container->lifetime().make_state<ListDelegate>();
|
||||
const auto controller = container->lifetime().make_state<ListController>(
|
||||
session,
|
||||
poll,
|
||||
context,
|
||||
answer.option);
|
||||
|
||||
const auto percent = answer.votes * 100 / poll->totalVoters;
|
||||
const auto phrase = poll->quiz()
|
||||
? tr::lng_polls_answers_count
|
||||
: tr::lng_polls_votes_count;
|
||||
const auto sampleText = phrase(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
answer.votes);
|
||||
const auto &font = st::boxDividerLabel.style.font;
|
||||
const auto sampleWidth = font->width(sampleText);
|
||||
const auto rightSkip = sampleWidth + font->spacew * 4;
|
||||
const auto headerWrap = container->add(
|
||||
object_ptr<Ui::RpWidget>(
|
||||
container));
|
||||
|
||||
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
container,
|
||||
st::boxLittleSkip));
|
||||
|
||||
controller->setStyleOverrides(&st::infoCommonGroupsList);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
delegate->setContent(content);
|
||||
controller->setDelegate(delegate);
|
||||
|
||||
const auto count = (answer.votes <= kFirstPage)
|
||||
? answer.votes
|
||||
: (kFirstPage - kLeavePreloaded);
|
||||
const auto placeholder = container->add(object_ptr<PeerListDummy>(
|
||||
container,
|
||||
count,
|
||||
st::infoCommonGroupsList));
|
||||
|
||||
controller->count(
|
||||
) | rpl::filter(_1 > 0) | rpl::on_next([=] {
|
||||
delete placeholder;
|
||||
}, placeholder->lifetime());
|
||||
|
||||
const auto header = Ui::CreateChild<Ui::DividerLabel>(
|
||||
container.get(),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
rpl::single(
|
||||
TextWithEntities(answer.text)
|
||||
.append(QString::fromUtf8(" \xe2\x80\x94 "))
|
||||
.append(QString::number(percent))
|
||||
.append('%')),
|
||||
st::boxDividerLabel,
|
||||
st::defaultPopupMenu,
|
||||
Core::TextContext({ .session = session })),
|
||||
style::margins(
|
||||
st::pollResultsHeaderPadding.left(),
|
||||
st::pollResultsHeaderPadding.top(),
|
||||
st::pollResultsHeaderPadding.right() + rightSkip,
|
||||
st::pollResultsHeaderPadding.bottom()));
|
||||
|
||||
const auto votes = Ui::CreateChild<Ui::FlatLabel>(
|
||||
header,
|
||||
phrase(
|
||||
lt_count_decimal,
|
||||
controller->fullCount() | rpl::map(_1 + 0.)),
|
||||
st::pollResultsVotesCount);
|
||||
const auto collapse = Ui::CreateChild<Ui::LinkButton>(
|
||||
header,
|
||||
tr::lng_polls_votes_collapse(tr::now),
|
||||
st::defaultLinkButton);
|
||||
collapse->setClickedCallback([=] {
|
||||
controller->scrollTo(headerWrap->y());
|
||||
controller->collapse();
|
||||
});
|
||||
rpl::combine(
|
||||
controller->fullCount(),
|
||||
controller->count()
|
||||
) | rpl::on_next([=](int fullCount, int count) {
|
||||
const auto many = (fullCount > kFirstPage)
|
||||
&& (count > kFirstPage - kLeavePreloaded);
|
||||
collapse->setVisible(many);
|
||||
votes->setVisible(!many);
|
||||
}, collapse->lifetime());
|
||||
|
||||
headerWrap->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
header->resizeToWidth(width);
|
||||
votes->moveToRight(
|
||||
st::pollResultsHeaderPadding.right(),
|
||||
st::pollResultsHeaderPadding.top(),
|
||||
width);
|
||||
collapse->moveToRight(
|
||||
st::pollResultsHeaderPadding.right(),
|
||||
st::pollResultsHeaderPadding.top(),
|
||||
width);
|
||||
}, header->lifetime());
|
||||
|
||||
header->heightValue(
|
||||
) | rpl::on_next([=](int height) {
|
||||
headerWrap->resize(headerWrap->width(), height);
|
||||
}, header->lifetime());
|
||||
|
||||
auto moreTopWidget = object_ptr<Ui::RpWidget>(container);
|
||||
moreTopWidget->resize(0, 0);
|
||||
const auto moreTop = container->add(std::move(moreTopWidget));
|
||||
const auto more = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
container,
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
container,
|
||||
tr::lng_polls_show_more(
|
||||
lt_count_decimal,
|
||||
controller->loadMoreCount() | rpl::map(_1 + 0.),
|
||||
tr::upper),
|
||||
st::pollResultsShowMore)));
|
||||
more->entity()->setClickedCallback([=] {
|
||||
controller->allowLoadMore();
|
||||
});
|
||||
controller->loadMoreCount(
|
||||
) | rpl::map(_1 > 0) | rpl::on_next([=](bool visible) {
|
||||
more->toggle(visible, anim::type::instant);
|
||||
}, more->lifetime());
|
||||
|
||||
container->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
container,
|
||||
st::boxLittleSkip));
|
||||
|
||||
rpl::combine(
|
||||
std::move(visibleTop),
|
||||
headerWrap->geometryValue(),
|
||||
moreTop->topValue()
|
||||
) | rpl::filter([=](int, QRect headerRect, int moreTop) {
|
||||
return moreTop >= headerRect.y() + headerRect.height();
|
||||
}) | rpl::on_next([=](
|
||||
int visibleTop,
|
||||
QRect headerRect,
|
||||
int moreTop) {
|
||||
const auto skip = st::pollResultsHeaderPadding.top()
|
||||
- st::pollResultsHeaderPadding.bottom();
|
||||
const auto top = std::clamp(
|
||||
visibleTop - skip,
|
||||
headerRect.y(),
|
||||
moreTop - headerRect.height());
|
||||
header->move(0, top);
|
||||
}, header->lifetime());
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
InnerWidget::InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId contextId)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _poll(poll)
|
||||
, _contextId(contextId)
|
||||
, _content(this) {
|
||||
setupContent();
|
||||
}
|
||||
|
||||
void InnerWidget::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
setChildVisibleTopBottom(_content, visibleTop, visibleBottom);
|
||||
_visibleTop = visibleTop;
|
||||
}
|
||||
|
||||
void InnerWidget::saveState(not_null<Memento*> memento) {
|
||||
auto states = base::flat_map<
|
||||
QByteArray,
|
||||
std::unique_ptr<PeerListState>>();
|
||||
for (const auto &[option, controller] : _sections) {
|
||||
states[option] = controller->saveState();
|
||||
}
|
||||
memento->setListStates(std::move(states));
|
||||
}
|
||||
|
||||
void InnerWidget::restoreState(not_null<Memento*> memento) {
|
||||
auto states = memento->listStates();
|
||||
for (const auto &[option, controller] : _sections) {
|
||||
const auto i = states.find(option);
|
||||
if (i != end(states)) {
|
||||
controller->restoreState(std::move(i->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int InnerWidget::desiredHeight() const {
|
||||
auto desired = 0;
|
||||
//auto count = qMax(_user->commonChatsCount(), 1);
|
||||
//desired += qMax(count, _list->fullRowsCount())
|
||||
// * st::infoCommonGroupsList.item.height;
|
||||
return qMax(height(), desired);
|
||||
}
|
||||
|
||||
void InnerWidget::setupContent() {
|
||||
_content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
_content,
|
||||
rpl::single(_poll->question),
|
||||
st::pollResultsQuestion,
|
||||
st::defaultPopupMenu,
|
||||
Core::TextContext({ .session = &_controller->session() })),
|
||||
st::boxRowPadding);
|
||||
Ui::AddSkip(_content, st::boxLittleSkip / 2);
|
||||
_content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
_content,
|
||||
tr::lng_polls_votes_count(
|
||||
lt_count_decimal,
|
||||
rpl::single(float64(_poll->totalVoters))),
|
||||
st::boxDividerLabel),
|
||||
st::boxRowPadding);
|
||||
Ui::AddSkip(_content, st::boxLittleSkip);
|
||||
for (const auto &answer : _poll->answers) {
|
||||
const auto session = &_controller->session();
|
||||
const auto controller = CreateAnswerRows(
|
||||
_content,
|
||||
_visibleTop.value(),
|
||||
session,
|
||||
_poll,
|
||||
_contextId,
|
||||
answer);
|
||||
if (!controller) {
|
||||
continue;
|
||||
}
|
||||
controller->showPeerInfoRequests(
|
||||
) | rpl::start_to_stream(
|
||||
_showPeerInfoRequests,
|
||||
lifetime());
|
||||
controller->scrollToRequests(
|
||||
) | rpl::on_next([=](int y) {
|
||||
_scrollToRequests.fire({ y, -1 });
|
||||
}, lifetime());
|
||||
_sections.emplace(answer.option, controller);
|
||||
}
|
||||
|
||||
widthValue(
|
||||
) | rpl::on_next([=](int newWidth) {
|
||||
_content->resizeToWidth(newWidth);
|
||||
}, _content->lifetime());
|
||||
|
||||
_content->heightValue(
|
||||
) | rpl::on_next([=](int height) {
|
||||
resize(width(), height);
|
||||
}, _content->lifetime());
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
auto InnerWidget::showPeerInfoRequests() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _showPeerInfoRequests.events();
|
||||
}
|
||||
|
||||
} // namespace Info::Polls
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
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/rp_widget.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
class Controller;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info::Polls {
|
||||
|
||||
class Memento;
|
||||
class ListController;
|
||||
|
||||
class InnerWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId contextId);
|
||||
|
||||
[[nodiscard]] not_null<PollData*> poll() const {
|
||||
return _poll;
|
||||
}
|
||||
[[nodiscard]] FullMsgId contextId() const {
|
||||
return _contextId;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto scrollToRequests() const
|
||||
-> rpl::producer<Ui::ScrollToRequest>;
|
||||
|
||||
[[nodiscard]] auto showPeerInfoRequests() const
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
|
||||
[[nodiscard]] int desiredHeight() const;
|
||||
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) override;
|
||||
|
||||
private:
|
||||
void setupContent();
|
||||
|
||||
not_null<Controller*> _controller;
|
||||
not_null<PollData*> _poll;
|
||||
FullMsgId _contextId;
|
||||
object_ptr<Ui::VerticalLayout> _content;
|
||||
base::flat_map<QByteArray, not_null<ListController*>> _sections;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<not_null<PeerData*>> _showPeerInfoRequests;
|
||||
rpl::variable<int> _visibleTop = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Polls
|
||||
115
Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp
Normal file
115
Telegram/SourceFiles/info/polls/info_polls_results_widget.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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_results_widget.h"
|
||||
|
||||
#include "info/polls/info_polls_results_inner_widget.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Info::Polls {
|
||||
|
||||
Memento::Memento(not_null<PollData*> poll, FullMsgId contextId)
|
||||
: ContentMemento(poll, contextId) {
|
||||
}
|
||||
|
||||
Section Memento::section() const {
|
||||
return Section(Section::Type::PollResults);
|
||||
}
|
||||
|
||||
void Memento::setListStates(base::flat_map<
|
||||
QByteArray,
|
||||
std::unique_ptr<PeerListState>> states) {
|
||||
_listStates = std::move(states);
|
||||
}
|
||||
|
||||
auto Memento::listStates()
|
||||
-> base::flat_map<QByteArray, std::unique_ptr<PeerListState>> {
|
||||
return std::move(_listStates);
|
||||
}
|
||||
|
||||
object_ptr<ContentWidget> Memento::createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
const QRect &geometry) {
|
||||
auto result = object_ptr<Widget>(parent, controller);
|
||||
result->setInternalState(geometry, this);
|
||||
return result;
|
||||
}
|
||||
|
||||
Memento::~Memento() = default;
|
||||
|
||||
Widget::Widget(QWidget *parent, not_null<Controller*> controller)
|
||||
: ContentWidget(parent, controller)
|
||||
, _inner(setInnerWidget(
|
||||
object_ptr<InnerWidget>(
|
||||
this,
|
||||
controller,
|
||||
controller->poll(),
|
||||
controller->pollContextId()))) {
|
||||
_inner->showPeerInfoRequests(
|
||||
) | rpl::on_next([=](not_null<PeerData*> peer) {
|
||||
controller->showPeerInfo(peer);
|
||||
}, _inner->lifetime());
|
||||
_inner->scrollToRequests(
|
||||
) | rpl::on_next([=](const Ui::ScrollToRequest &request) {
|
||||
scrollTo(request);
|
||||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
not_null<PollData*> Widget::poll() const {
|
||||
return _inner->poll();
|
||||
}
|
||||
|
||||
FullMsgId Widget::contextId() const {
|
||||
return _inner->contextId();
|
||||
}
|
||||
|
||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||
//if (const auto myMemento = dynamic_cast<Memento*>(memento.get())) {
|
||||
// Assert(myMemento->self() == self());
|
||||
|
||||
// if (_inner->showInternal(myMemento)) {
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Widget::setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<Memento*> memento) {
|
||||
setGeometry(geometry);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
restoreState(memento);
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
return poll()->quiz()
|
||||
? tr::lng_polls_quiz_results_title()
|
||||
: tr::lng_polls_poll_results_title();
|
||||
}
|
||||
|
||||
std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
|
||||
auto result = std::make_shared<Memento>(poll(), contextId());
|
||||
saveState(result.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
void Widget::saveState(not_null<Memento*> memento) {
|
||||
memento->setScrollTop(scrollTopSave());
|
||||
_inner->saveState(memento);
|
||||
}
|
||||
|
||||
void Widget::restoreState(not_null<Memento*> memento) {
|
||||
_inner->restoreState(memento);
|
||||
scrollTopRestore(memento->scrollTop());
|
||||
}
|
||||
|
||||
} // namespace Info::Polls
|
||||
70
Telegram/SourceFiles/info/polls/info_polls_results_widget.h
Normal file
70
Telegram/SourceFiles/info/polls/info_polls_results_widget.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 "info/info_controller.h"
|
||||
|
||||
struct PeerListState;
|
||||
|
||||
namespace Info::Polls {
|
||||
|
||||
class InnerWidget;
|
||||
|
||||
class Memento final : public ContentMemento {
|
||||
public:
|
||||
Memento(not_null<PollData*> poll, FullMsgId contextId);
|
||||
~Memento();
|
||||
|
||||
object_ptr<ContentWidget> createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
const QRect &geometry) override;
|
||||
|
||||
Section section() const override;
|
||||
|
||||
void setListStates(base::flat_map<
|
||||
QByteArray,
|
||||
std::unique_ptr<PeerListState>> states);
|
||||
auto listStates()
|
||||
-> base::flat_map<QByteArray, std::unique_ptr<PeerListState>>;
|
||||
|
||||
private:
|
||||
base::flat_map<
|
||||
QByteArray,
|
||||
std::unique_ptr<PeerListState>> _listStates;
|
||||
|
||||
};
|
||||
|
||||
class Widget final : public ContentWidget {
|
||||
public:
|
||||
Widget(QWidget *parent, not_null<Controller*> controller);
|
||||
|
||||
[[nodiscard]] not_null<PollData*> poll() const;
|
||||
[[nodiscard]] FullMsgId contextId() const;
|
||||
|
||||
bool showInternal(
|
||||
not_null<ContentMemento*> memento) override;
|
||||
|
||||
void setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<Memento*> memento);
|
||||
|
||||
rpl::producer<QString> title() override;
|
||||
|
||||
private:
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||
|
||||
not_null<InnerWidget*> _inner;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Polls
|
||||
Reference in New Issue
Block a user