Initial starref setup section implementation.

This commit is contained in:
John Preston 2024-11-28 11:39:05 +04:00
parent a6bfd35f1a
commit 1e15764bb9
17 changed files with 896 additions and 21 deletions

View file

@ -918,6 +918,8 @@ PRIVATE
info/bot/earn/info_bot_earn_list.h
info/bot/earn/info_bot_earn_widget.cpp
info/bot/earn/info_bot_earn_widget.h
info/bot/starref/info_bot_starref_widget.cpp
info/bot/starref/info_bot_starref_widget.h
info/channel_statistics/boosts/create_giveaway_box.cpp
info/channel_statistics/boosts/create_giveaway_box.h
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp

Binary file not shown.

After

(image error) Size: 78 KiB

View file

@ -4,6 +4,7 @@
<file alias="art/bg_thumbnail.png">../../art/bg_thumbnail.png</file>
<file alias="art/bg_initial.jpg">../../art/bg_initial.jpg</file>
<file alias="art/business_logo.png">../../art/business_logo.png</file>
<file alias="art/affiliate_logo.png">../../art/affiliate_logo.png</file>
<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/themeimage.jpg">../../art/themeimage.jpg</file>

View file

@ -46,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "history/admin_log/history_admin_log_section.h"
#include "info/bot/earn/info_bot_earn_widget.h"
#include "info/bot/starref/info_bot_starref_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/channel_statistics/earn/earn_format.h"
#include "info/channel_statistics/earn/earn_icons.h"
@ -358,6 +359,7 @@ private:
void fillBotUsernamesButton();
void fillBotCurrencyButton();
void fillBotCreditsButton();
void fillBotAffiliateProgram();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
@ -1181,6 +1183,7 @@ void Controller::fillManageSection() {
fillBotUsernamesButton();
fillBotCurrencyButton();
fillBotCreditsButton();
fillBotAffiliateProgram();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@ -1711,6 +1714,31 @@ void Controller::fillBotCreditsButton() {
}
void Controller::fillBotAffiliateProgram() {
Expects(_isBot);
const auto user = _peer->asUser();
auto label = user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::StarRefProgram
) | rpl::map([=] {
const auto commission = user->botInfo
? user->botInfo->starRefProgram.commission
: 0;
return commission
? u"%1%"_q.arg(commission)
: tr::lng_manage_peer_bot_star_ref_off(tr::now);
});
AddButtonWithCount(
_controls.buttonsLayout,
tr::lng_manage_peer_bot_star_ref(),
std::move(label),
[controller = _navigation->parentController(), user] {
controller->showSection(Info::BotStarRef::Make(user));
},
{ &st::menuIconSharing });
}
void Controller::fillBotEditIntroButton() {
Expects(_isBot);

View file

@ -92,27 +92,28 @@ struct PeerUpdate {
BusinessDetails = (1ULL << 30),
Birthday = (1ULL << 31),
PersonalChannel = (1ULL << 32),
StarRefProgram = (1ULL << 33),
// For chats and channels
InviteLinks = (1ULL << 33),
Members = (1ULL << 34),
Admins = (1ULL << 35),
BannedUsers = (1ULL << 36),
Rights = (1ULL << 37),
PendingRequests = (1ULL << 38),
Reactions = (1ULL << 39),
InviteLinks = (1ULL << 34),
Members = (1ULL << 35),
Admins = (1ULL << 36),
BannedUsers = (1ULL << 37),
Rights = (1ULL << 38),
PendingRequests = (1ULL << 39),
Reactions = (1ULL << 40),
// For channels
ChannelAmIn = (1ULL << 40),
StickersSet = (1ULL << 41),
EmojiSet = (1ULL << 42),
ChannelLinkedChat = (1ULL << 43),
ChannelLocation = (1ULL << 44),
Slowmode = (1ULL << 45),
GroupCall = (1ULL << 46),
ChannelAmIn = (1ULL << 41),
StickersSet = (1ULL << 42),
EmojiSet = (1ULL << 43),
ChannelLinkedChat = (1ULL << 44),
ChannelLocation = (1ULL << 45),
Slowmode = (1ULL << 46),
GroupCall = (1ULL << 47),
// For iteration
LastUsedBit = (1ULL << 46),
LastUsedBit = (1ULL << 47),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -600,6 +600,20 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
}
if (const auto info = user->botInfo.get()) {
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
auto starRefProgram = StarRefProgram();
if (const auto program = update.vstarref_program()) {
const auto &data = program->data();
starRefProgram.commission = data.vcommission_permille().v;
starRefProgram.durationMonths
= data.vduration_months().value_or_empty();
starRefProgram.endDate = data.vend_date().value_or_empty();
}
if (info->starRefProgram != starRefProgram) {
info->starRefProgram = starRefProgram;
user->session().changes().peerUpdated(
user,
Data::PeerUpdate::Flag::StarRefProgram);
}
}
if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, pinned->v);

