Poll data and updates handling added.

This commit is contained in:
John Preston 2018-12-18 09:43:11 +04:00
parent 099440d008
commit 47bdeeef9a
13 changed files with 393 additions and 20 deletions

View File

@ -2797,7 +2797,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
++i;
}
}
_session->data().sendWebPageGameNotifications();
_session->data().sendWebPageGamePollNotifications();
}
void ApiWrap::stickersSaveOrder() {

View File

@ -152,6 +152,10 @@ LocationData *Media::location() const {
return nullptr;
}
PollData *Media::poll() const {
return nullptr;
}
bool Media::uploading() const {
return false;
}
@ -1178,4 +1182,52 @@ std::unique_ptr<HistoryMedia> MediaInvoice::createView(
return std::make_unique<HistoryInvoice>(message, &_invoice);
}
MediaPoll::MediaPoll(
not_null<HistoryItem*> parent,
not_null<PollData*> poll)
: Media(parent)
, _poll(poll) {
}
MediaPoll::~MediaPoll() {
}
std::unique_ptr<Media> MediaPoll::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaPoll>(parent, _poll);
}
PollData *MediaPoll::poll() const {
return _poll;
}
QString MediaPoll::chatsListText() const {
return QString(); // #TODO polls
}
QString MediaPoll::notificationText() const {
return QString(); // #TODO polls
}
QString MediaPoll::pinnedTextSubstring() const {
return QString(); // #TODO polls
}
TextWithEntities MediaPoll::clipboardText() const {
return TextWithEntities(); // #TODO polls
}
bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) {
return false;
}
bool MediaPoll::updateSentMedia(const MTPMessageMedia &media) {
return false;
}
std::unique_ptr<HistoryMedia> MediaPoll::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent) {
return nullptr;
}
} // namespace Data

View File

