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:
@@ -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
|
||||
Reference in New Issue
Block a user