Implement dice media display.

This commit is contained in:
John Preston 2020-03-04 13:21:19 +04:00
parent d4b9b65724
commit c83e297554
17 changed files with 401 additions and 25 deletions

View file

@ -270,6 +270,8 @@ PRIVATE
chat_helpers/stickers.h
chat_helpers/stickers_emoji_pack.cpp
chat_helpers/stickers_emoji_pack.h
chat_helpers/stickers_dice_pack.cpp
chat_helpers/stickers_dice_pack.h
chat_helpers/stickers_list_widget.cpp
chat_helpers/stickers_list_widget.h
chat_helpers/tabbed_panel.cpp
@ -436,6 +438,8 @@ PRIVATE
history/view/media/history_view_call.cpp
history/view/media/history_view_contact.h
history/view/media/history_view_contact.cpp
history/view/media/history_view_dice.h
history/view/media/history_view_dice.cpp
history/view/media/history_view_document.h
history/view/media/history_view_document.cpp
history/view/media/history_view_file.h

Binary file not shown.

View file

@ -47,6 +47,7 @@
<file alias="art/logo_256.png">../../art/logo_256.png</file>
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
<file alias="art/sunrise.jpg">../../art/sunrise.jpg</file>
<file alias="art/dice_idle.tgs">../../art/dice_idle.tgs</file>
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>

View file

