mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 21:27:07 +02:00
Make nice paid reaction toast.
This commit is contained in:
parent
e89f2b55e8
commit
52a199a362
3 changed files with 196 additions and 59 deletions
|
@ -14,25 +14,126 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/history_item.h"
|
||||
//#include "main/main_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/numbers_animation.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/toast/toast_widget.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
//#include "boxes/sticker_set_box.h"
|
||||
//#include "boxes/premium_preview_box.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
//#include "window/window_session_controller.h"
|
||||
//#include "settings/settings_premium.h"
|
||||
//#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumToastDuration = 5 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] not_null<Ui::AbstractButton*> MakeUndoButton(
|
||||
not_null<QWidget*> parent,
|
||||
int width,
|
||||
const QString &text,
|
||||
rpl::producer<crl::time> finish,
|
||||
crl::time total,
|
||||
Fn<void()> click,
|
||||
Fn<void()> timeout) {
|
||||
const auto result = Ui::CreateChild<Ui::AbstractButton>(parent);
|
||||
result->setClickedCallback(std::move(click));
|
||||
|
||||
struct State {
|
||||
explicit State(not_null<QWidget*> button)
|
||||
: countdown(
|
||||
st::toastUndoFont,
|
||||
[=] { button->update(); }) {
|
||||
}
|
||||
|
||||
Ui::NumbersAnimation countdown;
|
||||
crl::time finish = 0;
|
||||
int secondsLeft = 0;
|
||||
Ui::Animations::Basic animation;
|
||||
Fn<void()> update;
|
||||
base::Timer timer;
|
||||
};
|
||||
const auto state = result->lifetime().make_state<State>(result);
|
||||
const auto updateLeft = [=] {
|
||||
const auto now = crl::now();
|
||||
const auto left = state->finish - now;
|
||||
if (left > 0) {
|
||||
const auto seconds = int((left + 999) / 1000);
|
||||
if (state->secondsLeft != seconds) {
|
||||
state->secondsLeft = seconds;
|
||||
state->countdown.setText(QString::number(seconds), seconds);
|
||||
}
|
||||
state->timer.callOnce((left % 1000) + 1);
|
||||
} else {
|
||||
state->animation.stop();
|
||||
state->timer.cancel();
|
||||
timeout();
|
||||
}
|
||||
if (anim::Disabled()) {
|
||||
}
|
||||
};
|
||||
state->update = [=] {
|
||||
if (anim::Disabled()) {
|
||||
state->animation.stop();
|
||||
} else {
|
||||
if (!state->animation.animating()) {
|
||||
state->animation.start();
|
||||
}
|
||||
state->timer.cancel();
|
||||
}
|
||||
updateLeft();
|
||||
result->update();
|
||||
};
|
||||
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
|
||||
const auto font = st::historyPremiumViewSet.font;
|
||||
const auto top = (result->height() - font->height) / 2;
|
||||
auto pen = st::historyPremiumViewSet.textFg->p;
|
||||
p.setPen(pen);
|
||||
p.setFont(font);
|
||||
p.drawText(0, top + font->ascent, text);
|
||||
|
||||
const auto inner = QRect(
|
||||
width - st::toastUndoSkip - st::toastUndoDiameter,
|
||||
(result->height() - st::toastUndoDiameter) / 2,
|
||||
st::toastUndoDiameter,
|
||||
st::toastUndoDiameter);
|
||||
p.setFont(st::toastUndoFont);
|
||||
state->countdown.paint(
|
||||
p,
|
||||
inner.x() + (inner.width() - state->countdown.countWidth()) / 2,
|
||||
inner.y() + (inner.height() - st::toastUndoFont->height) / 2,
|
||||
width);
|
||||
|
||||
const auto progress = (state->finish - crl::now()) / float64(total);
|
||||
const auto len = int(base::SafeRound(arc::kFullLength * progress));
|
||||
if (len > 0) {
|
||||
const auto from = arc::kFullLength / 4;
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
pen.setWidthF(st::toastUndoStroke);
|
||||
p.setPen(pen);
|
||||
p.drawArc(inner, from, len);
|
||||
}
|
||||
}, result->lifetime());
|
||||
result->resize(width, st::historyPremiumViewSet.height);
|
||||
|
||||
std::move(finish) | rpl::start_with_next([=](crl::time value) {
|
||||
state->finish = value;
|
||||
state->update();
|
||||
}, result->lifetime());
|
||||
state->animation.init(state->update);
|
||||
state->timer.setCallback(state->update);
|
||||
state->update();
|
||||
|
||||
result->show();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PaidReactionToast::PaidReactionToast(
|
||||
|
@ -60,30 +161,32 @@ PaidReactionToast::~PaidReactionToast() {
|
|||
}
|
||||
}
|
||||
|
||||
void PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) {
|
||||
bool PaidReactionToast::maybeShowFor(not_null<HistoryItem*> item) {
|
||||
const auto count = item->reactionsPaidScheduled();
|
||||
const auto at = _owner->reactions().sendingScheduledPaidAt(item);
|
||||
if (!count || !at) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const auto left = at - crl::now();
|
||||
const auto total = Data::Reactions::ScheduledPaidDelay();
|
||||
const auto ignore = total % 1000;
|
||||
if (left > ignore) {
|
||||
showFor(item->fullId(), count, left - ignore, total);
|
||||
if (at <= crl::now() + ignore) {
|
||||
return false;
|
||||
}
|
||||
showFor(item->fullId(), count, at - ignore, total);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PaidReactionToast::showFor(
|
||||
FullMsgId itemId,
|
||||
int count,
|
||||
crl::time left,
|
||||
crl::time finish,
|
||||
crl::time total) {
|
||||
const auto old = _weak.get();
|
||||
const auto i = ranges::find(_stack, itemId);
|
||||
if (i != end(_stack)) {
|
||||
if (old && i + 1 == end(_stack)) {
|
||||
update(old, count, left, total);
|
||||
_count = count;
|
||||
_timeFinish = finish;
|
||||
return;
|
||||
}
|
||||
_stack.erase(i);
|
||||
|
@ -93,28 +196,47 @@ void PaidReactionToast::showFor(
|
|||
clearHiddenHiding();
|
||||
if (old) {
|
||||
old->hideAnimated();
|
||||
_hiding.push_back(_weak);
|
||||
_hiding.push_back(base::take(_weak));
|
||||
}
|
||||
const auto text = tr::lng_paid_react_toast_title(
|
||||
tr::now,
|
||||
Ui::Text::Bold
|
||||
).append('\n').append(tr::lng_paid_react_toast_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
Ui::Text::RichLangValue
|
||||
));
|
||||
_st = st::historyPremiumToast;
|
||||
const auto skip = _st.padding.top();
|
||||
const auto size = _st.style.font->height * 2;
|
||||
const auto undo = tr::lng_paid_react_undo(tr::now);
|
||||
_st.padding.setLeft(skip + size + skip);
|
||||
_st.padding.setRight(st::historyPremiumViewSet.font->width(undo)
|
||||
- st::historyPremiumViewSet.width);
|
||||
_count.reset();
|
||||
_timeFinish.reset();
|
||||
_count = count;
|
||||
_timeFinish = finish;
|
||||
auto text = rpl::combine(
|
||||
tr::lng_paid_react_toast_title(Ui::Text::Bold),
|
||||
tr::lng_paid_react_toast_text(
|
||||
lt_count_decimal,
|
||||
_count.value() | tr::to_count(),
|
||||
Ui::Text::RichLangValue)
|
||||
) | rpl::map([](TextWithEntities &&title, TextWithEntities &&body) {
|
||||
title.append('\n').append(body);
|
||||
return std::move(title);
|
||||
});
|
||||
const auto &st = st::historyPremiumToast;
|
||||
const auto skip = st.padding.top();
|
||||
const auto size = st.style.font->height * 2;
|
||||
const auto undoText = tr::lng_paid_react_undo(tr::now);
|
||||
|
||||
auto content = object_ptr<Ui::RpWidget>((QWidget*)nullptr);
|
||||
const auto child = Ui::CreateChild<Ui::FlatLabel>(
|
||||
content.data(),
|
||||
std::move(text),
|
||||
st::paidReactToastLabel);
|
||||
content->resize(child->naturalWidth() * 1.5, child->height());
|
||||
child->show();
|
||||
|
||||
const auto leftSkip = skip + size + skip - st.padding.left();
|
||||
const auto undoFont = st::historyPremiumViewSet.font;
|
||||
|
||||
const auto rightSkip = undoFont->width(undoText)
|
||||
+ st::toastUndoSpace
|
||||
+ st::toastUndoDiameter
|
||||
+ st::toastUndoSkip
|
||||
- st.padding.right();
|
||||
_weak = Ui::Toast::Show(_parent, Ui::Toast::Config{
|
||||
.text = text,
|
||||
.st = &_st,
|
||||
.content = std::move(content),
|
||||
.padding = rpl::single(QMargins(leftSkip, 0, rightSkip, 0)),
|
||||
.st = &st,
|
||||
.attach = RectPart::Top,
|
||||
.acceptinput = true,
|
||||
.infinite = true,
|
||||
|
@ -124,18 +246,41 @@ void PaidReactionToast::showFor(
|
|||
return;
|
||||
}
|
||||
const auto widget = strong->widget();
|
||||
const auto hideToast = [weak = _weak] {
|
||||
const auto hideToast = [=, weak = _weak] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->hideAnimated();
|
||||
if (strong == _weak.get()) {
|
||||
_stack.erase(ranges::remove(_stack, itemId), end(_stack));
|
||||
|
||||
_hiding.push_back(base::take(_weak));
|
||||
strong->hideAnimated();
|
||||
|
||||
while (!_stack.empty()) {
|
||||
if (const auto item = _owner->message(_stack.back())) {
|
||||
if (maybeShowFor(item)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const auto button = Ui::CreateChild<Ui::RoundButton>(
|
||||
const auto undo = [=] {
|
||||
if (const auto item = _owner->message(itemId)) {
|
||||
_owner->reactions().undoScheduledPaid(item);
|
||||
}
|
||||
hideToast();
|
||||
};
|
||||
const auto button = MakeUndoButton(
|
||||
widget.get(),
|
||||
rpl::single(undo),
|
||||
st::historyPremiumViewSet);
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
button->show();
|
||||
rightSkip + st.padding.right(),
|
||||
undoText,
|
||||
_timeFinish.value(),
|
||||
total,
|
||||
undo,
|
||||
hideToast);
|
||||
|
||||
rpl::combine(
|
||||
widget->sizeValue(),
|
||||
button->sizeValue()
|
||||
|
@ -151,19 +296,6 @@ void PaidReactionToast::showFor(
|
|||
preview->show();
|
||||
|
||||
setupLottiePreview(preview, size);
|
||||
button->setClickedCallback([=] {
|
||||
if (const auto item = _owner->message(itemId)) {
|
||||
_owner->reactions().undoScheduledPaid(item);
|
||||
}
|
||||
hideToast();
|
||||
});
|
||||
}
|
||||
|
||||
void PaidReactionToast::update(
|
||||
not_null<Ui::Toast::Instance*> toast,
|
||||
int count,
|
||||
crl::time left,
|
||||
crl::time total) {
|
||||
}
|
||||
|
||||
void PaidReactionToast::clearHiddenHiding() {
|
||||
|
|
|
@ -36,17 +36,12 @@ public:
|
|||
~PaidReactionToast();
|
||||
|
||||
private:
|
||||
void maybeShowFor(not_null<HistoryItem*> item);
|
||||
bool maybeShowFor(not_null<HistoryItem*> item);
|
||||
void showFor(
|
||||
FullMsgId itemId,
|
||||
int count,
|
||||
crl::time left,
|
||||
crl::time total);
|
||||
void update(
|
||||
not_null<Ui::Toast::Instance*> toast,
|
||||
int count,
|
||||
crl::time left,
|
||||
crl::time total);
|
||||
|
||||
void setupLottiePreview(not_null<Ui::RpWidget*> widget, int size);
|
||||
void clearHiddenHiding();
|
||||
|
@ -55,9 +50,10 @@ private:
|
|||
const not_null<Data::Session*> _owner;
|
||||
const rpl::variable<int> _topOffset;
|
||||
|
||||
style::Toast _st;
|
||||
base::weak_ptr<Ui::Toast::Instance> _weak;
|
||||
std::vector<base::weak_ptr<Ui::Toast::Instance>> _hiding;
|
||||
rpl::variable<int> _count;
|
||||
rpl::variable<crl::time> _timeFinish;
|
||||
|
||||
std::vector<FullMsgId> _stack;
|
||||
|
||||
|
|
|
@ -394,3 +394,12 @@ paidReactTopMargin: margins(0px, 12px, 0px, 11px);
|
|||
paidReactTopUserpic: 42px;
|
||||
paidReactTopNameSkip: 47px;
|
||||
paidReactTopBadgeSkip: 32px;
|
||||
paidReactToastLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: toastFg;
|
||||
palette: defaultToastPalette;
|
||||
}
|
||||
toastUndoStroke: 2px;
|
||||
toastUndoSpace: 8px;
|
||||
toastUndoDiameter: 20px;
|
||||
toastUndoSkip: 20px;
|
||||
toastUndoFont: font(12px bold);
|
||||
|
|
Loading…
Add table
Reference in a new issue