2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-22 18:27:17 +00:00
tdesktop/Telegram/SourceFiles/dialogs/ui/posts_search_intro.cpp
2025-08-13 11:18:34 +04:00

255 lines
7.1 KiB
C++

/*
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 "dialogs/ui/posts_search_intro.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "lang/lang_keys.h"
#include "ui/controls/button_labels.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_credits.h"
#include "styles/style_dialogs.h"
namespace Dialogs {
namespace {
[[nodiscard]] rpl::producer<QString> FormatCountdownTill(TimeId when) {
return rpl::single(rpl::empty) | rpl::then(
base::timer_each(1000)
) | rpl::map([=] {
const auto now = base::unixtime::now();
const auto delta = std::max(when - now, 0);
const auto hours = delta / 3600;
const auto minutes = (delta % 3600) / 60;
const auto seconds = delta % 60;
constexpr auto kZero = QChar('0');
return (hours > 0)
? u"%1:%2:%3"_q
.arg(hours)
.arg(minutes, 2, 10, kZero)
.arg(seconds, 2, 10, kZero)
: u"%1:%2"_q
.arg(minutes)
.arg(seconds, 2, 10, kZero);
});
}
void SetSearchButtonLabel(
not_null<Ui::RpWidget*> button,
rpl::producer<TextWithEntities> text) {
const auto left = &st::postsSearchIcon;
const auto leftPadding = st::postsSearchIconPadding;
const auto right = &st::postsSearchArrow;
const auto rightPadding = st::postsSearchArrowPadding;
const auto leftSkip = left->size().grownBy(leftPadding).width();
const auto rightSkip = right->size().grownBy(rightPadding).width();
struct State {
State() : linkFg([] {
auto copy = st::windowFgActive->c;
copy.setAlphaF(0.6);
return copy;
}), st(st::resaleButtonTitle) {
}
style::complex_color linkFg;
style::FlatLabel st;
};
auto lifetime = rpl::lifetime();
const auto state = lifetime.make_state<State>();
state->st.palette.linkFg = state->linkFg.color();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button,
rpl::duplicate(text),
state->st);
label->lifetime().add(std::move(lifetime));
label->show();
const auto icons = Ui::CreateChild<Ui::RpWidget>(button);
icons->show();
rpl::combine(
button->sizeValue(),
std::move(text)
) | rpl::start_with_next([=](QSize size, const auto &) {
icons->setGeometry(QRect(QPoint(), size));
const auto available = size.width() - leftSkip - rightSkip;
if (available <= 0) {
return;
}
const auto width = std::min(available, label->textMaxWidth());
label->resizeToWidth(width);
const auto full = leftSkip + width + rightSkip;
const auto x = (size.width() - full) / 2;
const auto y = (size.height() - label->height()) / 2;
label->moveToLeft(x + leftSkip, y, size.width());
}, icons->lifetime());
icons->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(icons);
left->paint(
p,
label->x() - leftSkip + leftPadding.left(),
label->y() + leftPadding.top(),
icons->width());
right->paint(
p,
label->x() + label->width() + rightPadding.left(),
label->y() + rightPadding.top(),
icons->width());
}, icons->lifetime());
}
} // namespace
PostsSearchIntro::PostsSearchIntro(
not_null<Ui::RpWidget*> parent,
PostsSearchIntroState state)
: RpWidget(parent)
, _state(std::move(state))
, _content(std::make_unique<Ui::VerticalLayout>(this)) {
setup();
}
PostsSearchIntro::~PostsSearchIntro() = default;
void PostsSearchIntro::update(PostsSearchIntroState state) {
_state = std::move(state);
}
rpl::producer<int> PostsSearchIntro::searchWithStars() const {
return _button->clicks() | rpl::map([=] {
const auto &now = _state.current();
return (now.needsPremium || now.freeSearchesLeft)
? 0
: int(now.starsPerPaidSearch);
});
}
void PostsSearchIntro::setup() {
auto title = _state.value(
) | rpl::map([](const PostsSearchIntroState &state) {
return (state.needsPremium || state.freeSearchesLeft > 0)
? tr::lng_posts_title()
: tr::lng_posts_limit_reached();
}) | rpl::flatten_latest();
auto subtitle = _state.value(
) | rpl::map([](const PostsSearchIntroState &state) {
return (state.needsPremium || state.freeSearchesLeft > 0)
? tr::lng_posts_start()
: tr::lng_posts_limit_about(
lt_count,
rpl::single(state.freeSearchesPerDay * 1.));
}) | rpl::flatten_latest();
auto footer = _state.value(
) | rpl::map([](const PostsSearchIntroState &state)
-> rpl::producer<QString> {
if (state.needsPremium) {
return tr::lng_posts_need_subscribe();
} else if (state.freeSearchesLeft > 0) {
return tr::lng_posts_remaining(
lt_count,
rpl::single(state.freeSearchesLeft * 1.));
} else {
return rpl::single(QString());
}
}) | rpl::flatten_latest();
_title = _content->add(
object_ptr<Ui::FlatLabel>(
_content.get(),
std::move(title),
st::postsSearchIntroTitle),
st::postsSearchIntroTitleMargin,
style::al_top);
_title->setTryMakeSimilarLines(true);
_subtitle = _content->add(
object_ptr<Ui::FlatLabel>(
_content.get(),
std::move(subtitle),
st::postsSearchIntroSubtitle),
st::postsSearchIntroSubtitleMargin,
style::al_top);
_subtitle->setTryMakeSimilarLines(true);
_button = _content->add(
object_ptr<Ui::RoundButton>(
_content.get(),
rpl::single(QString()),
st::postsSearchIntroButton),
{},
style::al_top);
_button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
_footer = _content->add(
object_ptr<Ui::FlatLabel>(
_content.get(),
std::move(footer),
st::postsSearchIntroFooter),
st::postsSearchIntroFooterMargin,
style::al_top);
_footer->setTryMakeSimilarLines(true);
_state.value(
) | rpl::start_with_next([=](const PostsSearchIntroState &state) {
if (state.query.trimmed().isEmpty()) {
_button->resize(_button->width(), 0);
_content->resizeToWidth(width());
return;
}
auto copy = _button->children();
for (const auto child : copy) {
delete child;
}
if (state.needsPremium) {
_button->setText(tr::lng_posts_subscribe());
} else if (state.freeSearchesLeft > 0) {
_button->setText(rpl::single(QString()));
SetSearchButtonLabel(_button, tr::lng_posts_search_button(
lt_query,
rpl::single(Ui::Text::Colorized(state.query.trimmed())),
Ui::Text::WithEntities));
} else {
_button->setText(rpl::single(QString()));
Ui::SetButtonTwoLabels(
_button,
tr::lng_posts_limit_search_paid(
lt_cost,
rpl::single(Ui::Text::IconEmoji(
&st::starIconEmoji
).append(
Lang::FormatCountDecimal(state.starsPerPaidSearch))),
Ui::Text::WithEntities),
tr::lng_posts_limit_unlocks(
lt_duration,
FormatCountdownTill(
state.nextFreeSearchTime
) | Ui::Text::ToWithEntities(),
Ui::Text::WithEntities),
st::resaleButtonTitle,
st::resaleButtonSubtitle);
}
_button->resize(_button->width(), st::postsSearchIntroButton.height);
_content->resizeToWidth(width());
}, _button->lifetime());
}
void PostsSearchIntro::resizeEvent(QResizeEvent *e) {
_content->resizeToWidth(width());
const auto top = std::max(0, (height() - _content->height()) / 3);
_content->move(0, top);
}
} // namespace Dialogs