2018-12-26 18:00:08 +04:00
|
|
|
/*
|
|
|
|
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/emoji_sets_manager.h"
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
#include "mtproto/dedicated_file_loader.h"
|
2018-12-26 18:00:08 +04:00
|
|
|
#include "ui/wrap/vertical_layout.h"
|
|
|
|
#include "ui/wrap/fade_wrap.h"
|
|
|
|
#include "ui/widgets/buttons.h"
|
|
|
|
#include "ui/widgets/labels.h"
|
|
|
|
#include "ui/effects/radial_animation.h"
|
|
|
|
#include "ui/emoji_config.h"
|
|
|
|
#include "lang/lang_keys.h"
|
2018-12-26 21:05:06 +04:00
|
|
|
#include "base/zlib_help.h"
|
2018-12-26 18:00:08 +04:00
|
|
|
#include "layout.h"
|
2018-12-26 21:05:06 +04:00
|
|
|
#include "messenger.h"
|
|
|
|
#include "mainwidget.h"
|
2018-12-26 18:00:08 +04:00
|
|
|
#include "styles/style_boxes.h"
|
|
|
|
#include "styles/style_chat_helpers.h"
|
|
|
|
|
|
|
|
namespace Ui {
|
|
|
|
namespace Emoji {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
struct Available {
|
|
|
|
int size = 0;
|
|
|
|
|
|
|
|
inline bool operator<(const Available &other) const {
|
|
|
|
return size < other.size;
|
|
|
|
}
|
|
|
|
inline bool operator==(const Available &other) const {
|
|
|
|
return size == other.size;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
struct Ready {
|
|
|
|
inline bool operator<(const Ready &other) const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
inline bool operator==(const Ready &other) const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
struct Active {
|
|
|
|
inline bool operator<(const Active &other) const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
inline bool operator==(const Active &other) const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
2018-12-26 21:05:06 +04:00
|
|
|
using Loading = MTP::DedicatedLoader::Progress;
|
2018-12-26 18:00:08 +04:00
|
|
|
struct Failed {
|
|
|
|
inline bool operator<(const Failed &other) const {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
inline bool operator==(const Failed &other) const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
using SetState = base::variant<
|
|
|
|
Available,
|
|
|
|
Ready,
|
|
|
|
Active,
|
|
|
|
Loading,
|
|
|
|
Failed>;
|
|
|
|
|
|
|
|
class Loader : public QObject {
|
|
|
|
public:
|
2018-12-26 21:05:06 +04:00
|
|
|
Loader(QObject *parent, int id);
|
2018-12-26 18:00:08 +04:00
|
|
|
|
|
|
|
int id() const;
|
|
|
|
|
|
|
|
rpl::producer<SetState> state() const;
|
2018-12-27 12:01:03 +04:00
|
|
|
void destroy();
|
2018-12-26 18:00:08 +04:00
|
|
|
|
|
|
|
private:
|
2018-12-26 21:05:06 +04:00
|
|
|
void setImplementation(std::unique_ptr<MTP::DedicatedLoader> loader);
|
|
|
|
void unpack(const QString &path);
|
|
|
|
void finalize(const QString &path);
|
|
|
|
void fail();
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
int _id = 0;
|
|
|
|
int _size = 0;
|
|
|
|
rpl::variable<SetState> _state;
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
MTP::WeakInstance _mtproto;
|
|
|
|
std::unique_ptr<MTP::DedicatedLoader> _implementation;
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
class Inner : public Ui::RpWidget {
|
|
|
|
public:
|
|
|
|
Inner(QWidget *parent);
|
|
|
|
|
|
|
|
private:
|
|
|
|
void setupContent();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
class Row : public Ui::RippleButton {
|
|
|
|
public:
|
|
|
|
Row(QWidget *widget, const Set &set);
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
|
2018-12-27 13:37:11 +04:00
|
|
|
void onStateChanged(State was, StateChangeSource source) override;
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
private:
|
2018-12-27 13:37:11 +04:00
|
|
|
[[nodiscard]] bool showOver() const;
|
|
|
|
[[nodiscard]] bool showOver(State state) const;
|
2018-12-26 18:00:08 +04:00
|
|
|
void setupContent(const Set &set);
|
|
|
|
void setupCheck();
|
|
|
|
void setupLabels(const Set &set);
|
2018-12-27 13:37:11 +04:00
|
|
|
void setupAnimation();
|
|
|
|
void updateAnimation(TimeMs ms);
|
2018-12-26 18:00:08 +04:00
|
|
|
void setupHandler();
|
2018-12-26 21:05:06 +04:00
|
|
|
void load();
|
2018-12-26 18:00:08 +04:00
|
|
|
|
2018-12-27 13:37:11 +04:00
|
|
|
void step_radial(TimeMs ms, bool timer);
|
|
|
|
|
|
|
|
[[nodiscard]] QRect rightPartRect(QSize size) const;
|
|
|
|
[[nodiscard]] QRect radialRect() const;
|
|
|
|
[[nodiscard]] QRect checkRect() const;
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
int _id = 0;
|
2018-12-27 12:01:03 +04:00
|
|
|
bool _switching = false;
|
2018-12-26 18:00:08 +04:00
|
|
|
rpl::variable<SetState> _state;
|
2018-12-27 13:37:11 +04:00
|
|
|
Ui::FlatLabel *_status = nullptr;
|
2018-12-26 18:00:08 +04:00
|
|
|
std::unique_ptr<Ui::RadialAnimation> _loading;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2018-12-27 12:01:03 +04:00
|
|
|
base::unique_qptr<Loader> GlobalLoader;
|
2018-12-26 18:00:08 +04:00
|
|
|
rpl::event_stream<Loader*> GlobalLoaderValues;
|
|
|
|
|
2018-12-27 12:01:03 +04:00
|
|
|
void SetGlobalLoader(base::unique_qptr<Loader> loader) {
|
|
|
|
GlobalLoader = std::move(loader);
|
|
|
|
GlobalLoaderValues.fire(GlobalLoader.get());
|
|
|
|
}
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
int GetDownloadSize(int id) {
|
|
|
|
const auto sets = Sets();
|
|
|
|
return ranges::find(sets, id, &Set::id)->size;
|
|
|
|
}
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
MTP::DedicatedLoader::Location GetDownloadLocation(int id) {
|
|
|
|
constexpr auto kUsername = "tdhbcfiles";
|
|
|
|
const auto sets = Sets();
|
|
|
|
const auto i = ranges::find(sets, id, &Set::id);
|
|
|
|
return MTP::DedicatedLoader::Location{ kUsername, i->postId };
|
|
|
|
}
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
SetState ComputeState(int id) {
|
|
|
|
if (id == CurrentSetId()) {
|
|
|
|
return Active();
|
|
|
|
} else if (SetIsReady(id)) {
|
|
|
|
return Ready();
|
|
|
|
}
|
|
|
|
return Available{ GetDownloadSize(id) };
|
|
|
|
}
|
|
|
|
|
|
|
|
QString StateDescription(const SetState &state) {
|
|
|
|
return state.match([](const Available &data) {
|
|
|
|
return lng_emoji_set_available(lt_size, formatSizeText(data.size));
|
|
|
|
}, [](const Ready &data) -> QString {
|
|
|
|
return lang(lng_emoji_set_ready);
|
|
|
|
}, [](const Active &data) -> QString {
|
|
|
|
return lang(lng_emoji_set_active);
|
|
|
|
}, [](const Loading &data) {
|
|
|
|
return lng_emoji_set_loading(
|
|
|
|
lt_progress,
|
2018-12-26 21:05:06 +04:00
|
|
|
formatDownloadText(data.already, data.size));
|
2018-12-26 18:00:08 +04:00
|
|
|
}, [](const Failed &data) {
|
|
|
|
return lang(lng_attach_failed);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
QByteArray ReadFinalFile(const QString &path) {
|
|
|
|
constexpr auto kMaxZipSize = 10 * 1024 * 1024;
|
|
|
|
auto file = QFile(path);
|
|
|
|
if (file.size() > kMaxZipSize || !file.open(QIODevice::ReadOnly)) {
|
|
|
|
return QByteArray();
|
|
|
|
}
|
|
|
|
return file.readAll();
|
|
|
|
}
|
|
|
|
|
2018-12-27 12:01:03 +04:00
|
|
|
bool ExtractZipFile(zlib::FileToRead &zip, const QString path) {
|
|
|
|
constexpr auto kMaxSize = 10 * 1024 * 1024;
|
|
|
|
const auto content = zip.readCurrentFileContent(kMaxSize);
|
|
|
|
if (content.isEmpty() || zip.error() != UNZ_OK) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto file = QFile(path);
|
|
|
|
return file.open(QIODevice::WriteOnly)
|
|
|
|
&& (file.write(content) == content.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GoodSetPartName(const QString &name) {
|
|
|
|
return (name == qstr("config.json"))
|
|
|
|
|| (name.startsWith(qstr("emoji_"))
|
|
|
|
&& name.endsWith(qstr(".webp")));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UnpackSet(const QString &path, const QString &folder) {
|
|
|
|
const auto bytes = ReadFinalFile(path);
|
|
|
|
if (bytes.isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto zip = zlib::FileToRead(bytes);
|
|
|
|
if (zip.goToFirstFile() != UNZ_OK) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
const auto name = zip.getCurrentFileName();
|
|
|
|
const auto path = folder + '/' + name;
|
|
|
|
if (GoodSetPartName(name) && !ExtractZipFile(zip, path)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto jump = zip.goToNextFile();
|
|
|
|
if (jump == UNZ_END_OF_LIST_OF_FILE) {
|
|
|
|
break;
|
|
|
|
} else if (jump != UNZ_OK) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} while (true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
Loader::Loader(QObject *parent, int id)
|
|
|
|
: QObject(parent)
|
|
|
|
, _id(id)
|
2018-12-26 18:00:08 +04:00
|
|
|
, _size(GetDownloadSize(_id))
|
2018-12-26 21:05:06 +04:00
|
|
|
, _state(Loading{ 0, _size })
|
|
|
|
, _mtproto(Messenger::Instance().mtp()) {
|
|
|
|
const auto ready = [=](std::unique_ptr<MTP::DedicatedLoader> loader) {
|
|
|
|
if (loader) {
|
|
|
|
setImplementation(std::move(loader));
|
|
|
|
} else {
|
|
|
|
fail();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const auto location = GetDownloadLocation(id);
|
|
|
|
const auto folder = internal::SetDataPath(id);
|
|
|
|
MTP::StartDedicatedLoader(&_mtproto, location, folder, ready);
|
2018-12-26 18:00:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int Loader::id() const {
|
|
|
|
return _id;
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<SetState> Loader::state() const {
|
|
|
|
return _state.value();
|
|
|
|
}
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
void Loader::setImplementation(
|
|
|
|
std::unique_ptr<MTP::DedicatedLoader> loader) {
|
|
|
|
_implementation = std::move(loader);
|
|
|
|
auto convert = [](auto value) {
|
|
|
|
return SetState(value);
|
|
|
|
};
|
|
|
|
_state = _implementation->progress(
|
|
|
|
) | rpl::map([](const Loading &state) {
|
|
|
|
return SetState(state);
|
|
|
|
});
|
|
|
|
_implementation->failed(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
fail();
|
|
|
|
}, _implementation->lifetime());
|
|
|
|
|
|
|
|
_implementation->ready(
|
|
|
|
) | rpl::start_with_next([=](const QString &filepath) {
|
|
|
|
unpack(filepath);
|
|
|
|
}, _implementation->lifetime());
|
|
|
|
|
|
|
|
QDir(internal::SetDataPath(_id)).removeRecursively();
|
|
|
|
_implementation->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Loader::unpack(const QString &path) {
|
2018-12-27 12:01:03 +04:00
|
|
|
const auto folder = internal::SetDataPath(_id);
|
|
|
|
const auto weak = make_weak(this);
|
|
|
|
crl::async([=] {
|
|
|
|
if (UnpackSet(path, folder)) {
|
|
|
|
QFile(path).remove();
|
|
|
|
SwitchToSet(_id, crl::guard(weak, [=](bool success) {
|
|
|
|
if (success) {
|
|
|
|
destroy();
|
|
|
|
} else {
|
|
|
|
fail();
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
crl::on_main(weak, [=] {
|
|
|
|
fail();
|
|
|
|
});
|
2018-12-26 21:05:06 +04:00
|
|
|
}
|
2018-12-27 12:01:03 +04:00
|
|
|
});
|
2018-12-26 21:05:06 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void Loader::finalize(const QString &path) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void Loader::fail() {
|
|
|
|
_state = Failed();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Loader::destroy() {
|
2018-12-27 12:01:03 +04:00
|
|
|
Expects(GlobalLoader == this);
|
2018-12-26 21:05:06 +04:00
|
|
|
|
2018-12-27 12:01:03 +04:00
|
|
|
SetGlobalLoader(nullptr);
|
2018-12-26 21:05:06 +04:00
|
|
|
}
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
Inner::Inner(QWidget *parent) : RpWidget(parent) {
|
|
|
|
setupContent();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Inner::setupContent() {
|
|
|
|
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
|
|
|
|
|
|
|
const auto sets = Sets();
|
|
|
|
for (const auto &set : sets) {
|
|
|
|
content->add(object_ptr<Row>(content, set));
|
|
|
|
}
|
|
|
|
|
|
|
|
content->resizeToWidth(st::boxWidth);
|
|
|
|
Ui::ResizeFitChild(this, content);
|
|
|
|
}
|
|
|
|
|
|
|
|
Row::Row(QWidget *widget, const Set &set)
|
|
|
|
: RippleButton(widget, st::contactsRipple)
|
|
|
|
, _id(set.id)
|
|
|
|
, _state(Available{ set.size }) {
|
|
|
|
setupContent(set);
|
|
|
|
setupHandler();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Row::paintEvent(QPaintEvent *e) {
|
|
|
|
Painter p(this);
|
|
|
|
|
2018-12-27 13:37:11 +04:00
|
|
|
const auto over = showOver();
|
2018-12-26 18:00:08 +04:00
|
|
|
const auto bg = over ? st::windowBgOver : st::windowBg;
|
|
|
|
p.fillRect(rect(), bg);
|
|
|
|
|
|
|
|
const auto ms = getms();
|
|
|
|
paintRipple(p, 0, 0, ms);
|
2018-12-27 13:37:11 +04:00
|
|
|
|
|
|
|
updateAnimation(ms);
|
|
|
|
if (_loading) {
|
|
|
|
_loading->draw(
|
|
|
|
p,
|
|
|
|
radialRect(),
|
|
|
|
st::manageEmojiRadialThickness,
|
|
|
|
over ? st::windowSubTextFgOver : st::windowSubTextFg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect Row::rightPartRect(QSize size) const {
|
|
|
|
const auto x = width()
|
|
|
|
- (st::contactsPadding.right()
|
|
|
|
+ st::contactsCheckPosition.x()
|
|
|
|
+ st::manageEmojiCheck.width)
|
|
|
|
+ (st::manageEmojiCheck.width / 2);
|
|
|
|
const auto y = st::contactsPadding.top()
|
|
|
|
+ (st::contactsPhotoSize - st::manageEmojiCheck.height) / 2
|
|
|
|
+ (st::manageEmojiCheck.height / 2);
|
|
|
|
return QRect(
|
|
|
|
QPoint(x, y) - QPoint(size.width() / 2, size.height() / 2),
|
|
|
|
size);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect Row::radialRect() const {
|
|
|
|
return rightPartRect(st::manageEmojiRadialSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
QRect Row::checkRect() const {
|
|
|
|
return rightPartRect(QSize(
|
|
|
|
st::manageEmojiCheck.width,
|
|
|
|
st::manageEmojiCheck.height));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Row::showOver(State state) const {
|
|
|
|
return (!(state & StateFlag::Disabled))
|
|
|
|
&& (state & (StateFlag::Over | StateFlag::Down));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Row::showOver() const {
|
|
|
|
return showOver(state());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Row::onStateChanged(State was, StateChangeSource source) {
|
|
|
|
RippleButton::onStateChanged(was, source);
|
|
|
|
const auto over = showOver();
|
|
|
|
if (over != showOver(was)) {
|
|
|
|
_status->setTextColorOverride(over
|
|
|
|
? std::make_optional(st::windowSubTextFgOver->c)
|
|
|
|
: std::nullopt);
|
|
|
|
}
|
2018-12-26 18:00:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void Row::setupContent(const Set &set) {
|
|
|
|
_state = GlobalLoaderValues.events_starting_with(
|
2018-12-27 12:01:03 +04:00
|
|
|
GlobalLoader.get()
|
2018-12-26 18:00:08 +04:00
|
|
|
) | rpl::map([=](Loader *loader) {
|
|
|
|
return (loader && loader->id() == _id)
|
|
|
|
? loader->state()
|
|
|
|
: rpl::single(
|
|
|
|
rpl::empty_value()
|
|
|
|
) | rpl::then(
|
|
|
|
Updated()
|
|
|
|
) | rpl::map([=] {
|
|
|
|
return ComputeState(_id);
|
|
|
|
});
|
|
|
|
}) | rpl::flatten_latest(
|
|
|
|
) | rpl::filter([=](const SetState &state) {
|
|
|
|
return !_state.current().is<Failed>() || !state.is<Available>();
|
|
|
|
});
|
|
|
|
|
|
|
|
setupCheck();
|
|
|
|
setupLabels(set);
|
2018-12-27 13:37:11 +04:00
|
|
|
setupAnimation();
|
2018-12-26 18:00:08 +04:00
|
|
|
|
|
|
|
resize(width(), st::defaultPeerList.item.height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Row::setupHandler() {
|
|
|
|
clicks(
|
|
|
|
) | rpl::filter([=] {
|
|
|
|
const auto &state = _state.current();
|
2018-12-27 12:01:03 +04:00
|
|
|
return !_switching && (state.is<Ready>() || state.is<Available>());
|
2018-12-26 18:00:08 +04:00
|
|
|
}) | rpl::start_with_next([=] {
|
2018-12-26 21:05:06 +04:00
|
|
|
if (_state.current().is<Available>()) {
|
|
|
|
load();
|
|
|
|
return;
|
|
|
|
}
|
2018-12-27 12:01:03 +04:00
|
|
|
_switching = true;
|
|
|
|
SwitchToSet(_id, crl::guard(this, [=](bool success) {
|
|
|
|
_switching = false;
|
|
|
|
if (!success) {
|
2018-12-26 21:05:06 +04:00
|
|
|
load();
|
2018-12-27 12:01:03 +04:00
|
|
|
} else if (GlobalLoader && GlobalLoader->id() == _id) {
|
|
|
|
GlobalLoader->destroy();
|
2018-12-26 18:00:08 +04:00
|
|
|
}
|
2018-12-27 12:01:03 +04:00
|
|
|
}));
|
2018-12-26 18:00:08 +04:00
|
|
|
}, lifetime());
|
|
|
|
|
|
|
|
_state.value(
|
2018-12-26 21:05:06 +04:00
|
|
|
) | rpl::map([=](const SetState &state) {
|
2018-12-26 18:00:08 +04:00
|
|
|
return state.is<Ready>() || state.is<Available>();
|
|
|
|
}) | rpl::start_with_next([=](bool active) {
|
|
|
|
setDisabled(!active);
|
|
|
|
setPointerCursor(active);
|
|
|
|
}, lifetime());
|
|
|
|
}
|
|
|
|
|
2018-12-26 21:05:06 +04:00
|
|
|
void Row::load() {
|
2018-12-27 12:01:03 +04:00
|
|
|
SetGlobalLoader(base::make_unique_q<Loader>(App::main(), _id));
|
2018-12-26 21:05:06 +04:00
|
|
|
}
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
void Row::setupCheck() {
|
|
|
|
using namespace rpl::mappers;
|
|
|
|
|
|
|
|
const auto check = Ui::CreateChild<Ui::FadeWrapScaled<Ui::IconButton>>(
|
|
|
|
this,
|
|
|
|
object_ptr<Ui::IconButton>(
|
|
|
|
this,
|
|
|
|
st::manageEmojiCheck));
|
|
|
|
sizeValue(
|
|
|
|
) | rpl::start_with_next([=](QSize size) {
|
2018-12-27 13:37:11 +04:00
|
|
|
const auto rect = checkRect();
|
|
|
|
check->moveToLeft(rect.x(), rect.y());
|
2018-12-26 18:00:08 +04:00
|
|
|
}, check->lifetime());
|
|
|
|
|
|
|
|
check->toggleOn(_state.value(
|
|
|
|
) | rpl::map(
|
|
|
|
_1 == Active()
|
2018-12-27 13:37:11 +04:00
|
|
|
));
|
2018-12-26 18:00:08 +04:00
|
|
|
|
|
|
|
check->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Row::setupLabels(const Set &set) {
|
|
|
|
using namespace rpl::mappers;
|
|
|
|
|
|
|
|
const auto name = Ui::CreateChild<Ui::FlatLabel>(
|
|
|
|
this,
|
|
|
|
set.name,
|
|
|
|
Ui::FlatLabel::InitType::Simple,
|
|
|
|
st::localStorageRowTitle);
|
2018-12-26 21:05:06 +04:00
|
|
|
name->setAttribute(Qt::WA_TransparentForMouseEvents);
|
2018-12-27 13:37:11 +04:00
|
|
|
_status = Ui::CreateChild<Ui::FlatLabel>(
|
2018-12-26 18:00:08 +04:00
|
|
|
this,
|
|
|
|
_state.value() | rpl::map(StateDescription),
|
|
|
|
st::localStorageRowSize);
|
2018-12-27 13:37:11 +04:00
|
|
|
_status->setAttribute(Qt::WA_TransparentForMouseEvents);
|
2018-12-26 18:00:08 +04:00
|
|
|
|
|
|
|
sizeValue(
|
|
|
|
) | rpl::start_with_next([=](QSize size) {
|
|
|
|
const auto left = st::contactsPadding.left();
|
|
|
|
const auto namey = st::contactsPadding.top()
|
|
|
|
+ st::contactsNameTop;
|
|
|
|
const auto statusy = st::contactsPadding.top()
|
|
|
|
+ st::contactsStatusTop;
|
|
|
|
name->moveToLeft(left, namey);
|
2018-12-27 13:37:11 +04:00
|
|
|
_status->moveToLeft(left, statusy);
|
2018-12-26 18:00:08 +04:00
|
|
|
}, name->lifetime());
|
|
|
|
}
|
|
|
|
|
2018-12-27 13:37:11 +04:00
|
|
|
void Row::step_radial(TimeMs ms, bool timer) {
|
|
|
|
if (timer && !anim::Disabled()) {
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Row::setupAnimation() {
|
|
|
|
_state.value(
|
|
|
|
) | rpl::start_with_next([=](const SetState &state) {
|
|
|
|
update();
|
|
|
|
}, lifetime());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Row::updateAnimation(TimeMs ms) {
|
|
|
|
const auto state = _state.current();
|
|
|
|
if (const auto loading = base::get_if<Loading>(&state)) {
|
|
|
|
const auto progress = (loading->size > 0)
|
|
|
|
? (loading->already / float64(loading->size))
|
|
|
|
: 0.;
|
|
|
|
if (!_loading) {
|
|
|
|
_loading = std::make_unique<Ui::RadialAnimation>(
|
|
|
|
animation(this, &Row::step_radial));
|
|
|
|
_loading->start(progress);
|
|
|
|
} else {
|
|
|
|
_loading->update(progress, false, getms());
|
|
|
|
}
|
|
|
|
} else if (_loading) {
|
|
|
|
_loading->update(state.is<Failed>() ? 0. : 1., true, getms());
|
|
|
|
} else {
|
|
|
|
_loading = nullptr;
|
|
|
|
}
|
|
|
|
if (_loading && !_loading->animating()) {
|
|
|
|
_loading = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-26 18:00:08 +04:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
ManageSetsBox::ManageSetsBox(QWidget*) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void ManageSetsBox::prepare() {
|
|
|
|
const auto inner = setInnerWidget(object_ptr<Inner>(this));
|
|
|
|
|
|
|
|
setTitle(langFactory(lng_emoji_manage_sets));
|
|
|
|
|
|
|
|
addButton(langFactory(lng_close), [=] { closeBox(); });
|
|
|
|
|
|
|
|
setDimensionsToContent(st::boxWidth, inner);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Emoji
|
|
|
|
} // namespace Ui
|