init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
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 PeerData;
|
||||
|
||||
namespace Data {
|
||||
struct BoostPrepaidGiveaway;
|
||||
} // namespace Data
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
} // namespace Ui
|
||||
|
||||
void CreateGiveawayBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> reloadOnDone,
|
||||
std::optional<Data::BoostPrepaidGiveaway> prepaidGiveaway);
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
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/channel_statistics/boosts/giveaway/boost_badge.h"
|
||||
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_statistics.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Info::Statistics {
|
||||
|
||||
not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
int size,
|
||||
const style::InfiniteRadialAnimation *st) {
|
||||
class Widget final : public Ui::RpWidget {
|
||||
public:
|
||||
Widget(
|
||||
not_null<Ui::RpWidget*> p,
|
||||
int size,
|
||||
const style::InfiniteRadialAnimation *st)
|
||||
: Ui::RpWidget(p)
|
||||
, _st(st ? st : &st::startGiveawayButtonLoading)
|
||||
, _animation([=] { update(); }, *_st) {
|
||||
resize(size, size);
|
||||
shownValue() | rpl::on_next([=](bool v) {
|
||||
return v
|
||||
? _animation.start()
|
||||
: _animation.stop(anim::type::instant);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override {
|
||||
auto p = QPainter(this);
|
||||
p.setPen(st::activeButtonFg);
|
||||
p.setBrush(st::activeButtonFg);
|
||||
const auto r = rect() - Margins(_st->thickness);
|
||||
_animation.draw(p, r.topLeft(), r.size(), width());
|
||||
}
|
||||
|
||||
private:
|
||||
const style::InfiniteRadialAnimation *_st;
|
||||
Ui::InfiniteRadialAnimation _animation;
|
||||
|
||||
};
|
||||
|
||||
return Ui::CreateChild<Widget>(parent.get(), size, st);
|
||||
}
|
||||
|
||||
void AddChildToWidgetCenter(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Ui::RpWidget*> child) {
|
||||
parent->sizeValue(
|
||||
) | rpl::on_next([=](const QSize &s) {
|
||||
const auto size = child->size();
|
||||
child->moveToLeft(
|
||||
(s.width() - size.width()) / 2,
|
||||
(s.height() - size.height()) / 2);
|
||||
}, child->lifetime());
|
||||
}
|
||||
|
||||
QImage CreateBadge(
|
||||
const style::TextStyle &textStyle,
|
||||
const QString &text,
|
||||
int badgeHeight,
|
||||
const style::margins &textPadding,
|
||||
const style::color &bg,
|
||||
const style::color &fg,
|
||||
float64 bgOpacity,
|
||||
const style::margins &iconPadding,
|
||||
const style::icon &icon) {
|
||||
auto badgeText = Ui::Text::String(textStyle, text);
|
||||
const auto badgeTextWidth = badgeText.maxWidth();
|
||||
const auto badgex = 0;
|
||||
const auto badgey = 0;
|
||||
const auto badgeh = 0 + badgeHeight;
|
||||
const auto badgew = badgeTextWidth
|
||||
+ rect::m::sum::h(textPadding);
|
||||
auto result = QImage(
|
||||
QSize(badgew, badgeh) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::transparent);
|
||||
result.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
{
|
||||
auto p = Painter(&result);
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(bg);
|
||||
|
||||
const auto r = QRect(badgex, badgey, badgew, badgeh);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto o = ScopedPainterOpacity(p, bgOpacity);
|
||||
p.drawRoundedRect(r, badgeh / 2, badgeh / 2);
|
||||
}
|
||||
|
||||
p.setPen(fg);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
badgeText.drawLeftElided(
|
||||
p,
|
||||
r.x() + textPadding.left(),
|
||||
badgey + textPadding.top(),
|
||||
badgew,
|
||||
badgew * 2);
|
||||
|
||||
icon.paint(
|
||||
p,
|
||||
QPoint(r.x() + iconPadding.left(), r.y() + iconPadding.top()),
|
||||
badgew * 2);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddLabelWithBadgeToButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<int> number,
|
||||
rpl::producer<bool> shown) {
|
||||
struct State {
|
||||
QImage badge;
|
||||
};
|
||||
const auto state = parent->lifetime().make_state<State>();
|
||||
const auto label = Ui::CreateChild<Ui::LabelSimple>(
|
||||
parent.get(),
|
||||
st::startGiveawayButtonLabelSimple);
|
||||
std::move(
|
||||
text
|
||||
) | rpl::on_next([=](const QString &s) {
|
||||
label->setText(s);
|
||||
}, label->lifetime());
|
||||
const auto count = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
count->paintRequest(
|
||||
) | rpl::on_next([=] {
|
||||
auto p = QPainter(count);
|
||||
p.drawImage(0, 0, state->badge);
|
||||
}, count->lifetime());
|
||||
std::move(
|
||||
number
|
||||
) | rpl::on_next([=](int c) {
|
||||
state->badge = Info::Statistics::CreateBadge(
|
||||
st::startGiveawayButtonTextStyle,
|
||||
QString::number(c),
|
||||
st::boostsListBadgeHeight,
|
||||
st::startGiveawayButtonBadgeTextPadding,
|
||||
st::activeButtonFg,
|
||||
st::activeButtonBg,
|
||||
1.,
|
||||
st::boostsListMiniIconPadding,
|
||||
st::startGiveawayButtonMiniIcon);
|
||||
count->resize(state->badge.size() / style::DevicePixelRatio());
|
||||
count->update();
|
||||
}, count->lifetime());
|
||||
|
||||
std::move(
|
||||
shown
|
||||
) | rpl::on_next([=](bool shown) {
|
||||
count->setVisible(shown);
|
||||
label->setVisible(shown);
|
||||
}, count->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
parent->sizeValue(),
|
||||
label->sizeValue(),
|
||||
count->sizeValue()
|
||||
) | rpl::on_next([=](
|
||||
const QSize &s,
|
||||
const QSize &s1,
|
||||
const QSize &s2) {
|
||||
const auto sum = st::startGiveawayButtonMiniIconSkip
|
||||
+ s1.width()
|
||||
+ s2.width();
|
||||
const auto contentLeft = (s.width() - sum) / 2;
|
||||
label->moveToLeft(contentLeft, (s.height() - s1.height()) / 2);
|
||||
count->moveToLeft(
|
||||
contentLeft + sum - s2.width(),
|
||||
(s.height() - s2.height()) / 2 + st::boostsListMiniIconSkip);
|
||||
}, parent->lifetime());
|
||||
}
|
||||
|
||||
} // namespace Info::Statistics
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace style {
|
||||
struct InfiniteRadialAnimation;
|
||||
struct TextStyle;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info::Statistics {
|
||||
|
||||
[[nodiscard]] QImage CreateBadge(
|
||||
const style::TextStyle &textStyle,
|
||||
const QString &text,
|
||||
int badgeHeight,
|
||||
const style::margins &textPadding,
|
||||
const style::color &bg,
|
||||
const style::color &fg,
|
||||
float64 bgOpacity,
|
||||
const style::margins &iconPadding,
|
||||
const style::icon &icon);
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> InfiniteRadialAnimationWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
int size,
|
||||
const style::InfiniteRadialAnimation *st = nullptr);
|
||||
|
||||
void AddChildToWidgetCenter(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Ui::RpWidget*> child);
|
||||
|
||||
void AddLabelWithBadgeToButton(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<int> number,
|
||||
rpl::producer<bool> shown);
|
||||
|
||||
} // namespace Info::Statistics
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
using "ui/basic.style";
|
||||
using "boxes/boxes.style";
|
||||
using "ui/effects/premium.style";
|
||||
using "statistics/statistics.style";
|
||||
|
||||
giveawayTypeListItem: PeerListItem(defaultPeerListItem) {
|
||||
height: 52px;
|
||||
photoPosition: point(58px, 6px);
|
||||
namePosition: point(110px, 8px);
|
||||
statusPosition: point(110px, 28px);
|
||||
photoSize: 42px;
|
||||
}
|
||||
giveawayUserpic: icon {{ "boosts/filled_gift", windowFgActive }};
|
||||
giveawayUserpicSkip: 1px;
|
||||
giveawayUserpicGroup: icon {{ "limits/groups", windowFgActive }};
|
||||
giveawayRadioPosition: point(21px, 16px);
|
||||
|
||||
giveawayGiftCodeCountryButton: SettingsButton(reportReasonButton) {
|
||||
}
|
||||
giveawayGiftCodeCountrySelect: MultiSelect(defaultMultiSelect) {
|
||||
}
|
||||
|
||||
giveawayGiftCodeChannelDeleteIcon: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFg }};
|
||||
giveawayGiftCodeChannelDeleteIconOver: icon {{ "dialogs/dialogs_cancel_search", dialogsMenuIconFgOver }};
|
||||
|
||||
giveawayLoadingLabel: FlatLabel(membersAbout) {
|
||||
}
|
||||
giveawayGiftCodeTopHeight: 195px;
|
||||
giveawayGiftCodeLink: FlatLabel(defaultFlatLabel) {
|
||||
margin: margins(10px, 12px, 10px, 8px);
|
||||
textFg: menuIconColor;
|
||||
maxHeight: 24px;
|
||||
}
|
||||
giveawayGiftCodeLinkCopy: icon{{ "menu/copy", menuIconColor }};
|
||||
giveawayGiftCodeLinkHeight: 42px;
|
||||
giveawayGiftCodeLinkCopyWidth: 40px;
|
||||
giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px);
|
||||
|
||||
giveawayGiftCodeGiftOption: PremiumOption(premiumGiftOption) {
|
||||
badgeShift: point(5px, 0px);
|
||||
}
|
||||
giveawayGiftCodeStartButton: RoundButton(defaultActiveButton) {
|
||||
height: 42px;
|
||||
textTop: 12px;
|
||||
radius: 6px;
|
||||
}
|
||||
giveawayGiftCodeQuantitySubtitle: FlatLabel(defaultFlatLabel) {
|
||||
style: TextStyle(semiboldTextStyle) {
|
||||
font: font(boxFontSize semibold);
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
minWidth: 240px;
|
||||
align: align(right);
|
||||
}
|
||||
giveawayGiftCodeQuantityFloat: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
textFg: windowActiveTextFg;
|
||||
minWidth: 50px;
|
||||
align: align(center);
|
||||
}
|
||||
|
||||
boostLinkStatsButton: IconButton(defaultIconButton) {
|
||||
width: giveawayGiftCodeLinkCopyWidth;
|
||||
height: giveawayGiftCodeLinkHeight;
|
||||
icon: icon{{ "menu/stats", menuIconColor }};
|
||||
iconOver: icon{{ "menu/stats", menuIconColor }};
|
||||
ripple: emptyRippleAnimation;
|
||||
}
|
||||
|
||||
giveawayGiftCodeTable: Table(defaultTable) {
|
||||
labelMinWidth: 91px;
|
||||
}
|
||||
giveawayGiftCodeTableMargin: margins(24px, 4px, 24px, 4px);
|
||||
giveawayGiftCodeLabelMargin: margins(13px, 10px, 13px, 10px);
|
||||
giveawayGiftCodeValueMultiline: FlatLabel(defaultTableValue) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 100px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(10px);
|
||||
linkUnderline: kLinkUnderlineNever;
|
||||
}
|
||||
}
|
||||
giveawayGiftMessage: FlatLabel(defaultTableValue) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 0px;
|
||||
}
|
||||
giveawayGiftMessageRemove: IconButton(defaultIconButton) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
icon: icon{{ "menu/delete", windowActiveTextFg }};
|
||||
iconOver: icon{{ "menu/delete", windowActiveTextFg }};
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 32px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: lightButtonBgRipple;
|
||||
}
|
||||
}
|
||||
giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px);
|
||||
giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px);
|
||||
giveawayGiftCodeUserpic: UserpicButton(defaultUserpicButton) {
|
||||
size: size(24px, 24px);
|
||||
photoSize: 24px;
|
||||
photoPosition: point(-1px, -1px);
|
||||
}
|
||||
giveawayGiftCodeNamePosition: point(32px, 4px);
|
||||
giveawayGiftCodeCover: PremiumCover(userPremiumCover) {
|
||||
starSize: size(92px, 90px);
|
||||
starTopSkip: 20px;
|
||||
titlePadding: margins(0px, 15px, 0px, 17px);
|
||||
titleFont: font(15px semibold);
|
||||
about: FlatLabel(userPremiumCoverAbout) {
|
||||
textFg: windowBoldFg;
|
||||
style: TextStyle(premiumAboutTextStyle) {
|
||||
lineHeight: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
giveawayGiftCodeCoverClosePosition: point(5px, 0px);
|
||||
giveawayGiftCodeCoverDividerPadding: margins(0px, 11px, 0px, 5px);
|
||||
giveawayGiftCodeTypeDividerPadding: margins(0px, 7px, 0px, 5px);
|
||||
giveawayGiftCodeSliderPadding: margins(0px, 24px, 0px, 10px);
|
||||
giveawayGiftCodeSliderFloatSkip: 6px;
|
||||
giveawayGiftCodeChannelsSubsectionPadding: margins(0px, -1px, 0px, -4px);
|
||||
giveawayGiftCodeAdditionalPaddingMin: margins(50px, 4px, 22px, 0px);
|
||||
giveawayGiftCodeAdditionalField: InputField(defaultMultiSelectSearchField) {
|
||||
}
|
||||
giveawayGiftCodeAdditionalLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
giveawayGiftCodeAdditionalLabelSkip: 12px;
|
||||
|
||||
giveawayGiftCodeChannelsPeerList: PeerList(boostsListBox) {
|
||||
padding: margins(0px, 7px, 0px, 0px);
|
||||
}
|
||||
giveawayGiftCodeMembersPeerList: PeerList(defaultPeerList) {
|
||||
item: PeerListItem(defaultPeerListItem) {
|
||||
height: 50px;
|
||||
namePosition: point(62px, 7px);
|
||||
statusPosition: point(62px, 27px);
|
||||
}
|
||||
}
|
||||
giveawayRadioMembersPosition: point(21px, 14px);
|
||||
|
||||
giveawayGiftCodeChannelsAddButton: SettingsButton(defaultSettingsButton) {
|
||||
textFg: lightButtonFg;
|
||||
textFgOver: lightButtonFgOver;
|
||||
padding: margins(70px, 10px, 22px, 8px);
|
||||
iconLeft: 28px;
|
||||
}
|
||||
giveawayGiftCodeChannelsDividerPadding: margins(0px, 5px, 0px, 5px);
|
||||
|
||||
giveawayGiftCodeFooter: FlatLabel(defaultFlatLabel) {
|
||||
align: align(top);
|
||||
textFg: windowBoldFg;
|
||||
}
|
||||
giveawayGiftCodeFooterMargin: margins(0px, 9px, 0px, 4px);
|
||||
giveawayGiftCodeBoxButton: RoundButton(defaultActiveButton) {
|
||||
height: 42px;
|
||||
textTop: 12px;
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
giveawayGiftCodeBox: Box(defaultBox) {
|
||||
buttonPadding: margins(22px, 11px, 22px, 22px);
|
||||
buttonHeight: 42px;
|
||||
buttonWide: true;
|
||||
button: giveawayGiftCodeBoxButton;
|
||||
shadowIgnoreTopSkip: true;
|
||||
}
|
||||
giveawayGiftCodeBoxUpgradeNext: RoundButton(defaultLightButton, giveawayGiftCodeBoxButton) {
|
||||
}
|
||||
giveawayRefundedLabel: FlatLabel(boxLabel) {
|
||||
align: align(top);
|
||||
style: semiboldTextStyle;
|
||||
textFg: attentionButtonFg;
|
||||
}
|
||||
giveawayRefundedPadding: margins(8px, 10px, 8px, 10px);
|
||||
|
||||
startGiveawayBox: Box(premiumGiftBox) {
|
||||
shadowIgnoreTopSkip: true;
|
||||
}
|
||||
startGiveawayScrollArea: ScrollArea(boxScroll) {
|
||||
deltax: 3px;
|
||||
deltat: 50px;
|
||||
}
|
||||
startGiveawayBoxTitleClose: IconButton(boxTitleClose) {
|
||||
ripple: universalRippleAnimation;
|
||||
}
|
||||
startGiveawayCover: PremiumCover(giveawayGiftCodeCover) {
|
||||
bg: boxDividerBg;
|
||||
additionalShadowForDarkThemes: false;
|
||||
}
|
||||
|
||||
startGiveawayButtonLabelSimple: LabelSimple {
|
||||
font: semiboldFont;
|
||||
textFg: activeButtonFg;
|
||||
}
|
||||
startGiveawayButtonMiniIcon: icon{{ "boosts/boost_mini2", activeButtonBg }};
|
||||
startGiveawayButtonMiniIconSkip: 5px;
|
||||
startGiveawayButtonBadgeTextPadding: margins(16px, -1px, 6px, 0px);
|
||||
startGiveawayButtonTextStyle: TextStyle(defaultTextStyle) {
|
||||
font: semiboldFont;
|
||||
}
|
||||
|
||||
startGiveawayButtonLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: activeButtonFg;
|
||||
thickness: 2px;
|
||||
}
|
||||
|
||||
starConvertButtonLoading: InfiniteRadialAnimation(startGiveawayButtonLoading) {
|
||||
color: windowActiveTextFg;
|
||||
thickness: 2px;
|
||||
}
|
||||
|
||||
starGiftSmallButton: defaultTableSmallButton;
|
||||
darkGiftCodeBox: Box(giveawayGiftCodeBox) {
|
||||
bg: groupCallMembersBg;
|
||||
title: FlatLabel(boxTitle) {
|
||||
textFg: groupCallMembersFg;
|
||||
}
|
||||
titleAdditionalFg: groupCallMemberNotJoinedStatus;
|
||||
}
|
||||
darkGiftLink: icon {{ "menu/copy", groupCallMembersFg }};
|
||||
darkGiftShare: icon {{ "menu/share", groupCallMembersFg }};
|
||||
darkGiftTheme: icon {{ "menu/colors", groupCallMembersFg }};
|
||||
darkGiftTransfer: icon {{ "chat/input_replace", groupCallMembersFg }};
|
||||
darkGiftNftWear: icon {{ "menu/nft_wear", groupCallMembersFg }};
|
||||
darkGiftNftTakeOff: icon {{ "menu/nft_takeoff", groupCallMembersFg }};
|
||||
darkGiftNftResell: icon {{ "menu/tag_sell", groupCallMembersFg }};
|
||||
darkGiftNftUnlist: icon {{ "menu/tag_remove", groupCallMembersFg }};
|
||||
darkGiftHide: icon {{ "menu/stealth", groupCallMembersFg }};
|
||||
darkGiftShow: icon {{ "menu/show_in_chat", groupCallMembersFg }};
|
||||
darkGiftPin: icon {{ "menu/pin", groupCallMembersFg }};
|
||||
darkGiftUnpin: icon {{ "menu/unpin", groupCallMembersFg }};
|
||||
darkGiftOffer: icon {{ "menu/earn", groupCallMembersFg }};
|
||||
darkGiftPalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: mediaviewTextLinkFg;
|
||||
monoFg: groupCallMembersFg;
|
||||
spoilerFg: groupCallMembersFg;
|
||||
}
|
||||
darkGiftTable: Table(giveawayGiftCodeTable) {
|
||||
headerBg: groupCallMembersBgOver;
|
||||
borderFg: mediaviewMenuBgOver;
|
||||
smallButton: RoundButton(defaultTableSmallButton) {
|
||||
textFg: groupCallMembersFg;
|
||||
textFgOver: groupCallMembersFg;
|
||||
textBg: groupCallMenuBgRipple;
|
||||
textBgOver: groupCallMenuBgRipple;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: mediaviewMenuBgOver;
|
||||
}
|
||||
}
|
||||
defaultLabel: FlatLabel(defaultTableLabel) {
|
||||
textFg: groupCallMembersFg;
|
||||
palette: darkGiftPalette;
|
||||
}
|
||||
defaultValue: FlatLabel(defaultTableValue) {
|
||||
textFg: groupCallMembersFg;
|
||||
palette: darkGiftPalette;
|
||||
}
|
||||
}
|
||||
darkGiftTableValueMultiline: FlatLabel(giveawayGiftCodeValueMultiline) {
|
||||
textFg: groupCallMembersFg;
|
||||
palette: darkGiftPalette;
|
||||
}
|
||||
darkGiftTableMessage: FlatLabel(giveawayGiftMessage) {
|
||||
textFg: groupCallMembersFg;
|
||||
palette: darkGiftPalette;
|
||||
}
|
||||
darkGiftCodeLink: FlatLabel(giveawayGiftCodeLink) {
|
||||
textFg: mediaviewMenuFg;
|
||||
}
|
||||
darkGiftBoxClose: IconButton(boxTitleClose) {
|
||||
icon: icon {{ "box_button_close", groupCallMemberInactiveIcon }};
|
||||
iconOver: icon {{ "box_button_close", groupCallMemberInactiveIcon }};
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: groupCallMembersBgOver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
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/channel_statistics/boosts/giveaway/giveaway_list_controllers.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
|
||||
namespace Giveaway {
|
||||
namespace {
|
||||
|
||||
class ChannelRow final : public PeerListRow {
|
||||
public:
|
||||
using PeerListRow::PeerListRow;
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
void rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) override;
|
||||
void rightActionStopLastRipple() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
|
||||
|
||||
};
|
||||
|
||||
QSize ChannelRow::rightActionSize() const {
|
||||
return QSize(
|
||||
st::giveawayGiftCodeChannelDeleteIcon.width(),
|
||||
st::giveawayGiftCodeChannelDeleteIcon.height()) * 2;
|
||||
}
|
||||
|
||||
QMargins ChannelRow::rightActionMargins() const {
|
||||
const auto itemHeight = st::giveawayGiftCodeChannelsPeerList.item.height;
|
||||
return QMargins(
|
||||
0,
|
||||
(itemHeight - rightActionSize().height()) / 2,
|
||||
st::giveawayRadioPosition.x() / 2,
|
||||
0);
|
||||
}
|
||||
|
||||
void ChannelRow::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
if (_actionRipple) {
|
||||
_actionRipple->paint(
|
||||
p,
|
||||
x,
|
||||
y,
|
||||
outerWidth);
|
||||
if (_actionRipple->empty()) {
|
||||
_actionRipple.reset();
|
||||
}
|
||||
}
|
||||
const auto rect = QRect(QPoint(x, y), ChannelRow::rightActionSize());
|
||||
(actionSelected
|
||||
? st::giveawayGiftCodeChannelDeleteIconOver
|
||||
: st::giveawayGiftCodeChannelDeleteIcon).paintInCenter(p, rect);
|
||||
}
|
||||
|
||||
void ChannelRow::rightActionAddRipple(
|
||||
QPoint point,
|
||||
Fn<void()> updateCallback) {
|
||||
if (!_actionRipple) {
|
||||
auto mask = Ui::RippleAnimation::EllipseMask(rightActionSize());
|
||||
_actionRipple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
std::move(mask),
|
||||
std::move(updateCallback));
|
||||
}
|
||||
_actionRipple->add(point);
|
||||
}
|
||||
|
||||
void ChannelRow::rightActionStopLastRipple() {
|
||||
if (_actionRipple) {
|
||||
_actionRipple->lastStop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AwardMembersListController::AwardMembersListController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
std::vector<not_null<PeerData*>> selected)
|
||||
: ParticipantsBoxController(navigation, peer, ParticipantsRole::Members)
|
||||
, _selected(std::move(selected)) {
|
||||
}
|
||||
|
||||
void AwardMembersListController::prepare() {
|
||||
ParticipantsBoxController::prepare();
|
||||
delegate()->peerListAddSelectedPeers(base::take(_selected));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void AwardMembersListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto checked = !row->checked();
|
||||
if (checked
|
||||
&& _checkErrorCallback
|
||||
&& _checkErrorCallback(delegate()->peerListSelectedRowsCount())) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> AwardMembersListController::createRow(
|
||||
not_null<PeerData*> participant) const {
|
||||
const auto user = participant->asUser();
|
||||
if (!user || user->isInaccessible() || user->isBot() || user->isSelf()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<PeerListRow>(participant);
|
||||
}
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> AwardMembersListController::rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AwardMembersListController::setCheckError(Fn<bool(int)> callback) {
|
||||
_checkErrorCallback = std::move(callback);
|
||||
}
|
||||
|
||||
MyChannelsListController::MyChannelsListController(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
std::vector<not_null<PeerData*>> selected)
|
||||
: PeerListController(
|
||||
std::make_unique<PeerListGlobalSearchController>(&peer->session()))
|
||||
, _peer(peer)
|
||||
, _show(show)
|
||||
, _selected(std::move(selected))
|
||||
, _otherChannels(std::make_unique<std::vector<not_null<ChannelData*>>>()) {
|
||||
{
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
if (const auto history = row->history()) {
|
||||
const auto channel = history->peer->asChannel();
|
||||
if (channel && !channel->isMegagroup()) {
|
||||
_otherChannels->push_back(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
auto &data = _peer->owner();
|
||||
addList(data.chatsList()->indexed());
|
||||
if (const auto folder = data.folderLoaded(Data::Folder::kId)) {
|
||||
addList(folder->chatsList()->indexed());
|
||||
}
|
||||
addList(data.contactsNoChatsList());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> MyChannelsListController::createSearchRow(
|
||||
not_null<PeerData*> peer) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
return createRow(channel);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> MyChannelsListController::createRestoredRow(
|
||||
not_null<PeerData*> peer) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
return createRow(channel);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MyChannelsListController::loadMoreRows() {
|
||||
if (_apiLifetime || !_otherChannels) {
|
||||
return;
|
||||
} else if (_lastAddedIndex >= _otherChannels->size()) {
|
||||
_otherChannels.release();
|
||||
return;
|
||||
}
|
||||
constexpr auto kPerPage = int(40);
|
||||
const auto till = std::min(
|
||||
int(_otherChannels->size()),
|
||||
_lastAddedIndex + kPerPage);
|
||||
while (_lastAddedIndex < till) {
|
||||
delegate()->peerListAppendRow(
|
||||
createRow(_otherChannels->at(_lastAddedIndex++)));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
void MyChannelsListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto channel = row->peer()->asChannel();
|
||||
const auto checked = !row->checked();
|
||||
if (checked
|
||||
&& _checkErrorCallback
|
||||
&& _checkErrorCallback(delegate()->peerListSelectedRowsCount())) {
|
||||
return;
|
||||
}
|
||||
if (checked && channel && channel->username().isEmpty()) {
|
||||
_show->showBox(Box(Ui::ConfirmBox, Ui::ConfirmBoxArgs{
|
||||
.text = tr::lng_giveaway_channels_confirm_about(),
|
||||
.confirmed = [=](Fn<void()> close) {
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
close();
|
||||
},
|
||||
.confirmText = tr::lng_filters_recommended_add(),
|
||||
.title = tr::lng_giveaway_channels_confirm_title(),
|
||||
}));
|
||||
} else {
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
}
|
||||
}
|
||||
|
||||
Main::Session &MyChannelsListController::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
|
||||
void MyChannelsListController::prepare() {
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
const auto api = _apiLifetime.make_state<MTP::Sender>(
|
||||
&session().api().instance());
|
||||
api->request(
|
||||
MTPstories_GetChatsToSend()
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_apiLifetime.destroy();
|
||||
const auto &chats = result.match([](const auto &data) {
|
||||
return data.vchats().v;
|
||||
});
|
||||
auto &owner = session().data();
|
||||
for (const auto &chat : chats) {
|
||||
if (const auto peer = owner.processChat(chat)) {
|
||||
if (!peer->isChannel() || (peer == _peer)) {
|
||||
continue;
|
||||
}
|
||||
if (!delegate()->peerListFindRow(peer->id.value)) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
auto row = createRow(channel);
|
||||
const auto raw = row.get();
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (ranges::contains(_selected, peer)) {
|
||||
delegate()->peerListSetRowChecked(raw, true);
|
||||
_selected.erase(
|
||||
ranges::remove(_selected, peer),
|
||||
end(_selected));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &selected : _selected) {
|
||||
if (const auto channel = selected->asChannel()) {
|
||||
auto row = createRow(channel);
|
||||
const auto raw = row.get();
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListSetRowChecked(raw, true);
|
||||
}
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
_selected.clear();
|
||||
}).send();
|
||||
}
|
||||
|
||||
void MyChannelsListController::setCheckError(Fn<bool(int)> callback) {
|
||||
_checkErrorCallback = std::move(callback);
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> MyChannelsListController::createRow(
|
||||
not_null<ChannelData*> channel) const {
|
||||
auto row = std::make_unique<PeerListRow>(channel);
|
||||
row->setCustomStatus((channel->isBroadcast()
|
||||
? tr::lng_chat_status_subscribers
|
||||
: tr::lng_chat_status_members)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
channel->membersCount()));
|
||||
return row;
|
||||
}
|
||||
|
||||
SelectedChannelsListController::SelectedChannelsListController(
|
||||
not_null<PeerData*> peer)
|
||||
: _peer(peer) {
|
||||
PeerListController::setStyleOverrides(
|
||||
&st::giveawayGiftCodeChannelsPeerList);
|
||||
}
|
||||
|
||||
void SelectedChannelsListController::setTopStatus(rpl::producer<QString> s) {
|
||||
_statusLifetime = std::move(
|
||||
s
|
||||
) | rpl::on_next([=](const QString &t) {
|
||||
if (delegate()->peerListFullRowsCount() > 0) {
|
||||
delegate()->peerListRowAt(0)->setCustomStatus(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SelectedChannelsListController::rebuild(
|
||||
std::vector<not_null<PeerData*>> selected) {
|
||||
while (delegate()->peerListFullRowsCount() > 1) {
|
||||
delegate()->peerListRemoveRow(delegate()->peerListRowAt(1));
|
||||
}
|
||||
for (const auto &peer : selected) {
|
||||
delegate()->peerListAppendRow(createRow(peer->asChannel()));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
auto SelectedChannelsListController::channelRemoved() const
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _channelRemoved.events();
|
||||
}
|
||||
|
||||
void SelectedChannelsListController::rowClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
void SelectedChannelsListController::rowRightActionClicked(
|
||||
not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
delegate()->peerListRemoveRow(row);
|
||||
delegate()->peerListRefreshRows();
|
||||
_channelRemoved.fire_copy(peer);
|
||||
}
|
||||
|
||||
Main::Session &SelectedChannelsListController::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
|
||||
void SelectedChannelsListController::prepare() {
|
||||
delegate()->peerListAppendRow(createRow(_peer->asChannel()));
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> SelectedChannelsListController::createRow(
|
||||
not_null<ChannelData*> channel) const {
|
||||
const auto isYourChannel = (_peer->asChannel() == channel);
|
||||
auto row = isYourChannel
|
||||
? std::make_unique<PeerListRow>(channel)
|
||||
: std::make_unique<ChannelRow>(channel);
|
||||
row->setCustomStatus(isYourChannel
|
||||
? QString()
|
||||
: (channel->isMegagroup()
|
||||
? tr::lng_chat_status_members
|
||||
: tr::lng_chat_status_subscribers)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
channel->membersCount()));
|
||||
return row;
|
||||
}
|
||||
|
||||
} // namespace Giveaway
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 "boxes/peers/edit_participants_box.h"
|
||||
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
class PeerListRow;
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
namespace Giveaway {
|
||||
|
||||
class AwardMembersListController : public ParticipantsBoxController {
|
||||
public:
|
||||
AwardMembersListController(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
std::vector<not_null<PeerData*>> selected);
|
||||
|
||||
void prepare() override;
|
||||
|
||||
void setCheckError(Fn<bool(int)> callback);
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<PeerData*> participant) const override;
|
||||
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||
QWidget *parent,
|
||||
not_null<PeerListRow*> row) override;
|
||||
|
||||
private:
|
||||
Fn<bool(int)> _checkErrorCallback;
|
||||
|
||||
std::vector<not_null<PeerData*>> _selected;
|
||||
|
||||
};
|
||||
|
||||
class MyChannelsListController : public PeerListController {
|
||||
public:
|
||||
MyChannelsListController(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
std::vector<not_null<PeerData*>> selected);
|
||||
|
||||
void setCheckError(Fn<bool(int)> callback);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void loadMoreRows() override;
|
||||
|
||||
std::unique_ptr<PeerListRow> createSearchRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
std::unique_ptr<PeerListRow> createRestoredRow(
|
||||
not_null<PeerData*> peer) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<ChannelData*> channel) const;
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
const std::shared_ptr<Ui::Show> _show;
|
||||
|
||||
Fn<bool(int)> _checkErrorCallback;
|
||||
|
||||
std::vector<not_null<PeerData*>> _selected;
|
||||
std::unique_ptr<std::vector<not_null<ChannelData*>>> _otherChannels;
|
||||
int _lastAddedIndex = 0;
|
||||
|
||||
rpl::lifetime _apiLifetime;
|
||||
|
||||
};
|
||||
|
||||
class SelectedChannelsListController : public PeerListController {
|
||||
public:
|
||||
SelectedChannelsListController(not_null<PeerData*> peer);
|
||||
|
||||
void setTopStatus(rpl::producer<QString> status);
|
||||
|
||||
void rebuild(std::vector<not_null<PeerData*>> selected);
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> channelRemoved() const;
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<ChannelData*> channel) const;
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
rpl::event_stream<not_null<PeerData*>> _channelRemoved;
|
||||
rpl::lifetime _statusLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Giveaway
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
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/channel_statistics/boosts/giveaway/giveaway_type_row.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_color_indices.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_statistics.h"
|
||||
|
||||
namespace Giveaway {
|
||||
|
||||
GiveawayTypeRow::GiveawayTypeRow(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
Type type,
|
||||
rpl::producer<QString> subtitle,
|
||||
bool group)
|
||||
: GiveawayTypeRow(
|
||||
parent,
|
||||
type,
|
||||
(type == Type::SpecificUsers) ? st::colorIndexBlue : st::colorIndexGreen,
|
||||
(type == Type::SpecificUsers)
|
||||
? tr::lng_giveaway_award_option()
|
||||
: (type == Type::Random)
|
||||
? tr::lng_premium_summary_title()
|
||||
// ? tr::lng_giveaway_create_option()
|
||||
: (type == Type::AllMembers)
|
||||
? (group
|
||||
? tr::lng_giveaway_users_all_group()
|
||||
: tr::lng_giveaway_users_all())
|
||||
: (group
|
||||
? tr::lng_giveaway_users_new_group()
|
||||
: tr::lng_giveaway_users_new()),
|
||||
std::move(subtitle),
|
||||
QImage()) {
|
||||
}
|
||||
|
||||
GiveawayTypeRow::GiveawayTypeRow(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
Type type,
|
||||
int colorIndex,
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> subtitle,
|
||||
QImage badge)
|
||||
: RippleButton(parent, st::defaultRippleAnimation)
|
||||
, _type(type)
|
||||
, _st((_type == Type::SpecificUsers
|
||||
|| _type == Type::Random
|
||||
|| _type == Type::Credits)
|
||||
? st::giveawayTypeListItem
|
||||
: ((_type == Type::Prepaid) || (_type == Type::PrepaidCredits))
|
||||
? st::boostsListBox.item
|
||||
: st::giveawayGiftCodeMembersPeerList.item)
|
||||
, _userpic(
|
||||
Ui::EmptyUserpic::UserpicColor(Ui::EmptyUserpic::ColorIndex(colorIndex)),
|
||||
QString())
|
||||
, _badge(std::move(badge)) {
|
||||
if (_type == Type::Credits || _type == Type::PrepaidCredits) {
|
||||
_customUserpic = Ui::CreditsWhiteDoubledIcon(_st.photoSize, 1.);
|
||||
}
|
||||
std::move(
|
||||
subtitle
|
||||
) | rpl::on_next([=] (QString s) {
|
||||
_status.setText(
|
||||
st::defaultTextStyle,
|
||||
s.replace(QChar('>'), QString()),
|
||||
Ui::NameTextOptions());
|
||||
}, lifetime());
|
||||
std::move(
|
||||
title
|
||||
) | rpl::on_next([=] (const QString &s) {
|
||||
_name.setText(_st.nameStyle, s, Ui::NameTextOptions());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
int GiveawayTypeRow::resizeGetHeight(int) {
|
||||
return _st.height;
|
||||
}
|
||||
|
||||
void GiveawayTypeRow::paintEvent(QPaintEvent *e) {
|
||||
auto p = Painter(this);
|
||||
|
||||
const auto paintOver = (isOver() || isDown()) && !isDisabled();
|
||||
const auto skipRight = _st.photoPosition.x();
|
||||
const auto outerWidth = width();
|
||||
const auto isRandom = (_type == Type::Random);
|
||||
const auto isSpecific = (_type == Type::SpecificUsers);
|
||||
const auto isPrepaid = (_type == Type::Prepaid);
|
||||
const auto hasUserpic = isRandom
|
||||
|| isSpecific
|
||||
|| isPrepaid
|
||||
|| (!_customUserpic.isNull());
|
||||
|
||||
if (paintOver) {
|
||||
p.fillRect(e->rect(), _st.button.textBgOver);
|
||||
}
|
||||
Ui::RippleButton::paintRipple(p, 0, 0);
|
||||
if (hasUserpic) {
|
||||
_userpic.paintCircle(
|
||||
p,
|
||||
_st.photoPosition.x(),
|
||||
_st.photoPosition.y(),
|
||||
outerWidth,
|
||||
_st.photoSize);
|
||||
|
||||
const auto userpicRect = QRect(
|
||||
_st.photoPosition
|
||||
- QPoint(
|
||||
isSpecific ? -st::giveawayUserpicSkip : 0,
|
||||
isSpecific ? 0 : st::giveawayUserpicSkip),
|
||||
Size(_st.photoSize));
|
||||
if (!_customUserpic.isNull()) {
|
||||
p.drawImage(_st.photoPosition, _customUserpic);
|
||||
} else {
|
||||
const auto &userpic = isSpecific
|
||||
? st::giveawayUserpicGroup
|
||||
: st::giveawayUserpic;
|
||||
userpic.paintInCenter(p, userpicRect);
|
||||
}
|
||||
}
|
||||
|
||||
const auto namex = _st.namePosition.x();
|
||||
const auto namey = _st.namePosition.y();
|
||||
const auto namew = outerWidth - namex - skipRight;
|
||||
|
||||
const auto badgew = _badge.width() / style::DevicePixelRatio();
|
||||
|
||||
p.setPen(_st.nameFg);
|
||||
_name.drawLeftElided(p, namex, namey, namew - badgew, width());
|
||||
|
||||
if (!_badge.isNull()) {
|
||||
p.drawImage(
|
||||
std::min(
|
||||
namex + _name.maxWidth() + st::boostsListBadgePadding.left(),
|
||||
outerWidth - badgew - skipRight),
|
||||
namey + st::boostsListMiniIconSkip,
|
||||
_badge);
|
||||
}
|
||||
|
||||
const auto statusIcon = isRandom ? &st::topicButtonArrow : nullptr;
|
||||
const auto statusx = _st.statusPosition.x();
|
||||
const auto statusy = _st.statusPosition.y();
|
||||
const auto statusw = outerWidth
|
||||
- statusx
|
||||
- skipRight
|
||||
- (statusIcon
|
||||
? (statusIcon->width() + st::boostsListMiniIconSkip)
|
||||
: 0);
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.setPen((isRandom || !hasUserpic) ? st::lightButtonFg : _st.statusFg);
|
||||
_status.drawLeftElided(p, statusx, statusy, statusw, outerWidth);
|
||||
if (statusIcon) {
|
||||
statusIcon->paint(
|
||||
p,
|
||||
QPoint(
|
||||
statusx
|
||||
+ std::min(_status.maxWidth(), statusw)
|
||||
+ st::boostsListMiniIconSkip,
|
||||
statusy + st::contactsStatusFont->descent),
|
||||
outerWidth,
|
||||
st::lightButtonFg->c);
|
||||
}
|
||||
}
|
||||
|
||||
void GiveawayTypeRow::addRadio(
|
||||
std::shared_ptr<Ui::RadioenumGroup<Type>> typeGroup) {
|
||||
const auto &st = st::defaultCheckbox;
|
||||
const auto radio = Ui::CreateChild<Ui::Radioenum<Type>>(
|
||||
this,
|
||||
std::move(typeGroup),
|
||||
_type,
|
||||
QString(),
|
||||
st);
|
||||
const auto pos = (_type == Type::SpecificUsers || _type == Type::Random)
|
||||
? st::giveawayRadioPosition
|
||||
: st::giveawayRadioMembersPosition;
|
||||
radio->moveToLeft(pos.x(), pos.y());
|
||||
radio->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
radio->show();
|
||||
}
|
||||
|
||||
} // namespace Giveaway
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 "ui/empty_userpic.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
|
||||
namespace Ui {
|
||||
template <typename Enum>
|
||||
class RadioenumGroup;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Giveaway {
|
||||
|
||||
class GiveawayTypeRow final : public Ui::RippleButton {
|
||||
public:
|
||||
enum class Type {
|
||||
Random,
|
||||
SpecificUsers,
|
||||
|
||||
AllMembers,
|
||||
OnlyNewMembers,
|
||||
|
||||
Prepaid,
|
||||
PrepaidCredits,
|
||||
|
||||
Credits,
|
||||
};
|
||||
|
||||
GiveawayTypeRow(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
Type type,
|
||||
rpl::producer<QString> subtitle,
|
||||
bool group);
|
||||
|
||||
GiveawayTypeRow(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
Type type,
|
||||
int colorIndex,
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> subtitle,
|
||||
QImage badge);
|
||||
|
||||
void addRadio(std::shared_ptr<Ui::RadioenumGroup<Type>> typeGroup);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
int resizeGetHeight(int) override;
|
||||
|
||||
private:
|
||||
const Type _type;
|
||||
const style::PeerListItem _st;
|
||||
|
||||
Ui::EmptyUserpic _userpic;
|
||||
Ui::Text::String _status;
|
||||
Ui::Text::String _name;
|
||||
|
||||
QImage _customUserpic;
|
||||
|
||||
QImage _badge;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Giveaway
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
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/channel_statistics/boosts/giveaway/select_countries_box.h"
|
||||
|
||||
#include "countries/countries_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QImage CacheFlagEmoji(const QString &flag) {
|
||||
const auto &st = st::giveawayGiftCodeCountrySelect.item;
|
||||
auto roundPaintCache = QImage(
|
||||
Size(st.height) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
roundPaintCache.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
roundPaintCache.fill(Qt::transparent);
|
||||
{
|
||||
const auto size = st.height;
|
||||
auto p = Painter(&roundPaintCache);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto flagText = Ui::Text::String(st::defaultTextStyle, flag);
|
||||
p.setPen(st.textBg);
|
||||
p.setBrush(st.textBg);
|
||||
p.drawEllipse(0, 0, size, size);
|
||||
flagText.draw(p, {
|
||||
.position = QPoint(
|
||||
0 + (size - flagText.maxWidth()) / 2,
|
||||
0 + (size - flagText.minHeight()) / 2),
|
||||
.outerWidth = size,
|
||||
.availableWidth = size,
|
||||
});
|
||||
}
|
||||
return roundPaintCache;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SelectCountriesBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const std::vector<QString> &selected,
|
||||
Fn<void(std::vector<QString>)> doneCallback,
|
||||
Fn<bool(int)> checkErrorCallback) {
|
||||
struct State final {
|
||||
std::vector<QString> resultList;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto multiSelect = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::MultiSelect>(
|
||||
box,
|
||||
st::giveawayGiftCodeCountrySelect,
|
||||
tr::lng_participant_filter()));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
const auto &buttonSt = st::giveawayGiftCodeCountryButton;
|
||||
|
||||
struct Entry final {
|
||||
Ui::SlideWrap<Ui::SettingsButton> *wrap = nullptr;
|
||||
QStringList list;
|
||||
QString iso2;
|
||||
};
|
||||
|
||||
auto countries = Countries::Instance().list();
|
||||
ranges::sort(countries, [](
|
||||
const Countries::Info &a,
|
||||
const Countries::Info &b) {
|
||||
return (a.name.compare(b.name, Qt::CaseInsensitive) < 0);
|
||||
});
|
||||
auto buttons = std::vector<Entry>();
|
||||
buttons.reserve(countries.size());
|
||||
for (const auto &country : countries) {
|
||||
const auto flag = Countries::Instance().flagEmojiByISO2(country.iso2);
|
||||
if (!Ui::Emoji::Find(flag)) {
|
||||
continue;
|
||||
}
|
||||
const auto itemId = buttons.size();
|
||||
auto button = object_ptr<SettingsButton>(
|
||||
box->verticalLayout(),
|
||||
rpl::single(flag + ' ' + country.name),
|
||||
buttonSt);
|
||||
const auto radio = Ui::CreateChild<Ui::RpWidget>(button.data());
|
||||
const auto radioView = std::make_shared<Ui::RadioView>(
|
||||
st::defaultRadio,
|
||||
false,
|
||||
[=] { radio->update(); });
|
||||
|
||||
{
|
||||
const auto radioSize = radioView->getSize();
|
||||
radio->resize(radioSize);
|
||||
radio->paintRequest(
|
||||
) | rpl::on_next([=](const QRect &r) {
|
||||
auto p = QPainter(radio);
|
||||
radioView->paint(p, 0, 0, radioSize.width());
|
||||
}, radio->lifetime());
|
||||
const auto buttonHeight = buttonSt.height
|
||||
+ rect::m::sum::v(buttonSt.padding);
|
||||
radio->moveToLeft(
|
||||
st::giveawayRadioPosition.x(),
|
||||
(buttonHeight - radioSize.height()) / 2);
|
||||
}
|
||||
|
||||
const auto roundPaintCache = CacheFlagEmoji(flag);
|
||||
const auto paintCallback = [=](Painter &p, int x, int y, int, int) {
|
||||
p.drawImage(x, y, roundPaintCache);
|
||||
};
|
||||
const auto choose = [=](bool clicked) {
|
||||
const auto value = !radioView->checked();
|
||||
if (value && checkErrorCallback(state->resultList.size())) {
|
||||
return;
|
||||
}
|
||||
radioView->setChecked(value, anim::type::normal);
|
||||
|
||||
if (value) {
|
||||
state->resultList.push_back(country.iso2);
|
||||
multiSelect->addItem(
|
||||
itemId,
|
||||
country.name,
|
||||
st::activeButtonBg,
|
||||
paintCallback,
|
||||
clicked
|
||||
? Ui::MultiSelect::AddItemWay::Default
|
||||
: Ui::MultiSelect::AddItemWay::SkipAnimation);
|
||||
} else {
|
||||
auto &list = state->resultList;
|
||||
list.erase(ranges::remove(list, country.iso2), end(list));
|
||||
multiSelect->removeItem(itemId);
|
||||
}
|
||||
};
|
||||
button->setClickedCallback([=] {
|
||||
choose(true);
|
||||
});
|
||||
if (ranges::contains(selected, country.iso2)) {
|
||||
choose(false);
|
||||
}
|
||||
|
||||
const auto wrap = box->verticalLayout()->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
box,
|
||||
std::move(button)));
|
||||
wrap->toggle(true, anim::type::instant);
|
||||
|
||||
{
|
||||
auto list = QStringList{
|
||||
flag,
|
||||
country.name,
|
||||
country.alternativeName,
|
||||
};
|
||||
buttons.push_back({ wrap, std::move(list), country.iso2 });
|
||||
}
|
||||
}
|
||||
|
||||
const auto noResults = box->addRow(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
box,
|
||||
object_ptr<Ui::VerticalLayout>(box)));
|
||||
{
|
||||
noResults->toggle(false, anim::type::instant);
|
||||
const auto container = noResults->entity();
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_search_messages_none(),
|
||||
st::membersAbout),
|
||||
style::al_top);
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
}
|
||||
|
||||
multiSelect->setQueryChangedCallback([=](const QString &query) {
|
||||
auto wasAnyFound = false;
|
||||
for (const auto &entry : buttons) {
|
||||
const auto found = ranges::any_of(entry.list, [&](
|
||||
const QString &s) {
|
||||
return s.startsWith(query, Qt::CaseInsensitive);
|
||||
});
|
||||
entry.wrap->toggle(found, anim::type::instant);
|
||||
wasAnyFound |= found;
|
||||
}
|
||||
noResults->toggle(!wasAnyFound, anim::type::instant);
|
||||
});
|
||||
multiSelect->setItemRemovedCallback([=](uint64 itemId) {
|
||||
auto &list = state->resultList;
|
||||
auto &button = buttons[itemId];
|
||||
const auto it = ranges::find(list, button.iso2);
|
||||
if (it != end(list)) {
|
||||
list.erase(it);
|
||||
button.wrap->entity()->clicked({}, Qt::LeftButton);
|
||||
}
|
||||
});
|
||||
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
doneCallback(state->resultList);
|
||||
box->closeBox();
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
void SelectCountriesBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const std::vector<QString> &selected,
|
||||
Fn<void(std::vector<QString>)> doneCallback,
|
||||
Fn<bool(int)> checkErrorCallback);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
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/channel_statistics/boosts/info_boosts_inner_widget.h"
|
||||
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peers/edit_peer_invite_link.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "info/channel_statistics/boosts/create_giveaway_box.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/giveaway_type_row.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/statistics/info_statistics_inner_widget.h" // FillLoading.
|
||||
#include "info/statistics/info_statistics_list_controllers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "statistics/widgets/chart_header_widget.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/controls/invite_link_label.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/slider_natural_width.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "styles/style_color_indices.h"
|
||||
#include "styles/style_dialogs.h" // dialogsSearchTabs
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_statistics.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
|
||||
namespace Info::Boosts {
|
||||
namespace {
|
||||
|
||||
void AddHeader(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
tr::phrase<> text) {
|
||||
const auto header = content->add(
|
||||
object_ptr<Statistic::Header>(content),
|
||||
st::statisticsLayerMargins + st::boostsChartHeaderPadding);
|
||||
header->resizeToWidth(header->width());
|
||||
header->setTitle(text(tr::now));
|
||||
header->setSubTitle({});
|
||||
}
|
||||
|
||||
void FillOverview(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
const Data::BoostStatus &status) {
|
||||
const auto &stats = status.overview;
|
||||
|
||||
Ui::AddSkip(content, st::boostsLayerOverviewMargins.top());
|
||||
AddHeader(content, tr::lng_stats_overview_title);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto diffBetweenHeaders = 0
|
||||
+ st::statisticsOverviewValue.style.font->height
|
||||
- st::statisticsHeaderTitleTextStyle.font->height;
|
||||
|
||||
const auto container = content->add(
|
||||
object_ptr<Ui::RpWidget>(content),
|
||||
st::statisticsLayerMargins);
|
||||
|
||||
const auto addPrimary = [&](float64 v, bool approximately = false) {
|
||||
return Ui::CreateChild<Ui::FlatLabel>(
|
||||
container,
|
||||
(v >= 0)
|
||||
? (approximately && v ? QChar(0x2248) : QChar())
|
||||
+ Lang::FormatCountToShort(v).string
|
||||
: QString(),
|
||||
st::statisticsOverviewValue);
|
||||
};
|
||||
const auto addSub = [&](
|
||||
not_null<Ui::RpWidget*> primary,
|
||||
float64 percentage,
|
||||
tr::phrase<> text) {
|
||||
const auto second = Ui::CreateChild<Ui::FlatLabel>(
|
||||
container,
|
||||
percentage
|
||||
? u"%1%"_q.arg(std::abs(std::round(percentage * 10.) / 10.))
|
||||
: QString(),
|
||||
st::statisticsOverviewSecondValue);
|
||||
second->setTextColorOverride(st::windowSubTextFg->c);
|
||||
const auto sub = Ui::CreateChild<Ui::FlatLabel>(
|
||||
container,
|
||||
text(),
|
||||
st::statisticsOverviewSubtext);
|
||||
sub->setTextColorOverride(st::windowSubTextFg->c);
|
||||
|
||||
primary->geometryValue(
|
||||
) | rpl::on_next([=](const QRect &g) {
|
||||
const auto &padding = st::statisticsOverviewSecondValuePadding;
|
||||
second->moveToLeft(
|
||||
rect::right(g) + padding.left(),
|
||||
g.y() + padding.top());
|
||||
sub->moveToLeft(
|
||||
g.x(),
|
||||
st::statisticsChartHeaderHeight
|
||||
- st::statisticsOverviewSubtext.style.font->height
|
||||
+ g.y()
|
||||
+ diffBetweenHeaders);
|
||||
}, primary->lifetime());
|
||||
};
|
||||
|
||||
|
||||
const auto topLeftLabel = addPrimary(stats.level);
|
||||
const auto topRightLabel = addPrimary(stats.premiumMemberCount, true);
|
||||
const auto bottomLeftLabel = addPrimary(stats.boostCount);
|
||||
const auto bottomRightLabel = addPrimary(std::max(
|
||||
stats.nextLevelBoostCount - stats.boostCount,
|
||||
0));
|
||||
|
||||
addSub(
|
||||
topLeftLabel,
|
||||
0,
|
||||
tr::lng_boosts_level);
|
||||
addSub(
|
||||
topRightLabel,
|
||||
stats.premiumMemberPercentage,
|
||||
(stats.group
|
||||
? tr::lng_boosts_premium_members
|
||||
: tr::lng_boosts_premium_audience));
|
||||
addSub(
|
||||
bottomLeftLabel,
|
||||
0,
|
||||
tr::lng_boosts_existing);
|
||||
addSub(
|
||||
bottomRightLabel,
|
||||
0,
|
||||
tr::lng_boosts_next_level);
|
||||
|
||||
container->showChildren();
|
||||
container->resize(container->width(), topLeftLabel->height() * 5);
|
||||
container->sizeValue(
|
||||
) | rpl::on_next([=](const QSize &s) {
|
||||
const auto halfWidth = s.width() / 2;
|
||||
{
|
||||
const auto &p = st::boostsOverviewValuePadding;
|
||||
topLeftLabel->moveToLeft(p.left(), p.top());
|
||||
}
|
||||
topRightLabel->moveToLeft(
|
||||
topLeftLabel->x() + halfWidth + st::statisticsOverviewRightSkip,
|
||||
topLeftLabel->y());
|
||||
bottomLeftLabel->moveToLeft(
|
||||
topLeftLabel->x(),
|
||||
topLeftLabel->y() + st::statisticsOverviewMidSkip);
|
||||
bottomRightLabel->moveToLeft(
|
||||
topRightLabel->x(),
|
||||
bottomLeftLabel->y());
|
||||
}, container->lifetime());
|
||||
Ui::AddSkip(content, st::boostsLayerOverviewMargins.bottom());
|
||||
}
|
||||
|
||||
void FillShareLink(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
const QString &link,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto weak = base::make_weak(content);
|
||||
const auto copyLink = crl::guard(weak, [=] {
|
||||
QGuiApplication::clipboard()->setText(link);
|
||||
show->showToast(tr::lng_channel_public_link_copied(tr::now));
|
||||
});
|
||||
const auto shareLink = crl::guard(weak, [=] {
|
||||
show->showBox(ShareInviteLinkBox(peer, link));
|
||||
});
|
||||
|
||||
const auto label = content->lifetime().make_state<Ui::InviteLinkLabel>(
|
||||
content,
|
||||
rpl::single(base::duplicate(link).replace(u"https://"_q, QString())),
|
||||
nullptr);
|
||||
content->add(
|
||||
label->take(),
|
||||
st::boostsLinkFieldPadding);
|
||||
|
||||
label->clicks(
|
||||
) | rpl::on_next(copyLink, label->lifetime());
|
||||
{
|
||||
const auto wrap = content->add(
|
||||
object_ptr<Ui::FixedHeightWidget>(
|
||||
content,
|
||||
st::inviteLinkButton.height),
|
||||
st::inviteLinkButtonsPadding);
|
||||
const auto copy = CreateChild<Ui::RoundButton>(
|
||||
wrap,
|
||||
tr::lng_group_invite_context_copy(),
|
||||
st::inviteLinkCopy);
|
||||
copy->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
copy->setClickedCallback(copyLink);
|
||||
const auto share = CreateChild<Ui::RoundButton>(
|
||||
wrap,
|
||||
tr::lng_group_invite_context_share(),
|
||||
st::inviteLinkShare);
|
||||
share->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
share->setClickedCallback(shareLink);
|
||||
|
||||
wrap->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
const auto buttonWidth = (width - st::inviteLinkButtonsSkip) / 2;
|
||||
copy->setFullWidth(buttonWidth);
|
||||
share->setFullWidth(buttonWidth);
|
||||
copy->moveToLeft(0, 0, width);
|
||||
share->moveToRight(0, 0, width);
|
||||
}, wrap->lifetime());
|
||||
wrap->showChildren();
|
||||
}
|
||||
Ui::AddSkip(content, st::boostsLinkFieldPadding.bottom());
|
||||
}
|
||||
|
||||
void FillGetBoostsButton(
|
||||
not_null<Ui::VerticalLayout*> content,
|
||||
not_null<Controller*> controller,
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> reloadOnDone) {
|
||||
if (!Api::PremiumGiftCodeOptions(peer).giveawayGiftsPurchaseAvailable()) {
|
||||
return;
|
||||
}
|
||||
Ui::AddSkip(content);
|
||||
const auto &st = st::getBoostsButton;
|
||||
const auto &icon = st::getBoostsButtonIcon;
|
||||
const auto button = content->add(object_ptr<Ui::SettingsButton>(
|
||||
content.get(),
|
||||
tr::lng_boosts_get_boosts(),
|
||||
st));
|
||||
button->setClickedCallback([=] {
|
||||
show->showBox(Box(
|
||||
CreateGiveawayBox,
|
||||
controller,
|
||||
peer,
|
||||
reloadOnDone,
|
||||
std::nullopt));
|
||||
});
|
||||
Ui::CreateChild<Info::Profile::FloatingIcon>(
|
||||
button,
|
||||
icon,
|
||||
QPoint{
|
||||
st::infoSharedMediaButtonIconPosition.x(),
|
||||
(st.height + rect::m::sum::v(st.padding) - icon.height()) / 2,
|
||||
})->show();
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddDividerText(
|
||||
content,
|
||||
peer->isMegagroup()
|
||||
? tr::lng_boosts_get_boosts_subtext_group()
|
||||
: tr::lng_boosts_get_boosts_subtext());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
InnerWidget::InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
not_null<PeerData*> peer)
|
||||
: VerticalLayout(parent)
|
||||
, _controller(controller)
|
||||
, _peer(peer)
|
||||
, _show(controller->uiShow()) {
|
||||
}
|
||||
|
||||
void InnerWidget::load() {
|
||||
const auto api = lifetime().make_state<Api::Boosts>(_peer);
|
||||
|
||||
Info::Statistics::FillLoading(
|
||||
this,
|
||||
Info::Statistics::LoadingType::Boosts,
|
||||
_loaded.events_starting_with(false) | rpl::map(!rpl::mappers::_1),
|
||||
_showFinished.events());
|
||||
|
||||
_showFinished.events(
|
||||
) | rpl::take(1) | rpl::on_next([=] {
|
||||
api->request(
|
||||
) | rpl::on_error_done([](const QString &error) {
|
||||
}, [=] {
|
||||
_state = api->boostStatus();
|
||||
_loaded.fire(true);
|
||||
fill();
|
||||
}, lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void InnerWidget::fill() {
|
||||
const auto fakeShowed = lifetime().make_state<rpl::event_stream<>>();
|
||||
const auto &status = _state;
|
||||
const auto inner = this;
|
||||
|
||||
const auto reloadOnDone = crl::guard(this, [=] {
|
||||
while (Ui::VerticalLayout::count()) {
|
||||
delete Ui::VerticalLayout::widgetAt(0);
|
||||
}
|
||||
load();
|
||||
_showFinished.fire({});
|
||||
});
|
||||
|
||||
{
|
||||
auto dividerContent = object_ptr<Ui::VerticalLayout>(inner);
|
||||
dividerContent->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
dividerContent,
|
||||
st::boostSkipTop));
|
||||
Ui::FillBoostLimit(
|
||||
fakeShowed->events(),
|
||||
dividerContent.data(),
|
||||
rpl::single(Ui::BoostCounters{
|
||||
.level = status.overview.level,
|
||||
.boosts = status.overview.boostCount,
|
||||
.thisLevelBoosts
|
||||
= status.overview.currentLevelBoostCount,
|
||||
.nextLevelBoosts
|
||||
= status.overview.nextLevelBoostCount,
|
||||
.mine = status.overview.mine,
|
||||
}),
|
||||
st::statisticsLimitsLinePadding);
|
||||
inner->add(object_ptr<Ui::DividerLabel>(
|
||||
inner,
|
||||
std::move(dividerContent),
|
||||
st::statisticsLimitsDividerPadding));
|
||||
}
|
||||
|
||||
FillOverview(inner, status);
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
Ui::AddSkip(inner);
|
||||
|
||||
if (!status.prepaidGiveaway.empty()) {
|
||||
const auto multiplier = Api::PremiumGiftCodeOptions(_peer)
|
||||
.giveawayBoostsPerPremium();
|
||||
Ui::AddSkip(inner);
|
||||
AddHeader(inner, tr::lng_boosts_prepaid_giveaway_title);
|
||||
Ui::AddSkip(inner);
|
||||
for (const auto &g : status.prepaidGiveaway) {
|
||||
using namespace Giveaway;
|
||||
const auto button = inner->add(object_ptr<GiveawayTypeRow>(
|
||||
inner,
|
||||
g.credits
|
||||
? GiveawayTypeRow::Type::PrepaidCredits
|
||||
: GiveawayTypeRow::Type::Prepaid,
|
||||
g.credits ? st::colorIndexOrange : g.id,
|
||||
g.credits
|
||||
? tr::lng_boosts_prepaid_giveaway_single()
|
||||
: tr::lng_boosts_prepaid_giveaway_quantity(
|
||||
lt_count,
|
||||
rpl::single(g.quantity) | tr::to_count()),
|
||||
g.credits
|
||||
? tr::lng_boosts_prepaid_giveaway_credits_status(
|
||||
lt_count,
|
||||
rpl::single(g.quantity) | tr::to_count(),
|
||||
lt_amount,
|
||||
tr::lng_prize_credits_amount(
|
||||
lt_count_decimal,
|
||||
rpl::single(g.credits) | tr::to_count()))
|
||||
: tr::lng_boosts_prepaid_giveaway_moths(
|
||||
lt_count,
|
||||
rpl::single(g.months) | tr::to_count()),
|
||||
Info::Statistics::CreateBadge(
|
||||
st::statisticsDetailsBottomCaptionStyle,
|
||||
QString::number(
|
||||
g.boosts ? g.boosts : (g.quantity * multiplier)),
|
||||
st::boostsListBadgeHeight,
|
||||
st::boostsListBadgeTextPadding,
|
||||
st::premiumButtonBg2,
|
||||
st::premiumButtonFg,
|
||||
1.,
|
||||
st::boostsListMiniIconPadding,
|
||||
st::boostsListMiniIcon)));
|
||||
button->setClickedCallback([=] {
|
||||
_controller->uiShow()->showBox(Box(
|
||||
CreateGiveawayBox,
|
||||
_controller,
|
||||
_peer,
|
||||
reloadOnDone,
|
||||
g));
|
||||
});
|
||||
}
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDividerText(
|
||||
inner,
|
||||
tr::lng_boosts_prepaid_giveaway_title_subtext());
|
||||
Ui::AddSkip(inner);
|
||||
}
|
||||
|
||||
const auto hasBoosts = (status.firstSliceBoosts.multipliedTotal > 0);
|
||||
const auto hasGifts = (status.firstSliceGifts.multipliedTotal > 0);
|
||||
if (hasBoosts || hasGifts) {
|
||||
auto boostClicked = [=](const Data::Boost &boost) {
|
||||
if (!boost.giftCodeLink.slug.isEmpty()) {
|
||||
ResolveGiftCode(_controller, boost.giftCodeLink.slug);
|
||||
} else if (boost.userId) {
|
||||
const auto user = _peer->owner().user(boost.userId);
|
||||
if (boost.isGift || boost.isGiveaway) {
|
||||
const auto d = Api::GiftCode{
|
||||
.from = _peer->id,
|
||||
.to = user->id,
|
||||
.date = TimeId(boost.date.toSecsSinceEpoch()),
|
||||
.days = boost.expiresAfterMonths * 30,
|
||||
};
|
||||
_show->showBox(Box(GiftCodePendingBox, _controller, d));
|
||||
} else {
|
||||
crl::on_main(this, [=] {
|
||||
_controller->showPeerInfo(user);
|
||||
});
|
||||
}
|
||||
} else if (boost.credits) {
|
||||
_show->showBox(
|
||||
Box(
|
||||
::Settings::BoostCreditsBox,
|
||||
_controller->parentController(),
|
||||
boost));
|
||||
} else if (!boost.isUnclaimed) {
|
||||
_show->showToast(tr::lng_boosts_list_pending_about(tr::now));
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
const auto hasOneTab = false;
|
||||
#else
|
||||
const auto hasOneTab = (hasBoosts != hasGifts);
|
||||
#endif
|
||||
const auto boostsTabText = tr::lng_giveaway_quantity(
|
||||
tr::now,
|
||||
lt_count,
|
||||
status.firstSliceBoosts.multipliedTotal);
|
||||
const auto giftsTabText = tr::lng_boosts_list_tab_gifts(
|
||||
tr::now,
|
||||
lt_count,
|
||||
status.firstSliceGifts.multipliedTotal);
|
||||
if (hasOneTab) {
|
||||
Ui::AddSkip(inner);
|
||||
const auto header = inner->add(
|
||||
object_ptr<Statistic::Header>(inner),
|
||||
st::statisticsLayerMargins
|
||||
+ st::boostsChartHeaderPadding);
|
||||
header->resizeToWidth(header->width());
|
||||
header->setTitle(hasBoosts ? boostsTabText : giftsTabText);
|
||||
header->setSubTitle({});
|
||||
}
|
||||
|
||||
const auto slider = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::CustomWidthSlider>>(
|
||||
inner,
|
||||
object_ptr<Ui::CustomWidthSlider>(
|
||||
inner,
|
||||
st::dialogsSearchTabs)));
|
||||
if (!hasOneTab) {
|
||||
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(inner);
|
||||
shadow->show();
|
||||
slider->geometryValue(
|
||||
) | rpl::on_next([=](const QRect &r) {
|
||||
shadow->setGeometry(
|
||||
inner->x(),
|
||||
rect::bottom(r) - shadow->height(),
|
||||
inner->width(),
|
||||
shadow->height());
|
||||
}, shadow->lifetime());
|
||||
}
|
||||
slider->toggle(!hasOneTab, anim::type::instant);
|
||||
|
||||
slider->entity()->addSection(boostsTabText);
|
||||
slider->entity()->addSection(giftsTabText);
|
||||
|
||||
{
|
||||
const auto &st = st::defaultTabsSlider;
|
||||
slider->entity()->setNaturalWidth(0
|
||||
+ st.labelStyle.font->width(boostsTabText)
|
||||
+ st.labelStyle.font->width(giftsTabText)
|
||||
+ rect::m::sum::h(st::boxRowPadding));
|
||||
}
|
||||
|
||||
const auto boostsWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto giftsWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
|
||||
rpl::single(hasOneTab ? (hasGifts ? 1 : 0) : 0) | rpl::then(
|
||||
slider->entity()->sectionActivated()
|
||||
) | rpl::on_next([=](int index) {
|
||||
boostsWrap->toggle(!index, anim::type::instant);
|
||||
giftsWrap->toggle(index, anim::type::instant);
|
||||
}, inner->lifetime());
|
||||
|
||||
Statistics::AddBoostsList(
|
||||
status.firstSliceBoosts,
|
||||
boostsWrap->entity(),
|
||||
boostClicked,
|
||||
_peer,
|
||||
tr::lng_boosts_title());
|
||||
Statistics::AddBoostsList(
|
||||
status.firstSliceGifts,
|
||||
giftsWrap->entity(),
|
||||
std::move(boostClicked),
|
||||
_peer,
|
||||
tr::lng_boosts_title());
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDividerText(inner, status.overview.group
|
||||
? tr::lng_boosts_list_subtext_group()
|
||||
: tr::lng_boosts_list_subtext());
|
||||
}
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddSkip(inner);
|
||||
AddHeader(inner, tr::lng_boosts_link_title);
|
||||
Ui::AddSkip(inner, st::boostsLinkSkip);
|
||||
FillShareLink(inner, _show, status.link, _peer);
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDividerText(inner, status.overview.group
|
||||
? tr::lng_boosts_link_subtext_group()
|
||||
: tr::lng_boosts_link_subtext());
|
||||
|
||||
FillGetBoostsButton(inner, _controller, _show, _peer, reloadOnDone);
|
||||
|
||||
resizeToWidth(width());
|
||||
crl::on_main(this, [=]{ fakeShowed->fire({}); });
|
||||
}
|
||||
|
||||
void InnerWidget::saveState(not_null<Memento*> memento) {
|
||||
memento->setState(base::take(_state));
|
||||
}
|
||||
|
||||
void InnerWidget::restoreState(not_null<Memento*> memento) {
|
||||
_state = memento->state();
|
||||
if (!_state.link.isEmpty()) {
|
||||
fill();
|
||||
} else {
|
||||
load();
|
||||
}
|
||||
Ui::RpWidget::resizeToWidth(width());
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
auto InnerWidget::showRequests() const -> rpl::producer<ShowRequest> {
|
||||
return _showRequests.events();
|
||||
}
|
||||
|
||||
void InnerWidget::showFinished() {
|
||||
_showFinished.fire({});
|
||||
}
|
||||
|
||||
not_null<PeerData*> InnerWidget::peer() const {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
} // namespace Info::Boosts
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 "data/data_boosts.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
class Controller;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info::Boosts {
|
||||
|
||||
class Memento;
|
||||
|
||||
class InnerWidget final : public Ui::VerticalLayout {
|
||||
public:
|
||||
struct ShowRequest final {
|
||||
};
|
||||
|
||||
InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
[[nodiscard]] rpl::producer<ShowRequest> showRequests() const;
|
||||
|
||||
void showFinished();
|
||||
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
private:
|
||||
void load();
|
||||
void fill();
|
||||
|
||||
not_null<Controller*> _controller;
|
||||
not_null<PeerData*> _peer;
|
||||
std::shared_ptr<Ui::Show> _show;
|
||||
|
||||
Data::BoostStatus _state;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<ShowRequest> _showRequests;
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::event_stream<bool> _loaded;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Boosts
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
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/channel_statistics/boosts/info_boosts_widget.h"
|
||||
|
||||
#include "info/channel_statistics/boosts/info_boosts_inner_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Info::Boosts {
|
||||
|
||||
Memento::Memento(not_null<Controller*> controller)
|
||||
: ContentMemento(controller->statisticsTag()) {
|
||||
}
|
||||
|
||||
Memento::Memento(not_null<PeerData*> peer)
|
||||
: ContentMemento(Info::Statistics::Tag{ peer, {}, {} }) {
|
||||
}
|
||||
|
||||
Memento::~Memento() = default;
|
||||
|
||||
Section Memento::section() const {
|
||||
return Section(Section::Type::Boosts);
|
||||
}
|
||||
|
||||
void Memento::setState(SavedState state) {
|
||||
_state = std::move(state);
|
||||
}
|
||||
|
||||
Memento::SavedState Memento::state() {
|
||||
return base::take(_state);
|
||||
}
|
||||
|
||||
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,
|
||||
controller->statisticsTag().peer))) {
|
||||
_inner->showRequests(
|
||||
) | rpl::on_next([=](InnerWidget::ShowRequest request) {
|
||||
}, _inner->lifetime());
|
||||
_inner->scrollToRequests(
|
||||
) | rpl::on_next([=](const Ui::ScrollToRequest &request) {
|
||||
scrollTo(request);
|
||||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
not_null<PeerData*> Widget::peer() const {
|
||||
return _inner->peer();
|
||||
}
|
||||
|
||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||
return (memento->statisticsTag().peer == peer());
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
return tr::lng_boosts_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();
|
||||
}
|
||||
|
||||
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::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::Boosts
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 "data/data_boosts.h"
|
||||
#include "info/info_content_widget.h"
|
||||
|
||||
namespace Info::Boosts {
|
||||
|
||||
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;
|
||||
|
||||
using SavedState = Data::BoostStatus;
|
||||
|
||||
void setState(SavedState states);
|
||||
[[nodiscard]] SavedState state();
|
||||
|
||||
private:
|
||||
SavedState _state;
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
[[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);
|
||||
|
||||
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||
|
||||
const not_null<InnerWidget*> _inner;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Info::Boosts
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
using "ui/basic.style";
|
||||
using "boxes/boxes.style";
|
||||
using "statistics/statistics.style";
|
||||
|
||||
channelEarnLearnArrowMargins: margins(-2px, 5px, 0px, 0px);
|
||||
|
||||
channelEarnOverviewTitleSkip: 11px;
|
||||
channelEarnOverviewMajorLabel: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 30px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(15px semibold);
|
||||
}
|
||||
}
|
||||
channelEarnOverviewMinorLabel: FlatLabel(channelEarnOverviewMajorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px semibold);
|
||||
}
|
||||
}
|
||||
channelEarnOverviewMinorLabelSkip: 3px;
|
||||
channelEarnOverviewSubMinorLabel: FlatLabel(channelEarnOverviewMinorLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(13px);
|
||||
}
|
||||
}
|
||||
channelEarnOverviewSubMinorLabelPos: point(4px, 2px);
|
||||
channelEarnSemiboldLabel: FlatLabel(channelEarnOverviewMajorLabel) {
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
channelEarnHeaderLabel: FlatLabel(channelEarnOverviewMajorLabel) {
|
||||
style: TextStyle(statisticsHeaderTitleTextStyle) {
|
||||
}
|
||||
textFg: windowActiveTextFg;
|
||||
}
|
||||
channelEarnHistorySubLabel: FlatLabel(channelEarnOverviewSubMinorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
}
|
||||
}
|
||||
channelEarnHistoryRecipientLabel: FlatLabel(channelEarnOverviewSubMinorLabel) {
|
||||
maxHeight: 0px;
|
||||
minWidth: 100px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px);
|
||||
}
|
||||
}
|
||||
channelEarnHistoryMajorLabel: FlatLabel(channelEarnOverviewMajorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(14px semibold);
|
||||
}
|
||||
}
|
||||
channelEarnHistoryMinorLabel: FlatLabel(channelEarnOverviewMinorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(12px semibold);
|
||||
}
|
||||
}
|
||||
channelEarnHistoryDescriptionLabel: FlatLabel(channelEarnHistoryMajorLabel) {
|
||||
// boxWidth - boxRowPadding = 320 - 24 * 2
|
||||
minWidth: 272px;
|
||||
maxHeight: 0px;
|
||||
align: align(center);
|
||||
}
|
||||
channelEarnHistoryMinorLabelSkip: 2px;
|
||||
channelEarnHistoryOuter: margins(0px, 6px, 0px, 6px);
|
||||
channelEarnHistoryTwoSkip: 5px;
|
||||
channelEarnHistoryThreeSkip: 3px;
|
||||
|
||||
channelEarnHistoryRecipientButton: RoundButton {
|
||||
textFg: transparent;
|
||||
textFgOver: transparent;
|
||||
numbersTextFg: transparent;
|
||||
numbersTextFgOver: transparent;
|
||||
textBg: windowBgOver;
|
||||
textBgOver: windowBgOver;
|
||||
|
||||
numbersSkip: 7px;
|
||||
|
||||
width: 190px;
|
||||
height: 34px;
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
|
||||
textTop: 8px;
|
||||
radius: 8px;
|
||||
|
||||
iconPosition: point(0px, 0px);
|
||||
|
||||
style: semiboldTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgRipple;
|
||||
}
|
||||
}
|
||||
channelEarnHistoryRecipientButtonLabel: FlatLabel(channelEarnHistoryRecipientLabel) {
|
||||
align: align(center);
|
||||
}
|
||||
|
||||
channelEarnBalanceMajorLabel: FlatLabel(channelEarnOverviewMajorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(22px semibold);
|
||||
}
|
||||
}
|
||||
channelEarnBalanceMinorLabel: FlatLabel(channelEarnOverviewMinorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(16px semibold);
|
||||
}
|
||||
}
|
||||
channelEarnBalanceMinorLabelSkip: 6px;
|
||||
channelEarnFadeDuration: 60;
|
||||
|
||||
channelEarnLearnDescription: FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 0px;
|
||||
minWidth: 264px;
|
||||
align: align(top);
|
||||
}
|
||||
|
||||
channelEarnCurrencyCommonMargins: margins(0px, 3px, 1px, 0px);
|
||||
channelEarnCurrencyLearnMargins: margins(0px, 2px, 0px, 0px);
|
||||
|
||||
sponsoredAboutTitleIcon: icon {{ "sponsored/large_about", activeButtonFg }};
|
||||
sponsoredAboutPrivacyIcon: icon {{ "sponsored/privacy_about", boxTextFg }};
|
||||
sponsoredAboutRemoveIcon: icon {{ "sponsored/remove_about", boxTextFg }};
|
||||
sponsoredAboutSplitIcon: icon {{ "sponsored/revenue_split", boxTextFg }};
|
||||
|
||||
channelEarnLearnTitleIcon: icon {{ "sponsored/large_earn", activeButtonFg }};
|
||||
channelEarnLearnChannelIcon: icon {{ "sponsored/channel", boxTextFg }};
|
||||
channelEarnLearnWithdrawalsIcon: icon {{ "sponsored/withdrawals", boxTextFg }};
|
||||
|
||||
sponsoredReportLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: boxTextStyle;
|
||||
minWidth: 150px;
|
||||
}
|
||||
|
||||
botEarnInputField: InputField(defaultInputField) {
|
||||
textMargins: margins(23px, 28px, 0px, 4px);
|
||||
placeholderMargins: margins(-23px, 0px, 0px, 0px);
|
||||
width: 100px;
|
||||
heightMax: 55px;
|
||||
}
|
||||
botEarnLockedButtonLabel: FlatLabel(channelEarnOverviewMajorLabel) {
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(10px semibold);
|
||||
}
|
||||
}
|
||||
botEarnButtonLock: IconEmoji {
|
||||
icon: icon{{ "chat/mini_lock", premiumButtonFg }};
|
||||
padding: margins(-2px, 4px, 0px, 0px);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
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/channel_statistics/earn/earn_format.h"
|
||||
|
||||
#include <QtCore/QLocale>
|
||||
|
||||
namespace Info::ChannelEarn {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMinorPartLength = 9;
|
||||
constexpr auto kMaxChoppedZero = kMinorPartLength - 2;
|
||||
constexpr auto kZero = QChar('0');
|
||||
const auto DecimalPoint = QString() + QLocale().decimalPoint();
|
||||
|
||||
using EarnInt = Data::EarnInt;
|
||||
|
||||
} // namespace
|
||||
|
||||
QString MajorPart(EarnInt value) {
|
||||
const auto string = QString::number(value);
|
||||
const auto diff = int(string.size()) - kMinorPartLength;
|
||||
return (diff <= 0) ? QString(kZero) : string.mid(0, diff);
|
||||
}
|
||||
|
||||
QString MajorPart(CreditsAmount value) {
|
||||
return QString::number(int64(value.value()));
|
||||
}
|
||||
|
||||
QString MinorPart(EarnInt value) {
|
||||
if (!value) {
|
||||
return DecimalPoint + kZero + kZero;
|
||||
}
|
||||
const auto string = QString::number(value);
|
||||
const auto diff = int(string.size()) - kMinorPartLength;
|
||||
const auto result = (diff < 0)
|
||||
? DecimalPoint + u"%1"_q.arg(0, std::abs(diff), 10, kZero) + string
|
||||
: DecimalPoint + string.mid(diff);
|
||||
const auto begin = (result.constData());
|
||||
const auto end = (begin + result.size());
|
||||
auto ch = end - 1;
|
||||
auto zeroCount = 0;
|
||||
while (ch != begin) {
|
||||
if (((*ch) == kZero) && (zeroCount < kMaxChoppedZero)) {
|
||||
zeroCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
ch--;
|
||||
}
|
||||
return result.chopped(zeroCount);
|
||||
}
|
||||
|
||||
QString MinorPart(CreditsAmount value) {
|
||||
static const int DecimalPointLength = DecimalPoint.length();
|
||||
|
||||
const auto fractional = std::abs(int(value.value() * 100)) % 100;
|
||||
auto result = QString(DecimalPointLength + 2, Qt::Uninitialized);
|
||||
|
||||
for (int i = 0; i < DecimalPointLength; ++i) {
|
||||
result[i] = DecimalPoint[i];
|
||||
}
|
||||
|
||||
result[DecimalPointLength] = QChar('0' + fractional / 10);
|
||||
result[DecimalPointLength + 1] = QChar('0' + fractional % 10);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString ToUsd(
|
||||
Data::EarnInt value,
|
||||
float64 rate,
|
||||
int afterFloat) {
|
||||
return ToUsd(CreditsAmount(value), rate, afterFloat);
|
||||
}
|
||||
|
||||
QString ToUsd(
|
||||
CreditsAmount value,
|
||||
float64 rate,
|
||||
int afterFloat) {
|
||||
constexpr auto kApproximately = QChar(0x2248);
|
||||
|
||||
return QString(kApproximately)
|
||||
+ QChar('$')
|
||||
+ QLocale().toString(
|
||||
value.value() * rate,
|
||||
'f',
|
||||
afterFloat ? afterFloat : 2);
|
||||
}
|
||||
|
||||
} // namespace Info::ChannelEarn
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 "data/data_channel_earn.h"
|
||||
|
||||
namespace Info::ChannelEarn {
|
||||
|
||||
[[nodiscard]] QString MajorPart(Data::EarnInt value);
|
||||
[[nodiscard]] QString MajorPart(CreditsAmount value);
|
||||
[[nodiscard]] QString MinorPart(Data::EarnInt value);
|
||||
[[nodiscard]] QString MinorPart(CreditsAmount value);
|
||||
[[nodiscard]] QString ToUsd(
|
||||
Data::EarnInt value,
|
||||
float64 rate,
|
||||
int afterFloat);
|
||||
[[nodiscard]] QString ToUsd(
|
||||
CreditsAmount value,
|
||||
float64 rate,
|
||||
int afterFloat);
|
||||
|
||||
} // namespace Info::ChannelEarn
|
||||
186
Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp
Normal file
186
Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
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/channel_statistics/earn/earn_icons.h"
|
||||
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
#include "ui/rect.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_info.h" // infoIconReport.
|
||||
|
||||
#include <QFile>
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
namespace Ui::Earn {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QByteArray CurrencySvg(const QColor &c) {
|
||||
const auto color = u"rgb(%1,%2,%3)"_q
|
||||
.arg(c.red())
|
||||
.arg(c.green())
|
||||
.arg(c.blue())
|
||||
.toUtf8();
|
||||
return R"(
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72">
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(9.000000, 14.000000)
|
||||
" stroke-width="7.2" stroke=")" + color + R"(">
|
||||
<path d="M2.96014341,0 L50.9898193,0 C51.9732032,-7.06402744e-15
|
||||
52.7703933,0.797190129 52.7703933,1.78057399 C52.7703933,2.08038611
|
||||
52.6946886,2.3753442 52.5502994,2.63809702 L29.699977,44.2200383
|
||||
C28.7527832,45.9436969 26.5876295,46.5731461 24.8639708,45.6259523
|
||||
C24.2556953,45.2916896 23.7583564,44.7869606 23.4331014,44.1738213
|
||||
L1.38718565,2.61498853 C0.926351231,1.74626794 1.25700829,0.668450654
|
||||
2.12572888,0.20761623 C2.38272962,0.0712838007 2.6692209,4.97530809e-16
|
||||
2.96014341,0 Z"></path>
|
||||
<line x1="27" y1="44.4532875" x2="27" y2="0"></line>
|
||||
</g>
|
||||
</g>
|
||||
</svg>)";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QImage IconCurrencyColored(int size, const QColor &c) {
|
||||
const auto s = Size(size);
|
||||
auto svg = QSvgRenderer(CurrencySvg(c));
|
||||
auto image = QImage(
|
||||
s * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
svg.render(&p, Rect(s));
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
QImage IconCurrencyColored(
|
||||
const style::font &font,
|
||||
const QColor &c) {
|
||||
return IconCurrencyColored(font->ascent, c);
|
||||
}
|
||||
|
||||
QByteArray CurrencySvgColored(const QColor &c) {
|
||||
return CurrencySvg(c);
|
||||
}
|
||||
|
||||
QImage MenuIconCurrency(const QSize &size) {
|
||||
auto image = QImage(
|
||||
size * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
auto p = QPainter(&image);
|
||||
st::infoIconReport.paintInCenter(
|
||||
p,
|
||||
Rect(size),
|
||||
st::infoIconFg->c);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Clear);
|
||||
const auto w = st::lineWidth * 6;
|
||||
p.fillRect(
|
||||
QRect(
|
||||
rect::center(Rect(size)).x() - w / 2,
|
||||
rect::center(Rect(size)).y() - w,
|
||||
w,
|
||||
w * 2),
|
||||
Qt::white);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
|
||||
const auto s = Size(st::inviteLinkSubscribeBoxTerms.style.font->ascent);
|
||||
auto svg = QSvgRenderer(CurrencySvg(st::infoIconFg->c));
|
||||
svg.render(
|
||||
&p,
|
||||
QRectF(
|
||||
(size.width() - s.width()) / 2.,
|
||||
(size.height() - s.height()) / 2.,
|
||||
s.width(),
|
||||
s.height()));
|
||||
return image;
|
||||
}
|
||||
|
||||
QImage MenuIconCredits() {
|
||||
constexpr auto kStrokeWidth = 5;
|
||||
const auto sizeShift = st::lineWidth * 1.5;
|
||||
|
||||
auto colorized = [&] {
|
||||
auto f = QFile(Ui::Premium::Svg());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromUtf8(f.readAll()).replace(
|
||||
u"#fff"_q,
|
||||
u"#ffffff00"_q);
|
||||
}();
|
||||
colorized.replace(
|
||||
u"stroke=\"none\""_q,
|
||||
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
auto svg = QSvgRenderer(colorized.toUtf8());
|
||||
svg.setViewBox(svg.viewBox()
|
||||
+ Margins(style::ConvertScale(kStrokeWidth)));
|
||||
|
||||
auto image = QImage(
|
||||
st::menuIconLinks.size() * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
image.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&image);
|
||||
svg.render(&p, Rect(st::menuIconLinks.size()) - Margins(sizeShift));
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> MakeCurrencyIconEmoji(
|
||||
const style::font &font,
|
||||
const QColor &c) {
|
||||
return std::make_unique<Ui::CustomEmoji::Internal>(
|
||||
u"currency_icon:%1:%2"_q.arg(font->height).arg(c.name()),
|
||||
IconCurrencyColored(font, c));
|
||||
}
|
||||
|
||||
Ui::Text::PaletteDependentEmoji IconCreditsEmoji(
|
||||
IconDescriptor descriptor) {
|
||||
return { .factory = [=] {
|
||||
return Ui::GenerateStars(
|
||||
(descriptor.size
|
||||
? descriptor.size
|
||||
: st::defaultTableLabel.style.font->height),
|
||||
1);
|
||||
}, .margin = descriptor.margin.value_or(
|
||||
QMargins{ 0, st::giftBoxByStarsSkip, 0, 0 }) };
|
||||
}
|
||||
|
||||
Ui::Text::PaletteDependentEmoji IconCurrencyEmoji(
|
||||
IconDescriptor descriptor) {
|
||||
return { .factory = [=] {
|
||||
return IconCurrencyColored(
|
||||
descriptor.size ? descriptor.size : st::earnTonIconSize,
|
||||
st::currencyFg->c);
|
||||
}, .margin = descriptor.margin.value_or(st::earnTonIconMargin) };
|
||||
}
|
||||
|
||||
Ui::Text::PaletteDependentEmoji IconCreditsEmojiSmall() {
|
||||
return IconCreditsEmoji({
|
||||
.size = st::giftBoxByStarsStyle.font->height,
|
||||
.margin = QMargins{ 0, st::giftBoxByStarsStarTop, 0, 0 },
|
||||
});
|
||||
}
|
||||
|
||||
Ui::Text::PaletteDependentEmoji IconCurrencyEmojiSmall() {
|
||||
return IconCreditsEmoji({});
|
||||
}
|
||||
|
||||
} // namespace Ui::Earn
|
||||
@@ -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 "ui/text/custom_emoji_helper.h"
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Ui::Earn {
|
||||
|
||||
[[nodiscard]] QImage IconCurrencyColored(int size, const QColor &c);
|
||||
[[nodiscard]] QImage IconCurrencyColored(
|
||||
const style::font &font,
|
||||
const QColor &c);
|
||||
[[nodiscard]] QByteArray CurrencySvgColored(const QColor &c);
|
||||
|
||||
[[nodiscard]] QImage MenuIconCurrency(const QSize &size);
|
||||
[[nodiscard]] QImage MenuIconCredits();
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> MakeCurrencyIconEmoji(
|
||||
const style::font &font,
|
||||
const QColor &c);
|
||||
|
||||
struct IconDescriptor {
|
||||
int size = 0;
|
||||
std::optional<QMargins> margin;
|
||||
};
|
||||
[[nodiscard]] Ui::Text::PaletteDependentEmoji IconCreditsEmoji(
|
||||
IconDescriptor descriptor = {});
|
||||
[[nodiscard]] Ui::Text::PaletteDependentEmoji IconCurrencyEmoji(
|
||||
IconDescriptor descriptor = {});
|
||||
|
||||
[[nodiscard]] Ui::Text::PaletteDependentEmoji IconCreditsEmojiSmall();
|
||||
[[nodiscard]] Ui::Text::PaletteDependentEmoji IconCurrencyEmojiSmall();
|
||||
|
||||
} // namespace Ui::Earn
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
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/channel_statistics/earn/info_channel_earn_widget.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
class FlatLabel;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
class Controller;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info::ChannelEarn {
|
||||
|
||||
class Memento;
|
||||
|
||||
class InnerWidget final : public Ui::VerticalLayout {
|
||||
public:
|
||||
struct ShowRequest final {
|
||||
};
|
||||
|
||||
InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] not_null<PeerData*> peer() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
[[nodiscard]] rpl::producer<ShowRequest> showRequests() const;
|
||||
|
||||
void showFinished();
|
||||
void setInnerFocus();
|
||||
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
private:
|
||||
void load();
|
||||
void fill();
|
||||
|
||||
not_null<Controller*> _controller;
|
||||
not_null<PeerData*> _peer;
|
||||
std::shared_ptr<Ui::Show> _show;
|
||||
|
||||
Memento::SavedState _state;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<ShowRequest> _showRequests;
|
||||
rpl::event_stream<> _showFinished;
|
||||
rpl::event_stream<> _focusRequested;
|
||||
rpl::event_stream<bool> _loaded;
|
||||
rpl::event_stream<> _stateUpdated;
|
||||
|
||||
};
|
||||
|
||||
void AddEmojiToMajor(
|
||||
not_null<Ui::FlatLabel*> label,
|
||||
rpl::producer<CreditsAmount> value,
|
||||
std::optional<bool> isIn,
|
||||
std::optional<QMargins> margins);
|
||||
|
||||
} // namespace Info::ChannelEarn
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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/channel_statistics/earn/info_channel_earn_widget.h"
|
||||
|
||||
#include "info/channel_statistics/earn/info_channel_earn_list.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Info::ChannelEarn {
|
||||
|
||||
Memento::Memento(not_null<Controller*> controller)
|
||||
: ContentMemento(controller->statisticsTag()) {
|
||||
}
|
||||
|
||||
Memento::Memento(not_null<PeerData*> peer)
|
||||
: ContentMemento(Info::Statistics::Tag{ peer, {}, {} }) {
|
||||
}
|
||||
|
||||
Memento::~Memento() = default;
|
||||
|
||||
Section Memento::section() const {
|
||||
return Section(Section::Type::ChannelEarn);
|
||||
}
|
||||
|
||||
void Memento::setState(SavedState state) {
|
||||
_state = std::move(state);
|
||||
}
|
||||
|
||||
Memento::SavedState Memento::state() {
|
||||
return base::take(_state);
|
||||
}
|
||||
|
||||
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,
|
||||
controller->statisticsTag().peer))) {
|
||||
_inner->showRequests(
|
||||
) | rpl::on_next([=](InnerWidget::ShowRequest request) {
|
||||
}, _inner->lifetime());
|
||||
_inner->scrollToRequests(
|
||||
) | rpl::on_next([=](const Ui::ScrollToRequest &request) {
|
||||
scrollTo(request);
|
||||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
not_null<PeerData*> Widget::peer() const {
|
||||
return _inner->peer();
|
||||
}
|
||||
|
||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||
return (memento->statisticsTag().peer == peer());
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
return tr::lng_channel_earn_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();
|
||||
}
|
||||
|
||||
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::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::ChannelEarn
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 "data/data_channel_earn.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_credits_earn.h"
|
||||
#include "info/info_content_widget.h"
|
||||
|
||||
namespace Info::ChannelEarn {
|
||||
|
||||
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;
|
||||
|
||||
struct SavedState final {
|
||||
Data::EarnStatistics currencyEarn;
|
||||
Data::CreditsEarnStatistics creditsEarn;
|
||||
Data::CreditsStatusSlice creditsStatusSlice;
|
||||
PeerId premiumBotId = PeerId(0);
|
||||
bool canViewCurrencyMegagroupEarn = true;
|
||||
};
|
||||
|
||||
void setState(SavedState states);
|
||||
[[nodiscard]] SavedState state();
|
||||
|
||||
private:
|
||||
SavedState _state;
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
[[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);
|
||||
|
||||
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||
|
||||
const not_null<InnerWidget*> _inner;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Info::ChannelEarn
|
||||
Reference in New Issue
Block a user