@ -41,7 +41,6 @@ struct SharedContact {
QString firstName;
QString lastName;
QString phoneNumber;
};
struct Call {
@ -49,7 +48,6 @@ struct Call {
int duration = 0;
FinishReason finishReason = FinishReason::Missed;
};
struct Invoice {
@ -60,7 +58,6 @@ struct Invoice {
QString description;
PhotoData *photo = nullptr;
bool isTest = false;
};
class Media {
@ -80,6 +77,7 @@ public:
virtual GameData *game() const;
virtual const Invoice *invoice() const;
virtual LocationData *location() const;
virtual PollData *poll() const;
virtual bool uploading() const;
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
@ -381,6 +379,33 @@ private:
};
class MediaPoll : public Media {
public:
MediaPoll(
not_null<HistoryItem*> parent,
not_null<PollData*> poll);
~MediaPoll();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
PollData *poll() const override;
QString chatsListText() const override;
QString notificationText() const override;
QString pinnedTextSubstring() const override;
TextWithEntities clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
std::unique_ptr<HistoryMedia> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent) override;
private:
not_null<PollData*> _poll;
};
TextWithEntities WithCaptionClipboardText(
const QString &attachType,
TextWithEntities &&caption);

View File

@ -0,0 +1,122 @@
/*
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.h"
namespace {
const PollAnswer *AnswerByOption(
const std::vector<PollAnswer> &list,
const QByteArray &option) {
const auto i = ranges::find(
list,
option,
[](const PollAnswer &a) { return a.option; });
return (i != end(list)) ? &*i : nullptr;
}
PollAnswer *AnswerByOption(
std::vector<PollAnswer> &list,
const QByteArray &option) {
return const_cast<PollAnswer*>(AnswerByOption(
std::as_const(list),
option));
}
} // namespace
PollData::PollData(PollId id) : id(id) {
}
bool PollData::applyChanges(const MTPDpoll &poll) {
Expects(poll.vid.v == id);
const auto newQuestion = qs(poll.vquestion);
const auto newClosed = poll.is_closed();
auto newAnswers = ranges::view::all(
poll.vanswers.v
) | ranges::view::transform([](const MTPPollAnswer &data) {
return data.match([](const MTPDpollAnswer &answer) {
auto result = PollAnswer();
result.option = answer.voption.v;
result.text = qs(answer.vtext);
return result;
});
}) | ranges::to_vector;
const auto changed1 = (question != newQuestion)
|| (closed != newClosed);
const auto changed2 = (answers != newAnswers);
if (!changed1 && !changed2) {
return false;
}
if (changed1) {
question = newQuestion;
closed = newClosed;
}
if (changed2) {
std::swap(answers, newAnswers);
for (const auto &old : newAnswers) {
if (const auto current = answerByOption(old.option)) {
current->votes = old.votes;
current->chosen = old.chosen;
}
}
}
return true;
}
bool PollData::applyResults(const MTPPollResults &results) {
return results.match([&](const MTPDpollResults &results) {
const auto newTotalVoters = results.has_total_voters()
? results.vtotal_voters.v
: totalVoters;
auto changed = (newTotalVoters != totalVoters);
if (results.has_results()) {
for (const auto &result : results.vresults.v) {
if (applyResultToAnswers(result, results.is_min())) {
changed = true;
}
}
}
totalVoters = newTotalVoters;
return changed;
});
}
PollAnswer *PollData::answerByOption(const QByteArray &option) {
return AnswerByOption(answers, option);
}
const PollAnswer *PollData::answerByOption(const QByteArray &option) const {
return AnswerByOption(answers, option);
}
bool PollData::applyResultToAnswers(
const MTPPollAnswerVoters &result,
bool isMinResults) {
return result.match([&](const MTPDpollAnswerVoters &voters) {
const auto &option = voters.voption.v;
const auto answer = answerByOption(option);
if (!answer) {
return false;
}
auto changed = (answer->votes != voters.vvoters.v);
if (changed) {
answer->votes = voters.vvoters.v;
}
if (!isMinResults) {
if (answer->chosen != voters.is_chosen()) {
answer->chosen = voters.is_chosen();
changed = true;
}
} else if (const auto existing = answerByOption(option)) {
answer->chosen = existing->chosen;
}
return changed;
});
}

View File

@ -0,0 +1,46 @@
/*
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
struct PollAnswer {
QString text;
QByteArray option;
int votes = 0;
bool chosen = false;
};
inline bool operator==(const PollAnswer &a, const PollAnswer &b) {
return (a.text == b.text)
&& (a.option == b.option);
}
inline bool operator!=(const PollAnswer &a, const PollAnswer &b) {
return !(a == b);
}
struct PollData {
explicit PollData(PollId id);
bool applyChanges(const MTPDpoll &poll);
bool applyResults(const MTPPollResults &results);
PollAnswer *answerByOption(const QByteArray &option);
const PollAnswer *answerByOption(const QByteArray &option) const;
PollId id = 0;
QString question;
std::vector<PollAnswer> answers;
int totalVoters = 0;
bool closed = false;
private:
bool applyResultToAnswers(
const MTPPollAnswerVoters &result,
bool isMinResults);
};

View File

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_web_page.h"
#include "data/data_game.h"
#include "data/data_poll.h"
namespace Data {
namespace {
@ -297,6 +298,14 @@ void Session::requestDocumentViewRepaint(
}
}
void Session::requestPollViewRepaint(not_null<const PollData*> poll) {
if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
for (const auto view : i->second) {
requestViewResize(view);
}
}
}
void Session::markMediaRead(not_null<const DocumentData*> document) {
const auto i = _documentItems.find(document);
if (i != end(_documentItems)) {
@ -1490,6 +1499,55 @@ void Session::gameApplyFields(
notifyGameUpdateDelayed(game);
}
not_null<PollData*> Session::poll(PollId id) {
auto i = _polls.find(id);
if (i == _polls.cend()) {
i = _polls.emplace(id, std::make_unique<PollData>(id)).first;
}
return i->second.get();
}
not_null<PollData*> Session::poll(const MTPPoll &data) {
return data.match([&](const MTPDpoll &data) {
const auto id = data.vid.v;
const auto result = poll(id);
const auto changed = result->applyChanges(data);
if (changed) {
notifyPollUpdateDelayed(result);
}
return result;
});
}
not_null<PollData*> Session::poll(const MTPDmessageMediaPoll &data) {
const auto result = poll(data.vpoll);
const auto changed = result->applyResults(data.vresults);
if (changed) {
requestPollViewRepaint(result);
}
return result;
}
void Session::applyPollUpdate(const MTPDupdateMessagePoll &update) {
const auto poll = [&] {
if (update.has_poll()) {
return update.vpoll.match([&](const MTPDpoll &data) {
const auto i = _polls.find(data.vid.v);
return (i != end(_polls)) ? i->second.get() : nullptr;
});
}
const auto item = App::histItemById(
peerToChannel(peerFromMTP(update.vpeer)),
update.vmsg_id.v);
return (item && item->media())
? item->media()->poll()
: nullptr;
}();
if (poll && poll->applyResults(update.vresults)) {
requestPollViewRepaint(poll);
}
}
not_null<LocationData*> Session::location(const LocationCoords &coords) {
auto i = _locations.find(coords);
if (i == _locations.cend()) {
@ -1590,6 +1648,24 @@ void Session::unregisterGameView(
}
}
void Session::registerPollView(
not_null<const PollData*> poll,
not_null<ViewElement*> view) {
_pollViews[poll].insert(view);
}
void Session::unregisterPollView(
not_null<const PollData*> poll,
not_null<ViewElement*> view) {
const auto i = _pollViews.find(poll);
if (i != _pollViews.end()) {
auto &items = i->second;
if (items.remove(view) && items.empty()) {
_pollViews.erase(i);
}
}
}
void Session::registerContactView(
UserId contactId,
not_null<ViewElement*> view) {
@ -1714,23 +1790,37 @@ QString Session::findContactPhone(UserId contactId) const {
return QString();
}
bool Session::hasPendingWebPageGamePollNotification() const {
return !_webpagesUpdated.empty()
|| !_gamesUpdated.empty()
|| !_pollsUpdated.empty();
}
void Session::notifyWebPageUpdateDelayed(not_null<WebPageData*> page) {
const auto invoke = _webpagesUpdated.empty() && _gamesUpdated.empty();
const auto invoke = !hasPendingWebPageGamePollNotification();
_webpagesUpdated.insert(page);
if (invoke) {
crl::on_main(_session, [=] { sendWebPageGameNotifications(); });
crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
}
}
void Session::notifyGameUpdateDelayed(not_null<GameData*> game) {
const auto invoke = _webpagesUpdated.empty() && _gamesUpdated.empty();
const auto invoke = !hasPendingWebPageGamePollNotification();
_gamesUpdated.insert(game);
if (invoke) {
crl::on_main(_session, [=] { sendWebPageGameNotifications(); });
crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
}
}
void Session::sendWebPageGameNotifications() {
void Session::notifyPollUpdateDelayed(not_null<PollData*> poll) {
const auto invoke = !hasPendingWebPageGamePollNotification();
_pollsUpdated.insert(poll);
if (invoke) {
crl::on_main(_session, [=] { sendWebPageGamePollNotifications(); });
}
}
void Session::sendWebPageGamePollNotifications() {
for (const auto page : base::take(_webpagesUpdated)) {
const auto i = _webpageViews.find(page);
if (i != _webpageViews.end()) {
@ -1746,6 +1836,13 @@ void Session::sendWebPageGameNotifications() {
}
}
}
for (const auto poll : base::take(_pollsUpdated)) {
if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
for (const auto view : i->second) {
requestViewResize(view);
}
}
}
}
void Session::registerItemView(not_null<ViewElement*> view) {

View File

@ -246,6 +246,7 @@ public:
not_null<const DocumentData*> document);
void requestDocumentViewRepaint(not_null<const DocumentData*> document);
void markMediaRead(not_null<const DocumentData*> document);
void requestPollViewRepaint(not_null<const PollData*> poll);
not_null<PhotoData*> photo(PhotoId id);
not_null<PhotoData*> photo(const MTPPhoto &data);
@ -330,6 +331,11 @@ public:
not_null<GameData*> original,
const MTPGame &data);
not_null<PollData*> poll(PollId id);
not_null<PollData*> poll(const MTPPoll &data);
not_null<PollData*> poll(const MTPDmessageMediaPoll &data);
void applyPollUpdate(const MTPDupdateMessagePoll &update);
not_null<LocationData*> location(const LocationCoords &coords);
void registerPhotoItem(
@ -362,6 +368,12 @@ public:
void unregisterGameView(
not_null<const GameData*> game,
not_null<ViewElement*> view);
void registerPollView(
not_null<const PollData*> poll,
not_null<ViewElement*> view);
void unregisterPollView(
not_null<const PollData*> poll,
not_null<ViewElement*> view);
void registerContactView(
UserId contactId,
not_null<ViewElement*> view);
@ -386,7 +398,9 @@ public:
void notifyWebPageUpdateDelayed(not_null<WebPageData*> page);
void notifyGameUpdateDelayed(not_null<GameData*> game);
void sendWebPageGameNotifications();
void notifyPollUpdateDelayed(not_null<PollData*> poll);
bool hasPendingWebPageGamePollNotification() const;
void sendWebPageGamePollNotifications();
void stopAutoplayAnimations();
@ -604,21 +618,27 @@ private:
std::unordered_map<
WebPageId,
std::unique_ptr<WebPageData>> _webpages;
std::unordered_map<
LocationCoords,
std::unique_ptr<LocationData>> _locations;
std::map<
not_null<const WebPageData*>,
base::flat_set<not_null<HistoryItem*>>> _webpageItems;
std::map<
not_null<const WebPageData*>,
base::flat_set<not_null<ViewElement*>>> _webpageViews;
std::unordered_map<
LocationCoords,
std::unique_ptr<LocationData>> _locations;
std::unordered_map<
PollId,
std::unique_ptr<PollData>> _polls;
std::unordered_map<
GameId,
std::unique_ptr<GameData>> _games;
std::map<
not_null<const GameData*>,
base::flat_set<not_null<ViewElement*>>> _gameViews;
std::map<
not_null<const PollData*>,
base::flat_set<not_null<ViewElement*>>> _pollViews;
std::map<
UserId,
base::flat_set<not_null<HistoryItem*>>> _contactItems;
@ -631,6 +651,7 @@ private:
base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
base::flat_set<not_null<GameData*>> _gamesUpdated;
base::flat_set<not_null<PollData*>> _pollsUpdated;
std::deque<Dialogs::Key> _pinnedDialogs;
base::flat_map<FeedId, std::unique_ptr<Feed>> _feeds;

View File

@ -243,6 +243,7 @@ class DocumentData;
class PhotoData;
struct WebPageData;
struct GameData;
struct PollData;
class AudioMsgId;
class PhotoClickHandler;
@ -262,6 +263,7 @@ using AudioId = uint64;
using DocumentId = uint64;
using WebPageId = uint64;
using GameId = uint64;
using PollId = uint64;
constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL);
using PreparedPhotoThumbs = QMap<char, QImage>;

View File

@ -138,8 +138,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
});
}, [](const MTPDmessageMediaInvoice &) {
return Result::Good;
}, [](const MTPDmessageMediaPoll &) { // #TODO polls
return Result::Unsupported;
}, [](const MTPDmessageMediaPoll &) {
return Result::Good;
}, [](const MTPDmessageMediaUnsupported &) {
return Result::Unsupported;
});

View File

@ -855,8 +855,10 @@ std::unique_ptr<Data::Media> HistoryMessage::CreateMedia(
});
}, [&](const MTPDmessageMediaInvoice &media) -> Result {
return std::make_unique<Data::MediaInvoice>(item, media);
}, [&](const MTPDmessageMediaPoll &media) -> Result { // #TODO polls
return nullptr;
}, [&](const MTPDmessageMediaPoll &media) -> Result {
return std::make_unique<Data::MediaPoll>(
item,
Auth().data().poll(media));
}, [](const MTPDmessageMediaEmpty &) -> Result {
return nullptr;
}, [](const MTPDmessageMediaUnsupported &) -> Result {

View File

@ -5928,7 +5928,7 @@ void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtp
: nullptr;
updatePreview();
}
Auth().data().sendWebPageGameNotifications();
Auth().data().sendWebPageGamePollNotifications();
} else if (result.type() == mtpc_messageMediaEmpty) {
_previewCache.insert(links, 0);
if (links == _previewLinks && !_previewCancelled) {

View File

@ -4246,7 +4246,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
// Update web page anyway.
Auth().data().webpage(d.vwebpage);
_history->updatePreview();
Auth().data().sendWebPageGameNotifications();
Auth().data().sendWebPageGamePollNotifications();
ptsUpdateAndApply(d.vpts.v, d.vpts_count.v, update);
} break;
@ -4257,7 +4257,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
// Update web page anyway.
Auth().data().webpage(d.vwebpage);
_history->updatePreview();
Auth().data().sendWebPageGameNotifications();
Auth().data().sendWebPageGamePollNotifications();
auto channel = App::channelLoaded(d.vchannel_id.v);
if (channel && !_handlingChannelDifference) {
@ -4271,6 +4271,10 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
}
} break;
case mtpc_updateMessagePoll: {
Auth().data().applyPollUpdate(update.c_updateMessagePoll());
} break;
case mtpc_updateUserTyping: {
auto &d = update.c_updateUserTyping();
const auto userId = peerFromUser(d.vuser_id);

View File

@ -166,6 +166,8 @@
<(src_loc)/data/data_peer_values.h
<(src_loc)/data/data_photo.cpp
<(src_loc)/data/data_photo.h
<(src_loc)/data/data_poll.cpp
<(src_loc)/data/data_poll.h
<(src_loc)/data/data_search_controller.cpp
<(src_loc)/data/data_search_controller.h
<(src_loc)/data/data_session.cpp