View file

@ -19,6 +19,16 @@ struct BotCommand;
struct BusinessDetails;
} // namespace Data
struct StarRefProgram {
TimeId endDate = 0;
ushort commission = 0;
uint8 durationMonths = 0;
friend inline constexpr bool operator==(
StarRefProgram,
StarRefProgram) = default;
};
struct BotInfo {
BotInfo();
@ -44,6 +54,8 @@ struct BotInfo {
ChatAdminRights groupAdminRights;
ChatAdminRights channelAdminRights;
StarRefProgram starRefProgram;
int version = 0;
int descriptionVersion = 0;
int activeUsers = 0;

View file

@ -0,0 +1,681 @@
/*
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 "info/bot/starref/info_bot_starref_widget.h"
#include "apiwrap.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "core/click_handler_types.h"
#include "data/data_user.h"
#include "info/profile/info_profile_icon.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
namespace Info::BotStarRef {
namespace {
constexpr auto kDurationForeverValue = 999;
constexpr auto kCommissionDefault = 20;
constexpr auto kDurationDefault = 12;
} // namespace
struct State {
not_null<UserData*> user;
StarRefProgram program;
bool exists = false;
};
class InnerWidget final : public Ui::RpWidget {
public:
InnerWidget(QWidget *parent, not_null<Controller*> controller);
[[nodiscard]] not_null<PeerData*> peer() const;
[[nodiscard]] not_null<State*> state();
void showFinished();
void setInnerFocus();
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
private:
void prepare();
void setupInfo();
void setupCommission();
void setupDuration();
void setupViewExisting();
void setupEnd();
[[nodiscard]] object_ptr<Ui::RpWidget> infoRow(
rpl::producer<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon);
const not_null<Controller*> _controller;
State _state;
const not_null<Ui::VerticalLayout*> _container;
};
[[nodiscard]] int ValueForCommission(const State &state) {
return state.program.commission
? state.program.commission
: kCommissionDefault;
}
[[nodiscard]] int ValueForDurationMonths(const State &state) {
return state.program.durationMonths
? state.program.durationMonths
: state.exists
? kDurationForeverValue
: kDurationDefault;
}
[[nodiscard]] State StateForPeer(not_null<PeerData*> peer) {
const auto user = peer->asUser();
const auto program = user->botInfo->starRefProgram;
return State{
.user = user,
.program = program,
.exists = (program.commission > 0),
};
}
InnerWidget::InnerWidget(QWidget *parent, not_null<Controller*> controller)
: RpWidget(parent)
, _controller(controller)
, _state(StateForPeer(_controller->key().starrefPeer()))
, _container(Ui::CreateChild<Ui::VerticalLayout>(this)) {
prepare();
}
not_null<State*> InnerWidget::state() {
return &_state;
}
void InnerWidget::prepare() {
Ui::ResizeFitChild(this, _container);
setupInfo();
Ui::AddSkip(_container);
Ui::AddDivider(_container);
setupCommission();
setupDuration();
Ui::AddSkip(_container);
setupViewExisting();
Ui::AddSkip(_container);
Ui::AddDivider(_container);
Ui::AddSkip(_container);
setupEnd();
}
void InnerWidget::setupInfo() {
AddSkip(_container, st::defaultVerticalListSkip * 2);
_container->add(infoRow(
tr::lng_star_ref_share_title(),
tr::lng_star_ref_share_about(),
&st::menuIconPremium));
_container->add(infoRow(
tr::lng_star_ref_launch_title(),
tr::lng_star_ref_launch_about(),
&st::menuIconChannel));
_container->add(infoRow(
tr::lng_star_ref_let_title(),
tr::lng_star_ref_let_about(),
&st::menuIconStarRefLink));
}
void InnerWidget::setupCommission() {
Ui::AddSkip(_container);
Ui::AddSubsectionTitle(_container, tr::lng_star_ref_commission_title());
auto values = std::vector<int>();
for (auto i = 1; i != 91; ++i) {
values.push_back(i);
}
const auto valuesCount = int(values.size());
auto sliderWithLabel = ::Settings::MakeSliderWithLabel(
_container,
st::settingsScale,
st::settingsScaleLabel,
st::normalFont->spacew * 2,
st::settingsScaleLabel.style.font->width("90%"),
true);
_container->add(
std::move(sliderWithLabel.widget),
st::settingsBigScalePadding);
const auto slider = sliderWithLabel.slider;
const auto label = sliderWithLabel.label;
const auto updateLabel = [=](int value) {
const auto labelText = QString::number(value) + '%';
label->setText(labelText);
};
const auto commission = ValueForCommission(_state);
const auto setCommission = [=](int value) {
_state.program.commission = value;
updateLabel(value);
};
updateLabel(commission);
slider->setPseudoDiscrete(
valuesCount,
[=](int index) { return values[index]; },
commission,
setCommission,
setCommission);
Ui::AddSkip(_container);
Ui::AddDividerText(_container, tr::lng_star_ref_commission_about());
}
void InnerWidget::setupDuration() {
Ui::AddSkip(_container);
Ui::AddSubsectionTitle(_container, tr::lng_star_ref_duration_title());
auto values = std::vector<int>{ 1, 3, 6, 12, 24, 36, 999 };
const auto valuesCount = int(values.size());
auto sliderWithLabel = ::Settings::MakeSliderWithLabel(
_container,
st::settingsScale,
st::settingsScaleLabel,
st::normalFont->spacew * 2,
st::settingsScaleLabel.style.font->width("3y"),
true);
_container->add(
std::move(sliderWithLabel.widget),
st::settingsBigScalePadding);
const auto slider = sliderWithLabel.slider;
const auto label = sliderWithLabel.label;
const auto updateLabel = [=](int value) {
const auto labelText = (value < 12)
? (QString::number(value) + 'm')
: (value < 999)
? (QString::number(value / 12) + 'y')
: u"inf"_q;
label->setText(labelText);
};
const auto durationMonths = ValueForDurationMonths(_state);
const auto setDurationMonths = [=](int value) {
_state.program.durationMonths = (value == kDurationForeverValue)
? 0
: value;
updateLabel(durationMonths);
};
updateLabel(durationMonths);
slider->setPseudoDiscrete(
valuesCount,
[=](int index) { return values[index]; },
durationMonths,
setDurationMonths,
setDurationMonths);
Ui::AddSkip(_container);
Ui::AddDividerText(_container, tr::lng_star_ref_duration_about());
}
void InnerWidget::setupViewExisting() {
const auto &stLabel = st::defaultFlatLabel;
const auto iconSize = st::settingsPremiumIconDouble.size();
const auto &titlePadding = st::settingsPremiumRowTitlePadding;
const auto &descriptionPadding = st::settingsPremiumRowAboutPadding;
const auto content = _container;
const auto labelAscent = stLabel.style.font->ascent;
const auto button = Ui::CreateChild<Ui::SettingsButton>(
content.get(),
rpl::single(QString()));
const auto label = content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_star_ref_existing_title() | Ui::Text::ToBold(),
stLabel),
titlePadding);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto description = content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_star_ref_existing_about(),
st::boxDividerLabel),
descriptionPadding);
description->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto dummy = Ui::CreateChild<Ui::AbstractButton>(content.get());
dummy->setAttribute(Qt::WA_TransparentForMouseEvents);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
dummy->resize(s.width(), iconSize.height());
}, dummy->lifetime());
label->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
dummy->moveToLeft(0, r.y() + (r.height() - labelAscent));
}, dummy->lifetime());
::Settings::AddButtonIcon(dummy, st::settingsButton, {
.icon = &st::settingsPremiumIconStar,
.backgroundBrush = st::premiumIconBg3,
});
rpl::combine(
content->widthValue(),
label->heightValue(),
description->heightValue()
) | rpl::start_with_next([=,
topPadding = titlePadding,
bottomPadding = descriptionPadding](
int width,
int topHeight,
int bottomHeight) {
button->resize(
width,
topPadding.top()
+ topHeight
+ topPadding.bottom()
+ bottomPadding.top()
+ bottomHeight
+ bottomPadding.bottom());
}, button->lifetime());
label->topValue(
) | rpl::start_with_next([=, padding = titlePadding.top()](int top) {
button->moveToLeft(0, top - padding);
}, button->lifetime());
const auto arrow = Ui::CreateChild<Ui::IconButton>(
button,
st::backButton);
arrow->setIconOverride(
&st::settingsPremiumArrow,
&st::settingsPremiumArrowOver);
arrow->setAttribute(Qt::WA_TransparentForMouseEvents);
button->sizeValue(
) | rpl::start_with_next([=](const QSize &s) {
const auto &point = st::settingsPremiumArrowShift;
arrow->moveToRight(
-point.x(),
point.y() + (s.height() - arrow->height()) / 2);
}, arrow->lifetime());
button->setClickedCallback([=] {
_controller->showToast(u"List or smth.."_q);
});
}
void InnerWidget::setupEnd() {
if (!_state.exists) {
return;
}
const auto end = _container->add(object_ptr<Ui::SettingsButton>(
_container,
tr::lng_star_ref_end(),
st::settingsAttentionButton));
end->setClickedCallback([=] {
using Flag = MTPbots_UpdateStarRefProgram::Flag;
const auto user = _state.user;
const auto weak = Ui::MakeWeak(this);
user->session().api().request(MTPbots_UpdateStarRefProgram(
MTP_flags(0),
user->inputUser,
MTP_int(0),
MTP_int(0)
)).done([=] {
user->botInfo->starRefProgram.commission = 0;
user->botInfo->starRefProgram.durationMonths = 0;
user->updateFullForced();
if (weak) {
_controller->showToast("Removed!");
_controller->showBackFromStack();
}
}).fail(crl::guard(weak, [=] {
_controller->showToast("Remove failed!");
})).send();
});
}
object_ptr<Ui::RpWidget> InnerWidget::infoRow(
rpl::producer<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon) {
auto result = object_ptr<Ui::VerticalLayout>(_container);
const auto raw = result.data();
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | Ui::Text::ToBold(),
st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st::boxDividerLabel),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
return result;
}
not_null<PeerData*> InnerWidget::peer() const {
return _controller->key().starrefPeer();
}
void InnerWidget::showFinished() {
}
void InnerWidget::setInnerFocus() {
setFocus();
}
void InnerWidget::saveState(not_null<Memento*> memento) {
}
void InnerWidget::restoreState(not_null<Memento*> memento) {
}
Memento::Memento(not_null<Controller*> controller)
: ContentMemento(Tag(controller->starrefPeer())) {
}
Memento::Memento(not_null<PeerData*> peer)
: ContentMemento(Tag(peer)) {
}
Memento::~Memento() = default;
Section Memento::section() const {
return Section(Section::Type::BotStarRef);
}
object_ptr<ContentWidget> Memento::createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) {
auto result = object_ptr<Widget>(parent, controller);
result->setInternalState(geometry, this);
return result;
}
Widget::Widget(
QWidget *parent,
not_null<Controller*> controller)
: ContentWidget(parent, controller)
, _inner(setInnerWidget(object_ptr<InnerWidget>(this, controller)))
, _state(_inner->state()) {
_top = setupTop();
_bottom = setupBottom();
}
not_null<PeerData*> Widget::peer() const {
return _inner->peer();
}
bool Widget::showInternal(not_null<ContentMemento*> memento) {
return (memento->starrefPeer() == peer());
}
rpl::producer<QString> Widget::title() {
return tr::lng_star_ref_title();
}
void Widget::setInternalState(
const QRect &geometry,
not_null<Memento*> memento) {
setGeometry(geometry);
Ui::SendPendingMoveResizeEvents(this);
restoreState(memento);
}
rpl::producer<bool> Widget::desiredShadowVisibility() const {
return rpl::single<bool>(true);
}
void Widget::showFinished() {
_inner->showFinished();
}
void Widget::setInnerFocus() {
_inner->setInnerFocus();
}
void Widget::enableBackButton() {
_backEnabled = true;
}
std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
auto result = std::make_shared<Memento>(controller());
saveState(result.get());
return result;
}
void Widget::saveState(not_null<Memento*> memento) {
memento->setScrollTop(scrollTopSave());
_inner->saveState(memento);
}
void Widget::restoreState(not_null<Memento*> memento) {
_inner->restoreState(memento);
scrollTopRestore(memento->scrollTop());
}
std::unique_ptr<Ui::Premium::TopBarAbstract> Widget::setupTop() {
auto title = tr::lng_star_ref_title();
auto about = tr::lng_star_ref_about() | Ui::Text::ToWithEntities();
const auto controller = this->controller();
const auto weak = base::make_weak(controller->parentController());
const auto clickContextOther = [=] {
return QVariant::fromValue(ClickHandlerContext{
.sessionWindow = weak,
.botStartAutoSubmit = true,
});
};
auto result = std::make_unique<Ui::Premium::TopBar>(
this,
st::userPremiumCover,
Ui::Premium::TopBarDescriptor{
.clickContextOther = clickContextOther,
.logo = u"affiliate"_q,
.title = std::move(title),
.about = std::move(about),
.light = true,
});
const auto raw = result.get();
controller->wrapValue(
) | rpl::start_with_next([=](Info::Wrap wrap) {
raw->setRoundEdges(wrap == Info::Wrap::Layer);
}, raw->lifetime());
const auto calculateMaximumHeight = [=] {
return st::settingsPremiumTopHeight;
};
raw->setMaximumHeight(st::settingsPremiumTopHeight);
raw->setMinimumHeight(st::settingsPremiumTopHeight);
raw->resize(width(), raw->maximumHeight());
setPaintPadding({ 0, st::settingsPremiumTopHeight, 0, 0 });
controller->wrapValue(
) | rpl::start_with_next([=](Info::Wrap wrap) {
const auto isLayer = (wrap == Info::Wrap::Layer);
_back = base::make_unique_q<Ui::FadeWrap<Ui::IconButton>>(
raw,
object_ptr<Ui::IconButton>(
raw,
(isLayer
? st::infoLayerTopBar.back
: st::infoTopBar.back)),
st::infoTopBarScale);
_back->setDuration(0);
_back->toggleOn(isLayer
? _backEnabled.value() | rpl::type_erased()
: rpl::single(true));
_back->entity()->addClickHandler([=] {
controller->showBackFromStack();
});
_back->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar;
raw->setTextPosition(
toggled ? st.back.width : st.titlePosition.x(),
st.titlePosition.y());
}, _back->lifetime());
if (!isLayer) {
_close = nullptr;
} else {
_close = base::make_unique_q<Ui::IconButton>(
raw,
st::settingsPremiumTopBarClose);
_close->addClickHandler([=] {
controller->parentController()->hideLayer();
controller->parentController()->hideSpecialLayer();
});
raw->widthValue(
) | rpl::start_with_next([=] {
_close->moveToRight(0, 0);
}, _close->lifetime());
}
}, raw->lifetime());
raw->move(0, 0);
widthValue() | rpl::start_with_next([=](int width) {
raw->resizeToWidth(width);
setScrollTopSkip(raw->height());
}, raw->lifetime());
return result;
}
std::unique_ptr<Ui::RpWidget> Widget::setupBottom() {
auto result = std::make_unique<Ui::VerticalLayout>(this);
const auto raw = result.get();
auto text = base::timer_each(100) | rpl::map([=] {
const auto till = _state->user->botInfo->starRefProgram.endDate;
const auto now = base::unixtime::now();
const auto left = (till > now) ? (till - now) : 0;
return left
? tr::lng_star_ref_start_disabled(
tr::now,
lt_time,
QString::number(left))
: _state->exists
? tr::lng_star_ref_update(tr::now)
: tr::lng_star_ref_start(tr::now);
});
const auto save = raw->add(
object_ptr<Ui::RoundButton>(
raw,
rpl::duplicate(text),
st::defaultActiveButton),
st::starrefButtonMargin);
std::move(text) | rpl::start_with_next([=] {
save->resizeToWidth(raw->width()
- st::starrefButtonMargin.left()
- st::starrefButtonMargin.right());
}, save->lifetime());
save->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
const auto &margins = st::defaultBoxDividerLabelPadding;
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
(_state->exists
? tr::lng_star_ref_update_info
: tr::lng_star_ref_start_info)(
lt_terms,
tr::lng_star_ref_button_link() | Ui::Text::ToLink(),
Ui::Text::WithEntities),
st::boxDividerLabel),
QMargins(margins.left(), 0, margins.right(), 0));
save->setClickedCallback([=] {
using Flag = MTPbots_UpdateStarRefProgram::Flag;
const auto weak = Ui::MakeWeak(this);
const auto user = _state->user;
auto program = StarRefProgram{
.commission = _state->program.commission,
.durationMonths = _state->program.durationMonths,
};
user->session().api().request(MTPbots_UpdateStarRefProgram(
MTP_flags((program.commission > 0 && program.durationMonths > 0)
? Flag::f_duration_months
: Flag()),
user->inputUser,
MTP_int(program.commission),
MTP_int(program.durationMonths)
)).done([=] {
user->botInfo->starRefProgram.commission = program.commission;
user->botInfo->starRefProgram.durationMonths
= program.durationMonths;
if (weak) {
controller()->showBackFromStack();
}
}).fail(crl::guard(weak, [=] {
controller()->showToast("Failed!");
})).send();
});
widthValue() | rpl::start_with_next([=](int width) {
raw->resizeToWidth(width);
}, raw->lifetime());
rpl::combine(
raw->heightValue(),
heightValue()
) | rpl::start_with_next([=](int height, int fullHeight) {
setScrollBottomSkip(height);
raw->move(0, fullHeight - height);
}, raw->lifetime());
return result;
}
std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer) {
return std::make_shared<Info::Memento>(
std::vector<std::shared_ptr<ContentMemento>>(
1,
std::make_shared<Memento>(peer)));
}
} // namespace Info::BotStarRef

View file

@ -0,0 +1,82 @@
/*
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 "info/info_content_widget.h"
namespace Ui::Premium {
class TopBarAbstract;
} // namespace Ui::Premium
namespace Ui {
template <typename Widget>
class FadeWrap;
class IconButton;
} // namespace Ui
namespace Info::BotStarRef {
struct State;
class InnerWidget;
class Memento final : public ContentMemento {
public:
Memento(not_null<Controller*> controller);
Memento(not_null<PeerData*> peer);
~Memento();
object_ptr<ContentWidget> createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) override;
Section section() const override;
};
class Widget final : public ContentWidget {
public:
Widget(QWidget *parent, not_null<Controller*> controller);
bool showInternal(not_null<ContentMemento*> memento) override;
rpl::producer<QString> title() override;
rpl::producer<bool> desiredShadowVisibility() const override;
void showFinished() override;
void setInnerFocus() override;
void enableBackButton() override;
[[nodiscard]] not_null<PeerData*> peer() const;
void setInternalState(
const QRect &geometry,
not_null<Memento*> memento);
private:
void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento);
[[nodiscard]] std::unique_ptr<Ui::Premium::TopBarAbstract> setupTop();
[[nodiscard]] std::unique_ptr<Ui::RpWidget> setupBottom();
std::shared_ptr<ContentMemento> doCreateMemento() override;
const not_null<InnerWidget*> _inner;
const not_null<State*> _state;
std::unique_ptr<Ui::Premium::TopBarAbstract> _top;
base::unique_qptr<Ui::FadeWrap<Ui::IconButton>> _back;
base::unique_qptr<Ui::IconButton> _close;
rpl::variable<bool> _backEnabled;
std::unique_ptr<Ui::RpWidget> _bottom;
};
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer);
} // namespace Info::BotStarRef

View file

@ -1144,3 +1144,6 @@ infoHoursOuter: RoundButton(defaultActiveButton) {
}
infoHoursOuterMargin: margins(8px, 4px, 8px, 4px);
infoHoursDaySkip: 6px;
starrefInfoIconPosition: point(16px, 8px);
starrefButtonMargin: margins(12px, 12px, 12px, 12px);

View file

@ -374,10 +374,12 @@ Key ContentMemento::key() const {
return Key(poll, pollContextId());
} else if (const auto self = settingsSelf()) {
return Settings::Tag{ self };
} else if (const auto peer = storiesPeer()) {
return Stories::Tag{ peer, storiesTab() };
} else if (const auto peer = statisticsTag().peer) {
} else if (const auto stories = storiesPeer()) {
return Stories::Tag{ stories, storiesTab() };
} else if (statisticsTag().peer) {
return statisticsTag();
} else if (const auto starref = starrefPeer()) {
return BotStarRef::Tag(starref);
} else if (const auto who = reactionsWhoReadIds()) {
return Key(who, _reactionsSelected, _pollReactionsContextId);
} else {
@ -420,6 +422,10 @@ ContentMemento::ContentMemento(Statistics::Tag statistics)
: _statisticsTag(statistics) {
}
ContentMemento::ContentMemento(BotStarRef::Tag starref)
: _starrefPeer(starref.peer) {
}
ContentMemento::ContentMemento(
std::shared_ptr<Api::WhoReadList> whoReadIds,
FullMsgId contextId,

View file

@ -52,6 +52,10 @@ namespace Info::Statistics {
struct Tag;
} // namespace Info::Statistics
namespace Info::BotStarRef {
struct Tag;
} // namespace Info::BotStarRef
namespace Info {
class ContentMemento;
@ -191,6 +195,7 @@ public:
explicit ContentMemento(Downloads::Tag downloads);
explicit ContentMemento(Stories::Tag stories);
explicit ContentMemento(Statistics::Tag statistics);
explicit ContentMemento(BotStarRef::Tag starref);
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
: _poll(poll)
, _pollReactionsContextId(contextId) {
@ -226,6 +231,9 @@ public:
Statistics::Tag statisticsTag() const {
return _statisticsTag;
}
PeerData *starrefPeer() const {
return _starrefPeer;
}
PollData *poll() const {
return _poll;
}
@ -280,6 +288,7 @@ private:
PeerData * const _storiesPeer = nullptr;
Stories::Tab _storiesTab = {};
Statistics::Tag _statisticsTag;
PeerData * const _starrefPeer = nullptr;
PollData * const _poll = nullptr;
std::shared_ptr<Api::WhoReadList> _reactionsWhoReadIds;
Data::ReactionId _reactionsSelected;

View file

@ -46,6 +46,9 @@ Key::Key(Stories::Tag stories) : _value(stories) {
Key::Key(Statistics::Tag statistics) : _value(statistics) {
}
Key::Key(BotStarRef::Tag starref) : _value(starref) {
}
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
: _value(PollKey{ poll, contextId }) {
}
@ -106,6 +109,13 @@ Statistics::Tag Key::statisticsTag() const {
return Statistics::Tag();
}
PeerData *Key::starrefPeer() const {
if (const auto tag = std::get_if<BotStarRef::Tag>(&_value)) {
return tag->peer;
}
return nullptr;
}
PollData *Key::poll() const {
if (const auto data = std::get_if<PollKey>(&_value)) {
return data->poll;
@ -319,7 +329,8 @@ bool Controller::validateMementoPeer(
&& memento->migratedPeerId() == migratedPeerId()
&& memento->settingsSelf() == settingsSelf()
&& memento->storiesPeer() == storiesPeer()
&& memento->statisticsTag().peer == statisticsTag().peer;
&& memento->statisticsTag().peer == statisticsTag().peer
&& memento->starrefPeer() == starrefPeer();
}
void Controller::setSection(not_null<ContentMemento*> memento) {

View file

@ -61,6 +61,17 @@ struct Tag {
} // namespace Info::Stories
namespace Info::BotStarRef {
struct Tag {
explicit Tag(not_null<PeerData*> peer) : peer(peer) {
}
not_null<PeerData*> peer;
};
} // namespace Info::BotStarRef
namespace Info {
class Key {
@ -71,6 +82,7 @@ public:
Key(Downloads::Tag downloads);
Key(Stories::Tag stories);
Key(Statistics::Tag statistics);
Key(BotStarRef::Tag starref);
Key(not_null<PollData*> poll, FullMsgId contextId);
Key(
std::shared_ptr<Api::WhoReadList> whoReadIds,
@ -84,6 +96,7 @@ public:
PeerData *storiesPeer() const;
Stories::Tab storiesTab() const;
Statistics::Tag statisticsTag() const;
PeerData *starrefPeer() const;
PollData *poll() const;
FullMsgId pollContextId() const;
std::shared_ptr<Api::WhoReadList> reactionsWhoReadIds() const;
@ -107,6 +120,7 @@ private:
Downloads::Tag,
Stories::Tag,
Statistics::Tag,
BotStarRef::Tag,
PollKey,
ReactionsKey> _value;
@ -134,6 +148,7 @@ public:
Stories,
PollResults,
Statistics,
BotStarRef,
Boosts,
ChannelEarn,
BotEarn,
@ -202,6 +217,9 @@ public:
[[nodiscard]] Statistics::Tag statisticsTag() const {
return key().statisticsTag();
}
[[nodiscard]] PeerData *starrefPeer() const {
return key().starrefPeer();
}
[[nodiscard]] PollData *poll() const;
[[nodiscard]] FullMsgId pollContextId() const {
return key().pollContextId();

View file

@ -64,8 +64,9 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) {
[[nodiscard]] bool HasCustomTopBar(not_null<const Controller*> controller) {
const auto section = controller->section();
return (section.type() == Section::Type::Settings)
&& section.settingsType()->hasCustomTopBar();
return (section.type() == Section::Type::BotStarRef)
|| ((section.type() == Section::Type::Settings)
&& section.settingsType()->hasCustomTopBar());
}
[[nodiscard]] Fn<Ui::StringWithNumbers(int)> SelectedTitleForMedia(

View file

@ -138,6 +138,10 @@ TopBar::TopBar(
_dollar = ScaleTo(QImage(u":/gui/art/business_logo.png"_q));
_ministars.setColorOverride(
QGradientStops{{ 0, st::premiumButtonFg->c }});
} else if (_logo == u"affiliate"_q) {
_dollar = ScaleTo(QImage(u":/gui/art/affiliate_logo.png"_q));
_ministars.setColorOverride(
QGradientStops{{ 0, st::premiumButtonFg->c }});
} else if (!_light && !TopBarAbstract::isDark()) {
_star.load(Svg());
_ministars.setColorOverride(

View file

@ -42,6 +42,7 @@ menuIconRemove: icon {{ "menu/remove", menuIconColor }};
menuIconRetractVote: icon {{ "menu/retract_vote", menuIconColor }};
menuIconPermissions: icon {{ "menu/permissions", menuIconColor }};
menuIconShare: icon {{ "menu/share", menuIconColor }};
menuIconSharing: icon {{ "menu/share2", menuIconColor }};
menuIconArchive: icon {{ "menu/archive", menuIconColor }};
menuIconUnarchive: icon {{ "menu/unarchive", menuIconColor }};
menuIconMarkRead: icon {{ "menu/read", menuIconColor }};
@ -161,6 +162,7 @@ menuIconAppleWatch: icon {{ "menu/passcode_watch", menuIconColor }};
menuIconSystemPwd: menuIconPermissions;
menuIconPlayerFullScreen: icon {{ "player/player_fullscreen", menuIconColor }};
menuIconPlayerWindowed: icon {{ "player/player_minimize", menuIconColor }};
menuIconStarRefLink: icon{{ "settings/premium/features/feature_links2", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);