@ -0,0 +1,96 @@
/*
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 "chat_helpers/stickers_dice_pack.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "storage/localimageloader.h"
#include "base/unixtime.h"
#include "apiwrap.h"
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
namespace Stickers {
namespace {
constexpr auto kZeroDiceDocumentId = 0xa3b83c9f84fa9e83ULL;
} // namespace
DicePack::DicePack(not_null<Main::Session*> session)
: _session(session) {
}
DicePack::~DicePack() = default;
DocumentData *DicePack::lookup(int value) {
if (!_requestId) {
load();
}
if (!value) {
ensureZeroGenerated();
return _zero;
}
const auto i = _map.find(value);
return (i != end(_map)) ? i->second.get() : nullptr;
}
void DicePack::load() {
if (_requestId) {
return;
}
_requestId = _session->api().request(MTPmessages_GetStickerSet(
MTP_inputStickerSetDice()
)).done([=](const MTPmessages_StickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &data) {
applySet(data);
});
}).fail([=](const RPCError &error) {
_requestId = 0;
}).send();
}
void DicePack::applySet(const MTPDmessages_stickerSet &data) {
auto index = 0;
for (const auto &sticker : data.vdocuments().v) {
const auto document = _session->data().processDocument(
sticker);
if (document->sticker()) {
_map.emplace(++index, document);
}
}
}
void DicePack::ensureZeroGenerated() {
if (_zero) {
return;
}
const auto path = qsl(":/gui/art/dice_idle.tgs");
auto task = FileLoadTask(
path,
QByteArray(),
nullptr,
SendMediaType::File,
FileLoadTo(0, {}, 0),
{});
task.process();
const auto result = task.peekResult();
Assert(result != nullptr);
_zero = _session->data().processDocument(
result->document,
std::move(result->thumb));
_zero->setLocation(FileLocation(path));
Ensures(_zero->sticker());
Ensures(_zero->sticker()->animated);
}
} // namespace Stickers

View file

@ -0,0 +1,37 @@
/*
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 DocumentData;
namespace Main {
class Session;
} // namespace Main
namespace Stickers {
class DicePack final {
public:
explicit DicePack(not_null<Main::Session*> session);
~DicePack();
DocumentData *lookup(int value);
private:
void load();
void applySet(const MTPDmessages_stickerSet &data);
void ensureZeroGenerated();
not_null<Main::Session*> _session;
base::flat_map<int, not_null<DocumentData*>> _map;
DocumentData *_zero = nullptr;
mtpRequestId _requestId = 0;
};
} // namespace Stickers

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_web_page.h"
#include "history/view/media/history_view_poll.h"
#include "history/view/media/history_view_theme_document.h"
#include "history/view/media/history_view_dice.h"
#include "ui/image/image.h"
#include "ui/image/image_source.h"
#include "ui/text_options.h"
@ -1325,4 +1326,49 @@ std::unique_ptr<HistoryView::Media> MediaPoll::createView(
return std::make_unique<HistoryView::Poll>(message, _poll);
}
MediaDice::MediaDice(not_null<HistoryItem*> parent, int value)
: Media(parent)
, _value(value) {
}
std::unique_ptr<Media> MediaDice::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaDice>(parent, _value);
}
int MediaDice::diceValue() const {
return _value;
}
QString MediaDice::notificationText() const {
return QString::fromUtf8("\xF0\x9F\x8E\xB2");
}
QString MediaDice::pinnedTextSubstring() const {
return QChar(171) + notificationText() + QChar(187);
}
TextForMimeData MediaDice::clipboardText() const {
return { notificationText() };
}
bool MediaDice::updateInlineResultMedia(const MTPMessageMedia &media) {
return updateSentMedia(media);
}
bool MediaDice::updateSentMedia(const MTPMessageMedia &media) {
if (media.type() != mtpc_messageMediaDice) {
return false;
}
_value = media.c_messageMediaDice().vvalue().v;
return true;
}
std::unique_ptr<HistoryView::Media> MediaDice::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent) {
return std::make_unique<HistoryView::UnwrappedMedia>(
message,
std::make_unique<HistoryView::Dice>(message, _value));
}
} // namespace Data

View file

@ -118,7 +118,7 @@ private:
};
class MediaPhoto : public Media {
class MediaPhoto final : public Media {
public:
MediaPhoto(
not_null<HistoryItem*> parent,
@ -158,7 +158,7 @@ private:
};
class MediaFile : public Media {
class MediaFile final : public Media {
public:
MediaFile(
not_null<HistoryItem*> parent,
@ -195,7 +195,7 @@ private:
};
class MediaContact : public Media {
class MediaContact final : public Media {
public:
MediaContact(
not_null<HistoryItem*> parent,
@ -223,7 +223,7 @@ private:
};
class MediaLocation : public Media {
class MediaLocation final : public Media {
public:
MediaLocation(
not_null<HistoryItem*> parent,
@ -255,7 +255,7 @@ private:
};
class MediaCall : public Media {
class MediaCall final : public Media {
public:
MediaCall(
not_null<HistoryItem*> parent,
@ -284,7 +284,7 @@ private:
};
class MediaWebPage : public Media {
class MediaWebPage final : public Media {
public:
MediaWebPage(
not_null<HistoryItem*> parent,
@ -316,7 +316,7 @@ private:
};
class MediaGame : public Media {
class MediaGame final : public Media {
public:
MediaGame(
not_null<HistoryItem*> parent,
@ -348,7 +348,7 @@ private:
};
class MediaInvoice : public Media {
class MediaInvoice final : public Media {
public:
MediaInvoice(
not_null<HistoryItem*> parent,
@ -378,7 +378,7 @@ private:
};
class MediaPoll : public Media {
class MediaPoll final : public Media {
public:
MediaPoll(
not_null<HistoryItem*> parent,
@ -405,6 +405,28 @@ private:
};
class MediaDice final : public Media {
public:
MediaDice(not_null<HistoryItem*> parent, int value);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
int diceValue() const;
QString notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
std::unique_ptr<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent) override;
private:
int _value = 0;
};
TextForMimeData WithCaptionClipboardText(
const QString &attachType,
TextForMimeData &&caption);

View file

@ -154,7 +154,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaPoll &) {
return Result::Good;
}, [](const MTPDmessageMediaDice &) {
return Result::Unsupported; // #TODO dice
return Result::Good;
}, [](const MTPDmessageMediaUnsupported &) {
return Result::Unsupported;
});

View file

@ -1024,8 +1024,8 @@ std::unique_ptr<Data::Media> HistoryMessage::CreateMedia(
return std::make_unique<Data::MediaPoll>(
item,
item->history()->owner().processPoll(media));
}, [](const MTPDmessageMediaDice &media) -> Result {
return nullptr; // #TODO dice
}, [&](const MTPDmessageMediaDice &media) -> Result {
return std::make_unique<Data::MediaDice>(item, media.vvalue().v);
}, [](const MTPDmessageMediaEmpty &) -> Result {
return nullptr;
}, [](const MTPDmessageMediaUnsupported &) -> Result {

View file

@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_dice.h"
#include "data/data_session.h"
#include "chat_helpers/stickers_dice_pack.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "main/main_session.h"
namespace HistoryView {
namespace {
DocumentData *Lookup(not_null<Element*> view, int value) {
const auto &session = view->data()->history()->session();
return session.diceStickersPack().lookup(value);
}
} // namespace
Dice::Dice(not_null<Element*> parent, int value)
: _parent(parent)
, _start(parent, Lookup(parent, 0))
, _value(value) {
_start.setDiceIndex(0);
}
Dice::~Dice() = default;
QSize Dice::size() {
return _start.size();
}
void Dice::draw(Painter &p, const QRect &r, bool selected) {
Expects(_end.has_value() || !_drawingEnd);
if (_drawingEnd) {
_end->draw(p, r, selected);
} else {
_start.draw(p, r, selected);
if (!_end && _value) {
if (const auto document = Lookup(_parent, _value)) {
_end.emplace(_parent, document);
_end->setDiceIndex(_value);
_end->initSize();
}
}
if (_end && _end->readyToDrawLottie() && _start.atTheEnd()) {
_drawingEnd = true;
}
}
}
} // namespace HistoryView

View file

@ -0,0 +1,43 @@
/*
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 "history/view/media/history_view_media_unwrapped.h"
#include "history/view/media/history_view_sticker.h"
namespace HistoryView {
class Dice final : public UnwrappedMedia::Content {
public:
Dice(not_null<Element*> parent, int value);
~Dice();
QSize size() override;
void draw(Painter &p, const QRect &r, bool selected) override;
void clearStickerLoopPlayed() override {
_lottieOncePlayed = false;
}
void unloadHeavyPart() override {
_start.unloadHeavyPart();
if (_end) {
_end->unloadHeavyPart();
}
}
private:
const not_null<Element*> _parent;
std::optional<Sticker> _end;
Sticker _start;
int _value = 0;
mutable bool _lottieOncePlayed = false;
mutable bool _drawingEnd = false;
};
} // namespace HistoryView

View file

@ -57,7 +57,7 @@ bool Sticker::isEmojiSticker() const {
return (_parent->data()->media() == nullptr);
}
QSize Sticker::size() {
void Sticker::initSize() {
_size = _document->dimensions;
if (isEmojiSticker()) {
constexpr auto kIdealStickerSize = 512;
@ -70,14 +70,22 @@ QSize Sticker::size() {
_size = DownscaledSize(
_size,
{ st::maxStickerSize, st::maxStickerSize });
[[maybe_unused]] bool result = readyToDrawLottie();
}
}
QSize Sticker::size() {
initSize();
return _size;
}
void Sticker::draw(Painter &p, const QRect &r, bool selected) {
bool Sticker::readyToDrawLottie() {
if (!_lastDiceFrame.isNull()) {
return true;
}
const auto sticker = _document->sticker();
if (!sticker) {
return;
return false;
}
_document->checkStickerLarge();
@ -85,10 +93,14 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) {
if (sticker->animated && !_lottie && loaded) {
setupLottie();
}
return (_lottie && _lottie->ready());
}
if (_lottie && _lottie->ready()) {
void Sticker::draw(Painter &p, const QRect &r, bool selected) {
if (readyToDrawLottie()) {
paintLottie(p, r, selected);
} else if (!sticker->animated || !_replacements) {
} else if (_document->sticker()
&& (!_document->sticker()->animated || !_replacements)) {
paintPixmap(p, r, selected);
}
}
@ -96,24 +108,51 @@ void Sticker::draw(Painter &p, const QRect &r, bool selected) {
void Sticker::paintLottie(Painter &p, const QRect &r, bool selected) {
auto request = Lottie::FrameRequest();
request.box = _size * cIntRetinaFactor();
if (selected) {
if (selected && !_nextLastDiceFrame) {
request.colored = st::msgStickerOverlay->c;
}
const auto frame = _lottie->frameInfo(request);
const auto size = frame.image.size() / cIntRetinaFactor();
const auto frame = _lottie
? _lottie->frameInfo(request)
: Lottie::Animation::FrameInfo();
if (_nextLastDiceFrame) {
_nextLastDiceFrame = false;
_lastDiceFrame = frame.image;
}
const auto &image = _lastDiceFrame.isNull()
? frame.image
: _lastDiceFrame;
const auto prepared = (!_lastDiceFrame.isNull() && selected)
? Images::prepareColored(st::msgStickerOverlay->c, image)
: image;
const auto size = prepared.size() / cIntRetinaFactor();
p.drawImage(
QRect(
QPoint(
r.x() + (r.width() - size.width()) / 2,
r.y() + (r.height() - size.height()) / 2),
size),
frame.image);
prepared);
if (!_lastDiceFrame.isNull()) {
return;
}
const auto paused = App::wnd()->sessionController()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
const auto playOnce = isEmojiSticker()
|| !_document->session().settings().loopAnimatedStickers();
const auto playOnce = (_diceIndex > 0)
? true
: (_diceIndex == 0)
? false
: (isEmojiSticker()
|| !_document->session().settings().loopAnimatedStickers());
const auto count = _lottie->information().framesCount;
_atTheEnd = (frame.index + 1 == count);
_nextLastDiceFrame = !paused
&& (_diceIndex > 0)
&& (frame.index + 2 == count);
const auto lastDiceFrame = (_diceIndex > 0) && _atTheEnd;
const auto switchToNext = !playOnce
|| (!lastDiceFrame && (frame.index != 0 || !_lottieOncePlayed));
if (!paused
&& (!playOnce || frame.index != 0 || !_lottieOncePlayed)
&& switchToNext
&& _lottie->markFrameShown()
&& playOnce
&& !_lottieOncePlayed) {
@ -188,6 +227,10 @@ void Sticker::refreshLink() {
}
}
void Sticker::setDiceIndex(int index) {
_diceIndex = index;
}
void Sticker::setupLottie() {
_lottie = Stickers::LottiePlayerFromDocument(
_document,

View file

@ -31,6 +31,7 @@ public:
const Lottie::ColorReplacements *replacements = nullptr);
~Sticker();
void initSize();
QSize size() override;
void draw(Painter &p, const QRect &r, bool selected) override;
ClickHandlerPtr link() override {
@ -48,6 +49,12 @@ public:
}
void refreshLink() override;
void setDiceIndex(int index);
[[nodiscard]] bool atTheEnd() const {
return _atTheEnd;
}
[[nodiscard]] bool readyToDrawLottie();
private:
[[nodiscard]] bool isEmojiSticker() const;
void paintLottie(Painter &p, const QRect &r, bool selected);
@ -63,7 +70,11 @@ private:
std::unique_ptr<Lottie::SinglePlayer> _lottie;
ClickHandlerPtr _link;
QSize _size;
QImage _lastDiceFrame;
int _diceIndex = -1;
mutable bool _lottieOncePlayed = false;
mutable bool _atTheEnd = false;
mutable bool _nextLastDiceFrame = false;
rpl::lifetime _lifetime;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/changelogs.h"
#include "main/main_account.h"
#include "chat_helpers/stickers_emoji_pack.h"
#include "chat_helpers/stickers_dice_pack.h"
#include "storage/file_download.h"
#include "storage/download_manager_mtproto.h"
#include "storage/file_upload.h"
@ -56,6 +57,7 @@ Session::Session(
, _data(std::make_unique<Data::Session>(this))
, _user(_data->processUser(user))
, _emojiStickersPack(std::make_unique<Stickers::EmojiPack>(this))
, _diceStickersPack(std::make_unique<Stickers::DicePack>(this))
, _changelogs(Core::Changelogs::Create(this))
, _supportHelper(Support::Helper::Create(this)) {
Core::App().passcodeLockChanges(

View file

@ -46,6 +46,7 @@ class Instance;
namespace Stickers {
class EmojiPack;
class DicePack;
} // namespace Stickers;
namespace Core {
@ -89,9 +90,12 @@ public:
[[nodiscard]] Storage::Facade &storage() {
return *_storage;
}
[[nodiscard]] Stickers::EmojiPack &emojiStickersPack() {
[[nodiscard]] Stickers::EmojiPack &emojiStickersPack() const {
return *_emojiStickersPack;
}
[[nodiscard]] Stickers::DicePack &diceStickersPack() const {
return *_diceStickersPack;
}
[[nodiscard]] base::Observable<void> &downloaderTaskFinished();
@ -157,6 +161,7 @@ private:
// _emojiStickersPack depends on _data.
const std::unique_ptr<Stickers::EmojiPack> _emojiStickersPack;
const std::unique_ptr<Stickers::DicePack> _diceStickersPack;
// _changelogs depends on _data, subscribes on chats loading event.
const std::unique_ptr<Core::Changelogs> _changelogs;

View file

@ -967,6 +967,10 @@ void FileLoadTask::finish() {
}
}
FileLoadResult *FileLoadTask::peekResult() const {
return _result.get();
}
void FileLoadTask::removeFromAlbum() {
if (!_album) {
return;

View file

@ -292,6 +292,8 @@ public:
void process();
void finish();
FileLoadResult *peekResult() const;
private:
static bool CheckForSong(
const QString &filepath,