Name color changing for me / channels.

This commit is contained in:
John Preston 2023-10-27 17:49:37 +04:00
parent effc9873c9
commit bcdb1bdfd2
16 changed files with 952 additions and 195 deletions

View File

@ -794,6 +794,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them.";
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
"lng_settings_color_changed" = "Your name color has been updated!";
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
"lng_suggest_hide_new_title" = "Hide new chats?";
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";

View File

@ -251,68 +251,24 @@ struct GiftCodeLink {
};
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkLabel(
not_null<QWidget*> parent,
rpl::producer<QString> text,
rpl::producer<QString> link,
std::shared_ptr<Ui::Show> show) {
auto result = object_ptr<Ui::AbstractButton>(parent);
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkCopyIcon(
not_null<QWidget*> parent) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
struct State {
State(
not_null<QWidget*> parent,
rpl::producer<QString> value,
rpl::producer<QString> link)
: text(std::move(value))
, link(std::move(link))
, label(parent, text.value(), st::giveawayGiftCodeLink)
, bg(st::roundRadiusLarge, st::windowBgOver) {
}
rpl::variable<QString> text;
rpl::variable<QString> link;
Ui::FlatLabel label;
Ui::RoundRect bg;
};
const auto state = raw->lifetime().make_state<State>(
raw,
rpl::duplicate(text),
std::move(link));
state->label.setSelectable(true);
rpl::combine(
raw->widthValue(),
std::move(text)
) | rpl::start_with_next([=](int outer, const auto&) {
const auto textWidth = state->label.textMaxWidth();
const auto skipLeft = st::giveawayGiftCodeLink.margin.left();
const auto skipRight = st::giveawayGiftCodeLinkCopyWidth;
const auto available = outer - skipRight - skipLeft;
const auto use = std::min(textWidth, available);
state->label.resizeToWidth(use);
state->label.move(outer - skipRight - use - skipLeft, 0);
}, raw->lifetime());
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
state->bg.paint(p, raw->rect());
const auto outer = raw->width();
const auto width = st::giveawayGiftCodeLinkCopyWidth;
const auto &icon = st::giveawayGiftCodeLinkCopy;
const auto left = outer - width + (width - icon.width()) / 2;
const auto left = (raw->width() - icon.width()) / 2;
const auto top = (raw->height() - icon.height()) / 2;
icon.paint(p, left, top, raw->width());
}, raw->lifetime());
state->label.setAttribute(Qt::WA_TransparentForMouseEvents);
raw->resize(
st::giveawayGiftCodeLinkCopyWidth,
st::giveawayGiftCodeLinkHeight);
raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight);
raw->setClickedCallback([=] {
QGuiApplication::clipboard()->setText(state->link.current());
show->showToast(tr::lng_username_copied(tr::now));
});
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
@ -502,11 +458,12 @@ void GiftCodeBox(
const auto link = MakeGiftCodeLink(&controller->session(), slug);
box->addRow(
MakeLinkLabel(
Ui::MakeLinkLabel(
box,
rpl::single(link.text),
rpl::single(link.link),
box->uiShow()),
box->uiShow(),
MakeLinkCopyIcon(box)),
st::giveawayGiftCodeLinkMargin);
auto table = box->addRow(

View File

@ -7,14 +7,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/edit_peer_color_box.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "info/boosts/info_boosts_widget.h"
#include "info/info_memento.h"
#include "lang/lang_keys.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h"
#include "ui/boxes/boost_box.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
@ -23,32 +47,97 @@ namespace {
using namespace Settings;
class ColorSample final : public Ui::RpWidget {
constexpr auto kFakeChannelId = ChannelId(0xFFFFFFF000ULL);
constexpr auto kFakeWebPageId = WebPageId(0xFFFFFFFF00000000ULL);
constexpr auto kSelectAnimationDuration = crl::time(150);
class ColorSample final : public Ui::AbstractButton {
public:
ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> st,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndex,
const QString &name);
ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
uint8 colorIndex,
bool selected);
[[nodiscard]] uint8 index() const;
int naturalWidth() const override;
void setSelected(bool selected);
private:
void paintEvent(QPaintEvent *e) override;
std::shared_ptr<Ui::ChatStyle> _st;
std::shared_ptr<Ui::ChatStyle> _style;
Ui::Text::String _name;
uint8 _index = 0;
Ui::Animations::Simple _selectAnimation;
bool _selected = false;
bool _simple = false;
};
class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
public:
PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update);
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
HistoryView::Context elementContext() override;
private:
const not_null<QWidget*> _parent;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
};
class PreviewWrap final : public Ui::RpWidget {
public:
PreviewWrap(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme,
not_null<PeerData*> peer,
rpl::producer<uint8> colorIndexValue);
~PreviewWrap();
private:
using Element = HistoryView::Element;
void paintEvent(QPaintEvent *e) override;
void initElements();
const not_null<Ui::GenericBox*> _box;
const not_null<PeerData*> _peer;
const not_null<ChannelData*> _fake;
const not_null<History*> _history;
const not_null<WebPageData*> _webpage;
const std::shared_ptr<Ui::ChatTheme> _theme;
const std::shared_ptr<Ui::ChatStyle> _style;
const std::unique_ptr<PreviewDelegate> _delegate;
const not_null<HistoryItem*> _replyToItem;
const not_null<HistoryItem*> _replyItem;
std::unique_ptr<Element> _element;
Ui::PeerUserpicView _userpic;
QPoint _position;
};
ColorSample::ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> st,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndex,
const QString &name)
: RpWidget(parent)
, _st(st)
: AbstractButton(parent)
, _style(style)
, _name(st::semiboldTextStyle, name) {
std::move(
colorIndex
@ -58,11 +147,35 @@ ColorSample::ColorSample(
}, lifetime());
}
ColorSample::ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
uint8 colorIndex,
bool selected)
: AbstractButton(parent)
, _style(style)
, _index(colorIndex)
, _selected(selected)
, _simple(true) {
}
void ColorSample::setSelected(bool selected) {
if (_selected == selected) {
return;
}
_selected = selected;
_selectAnimation.start(
[=] { update(); },
_selected ? 0. : 1.,
_selected ? 1. : 0.,
kSelectAnimationDuration);
}
void ColorSample::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
auto hq = PainterHighQualityEnabler(p);
const auto colors = _st->coloredValues(false, _index);
if (!colors.outlines[1].alpha()) {
const auto colors = _style->coloredValues(false, _index);
if (!_simple && !colors.outlines[1].alpha()) {
const auto radius = height() / 2;
p.setPen(Qt::NoPen);
p.setBrush(colors.bg);
@ -81,32 +194,52 @@ void ColorSample::paintEvent(QPaintEvent *e) {
1,
style::al_top);
} else {
const auto size = width();
const auto size = float64(width());
const auto half = size / 2.;
const auto full = QRectF(-half, -half, size, size);
p.translate(size / 2., size / 2.);
p.rotate(-45.);
p.setPen(Qt::NoPen);
p.setClipRect(-size, -size, 3 * size, size);
if (colors.outlines[1].alpha()) {
p.rotate(-45.);
p.setClipRect(-size, 0, 3 * size, size);
p.setBrush(colors.outlines[1]);
p.drawEllipse(full);
p.setClipRect(-size, -size, 3 * size, size);
}
p.setBrush(colors.outlines[0]);
p.drawEllipse(-half, -half, size, size);
p.setClipRect(-size, 0, 3 * size, size);
p.setBrush(colors.outlines[1]);
p.drawEllipse(-half, -half, size, size);
p.drawEllipse(full);
p.setClipping(false);
if (colors.outlines[2].alpha()) {
const auto center = st::settingsColorSampleCenter;
const auto radius = st::settingsColorSampleCenterRadius;
p.setClipping(false);
const auto multiplier = size / st::settingsColorSampleSize;
const auto center = st::settingsColorSampleCenter * multiplier;
const auto radius = st::settingsColorSampleCenterRadius
* multiplier;
p.setBrush(colors.outlines[2]);
p.drawRoundedRect(
QRectF(-center / 2., -center / 2., center, center),
radius,
radius);
}
const auto selected = _selectAnimation.value(_selected ? 1. : 0.);
if (selected > 0) {
const auto line = st::settingsColorRadioStroke * 1.;
const auto thickness = selected * line;
auto pen = st::boxBg->p;
pen.setWidthF(thickness);
p.setBrush(Qt::NoBrush);
p.setPen(pen);
const auto skip = 1.5 * line;
p.drawEllipse(full.marginsRemoved({ skip, skip, skip, skip }));
}
}
}
uint8 ColorSample::index() const {
return _index;
}
int ColorSample::naturalWidth() const {
if (_st->colorPatternIndex(_index)) {
if (_name.isEmpty() || _style->colorPatternIndex(_index)) {
return st::settingsColorSampleSize;
}
const auto padding = st::settingsColorSamplePadding;
@ -115,14 +248,463 @@ int ColorSample::naturalWidth() const {
padding.top() + st::semiboldFont->height + padding.bottom());
}
PreviewWrap::PreviewWrap(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme,
not_null<PeerData*> peer,
rpl::producer<uint8> colorIndexValue)
: RpWidget(box)
, _box(box)
, _peer(peer)
, _fake(_peer->owner().channel(kFakeChannelId))
, _history(_fake->owner().history(_fake))
, _webpage(_peer->owner().webpage(
kFakeWebPageId,
WebPageType::Article,
u"internal:peer-color-webpage-preview"_q,
u"internal:peer-color-webpage-preview"_q,
tr::lng_settings_color_link_name(tr::now),
tr::lng_settings_color_link_title(tr::now),
{ tr::lng_settings_color_link_description(tr::now) },
nullptr, // photo
nullptr, // document
WebPageCollage(),
0, // duration
QString(), // author
false, // hasLargeMedia
0)) // pendingTill
, _theme(theme)
, _style(style)
, _delegate(std::make_unique<PreviewDelegate>(box, _style.get(), [=] {
update();
}))
, _replyToItem(_history->addNewLocalMessage(
_history->nextNonHistoryEntryId(),
(MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| MessageFlag::Post),
UserId(), // via
FullReplyTo(),
base::unixtime::now(), // date
_fake->id,
QString(), // postAuthor
TextWithEntities{ _peer->isSelf()
? tr::lng_settings_color_reply(tr::now)
: tr::lng_settings_color_reply_channel(tr::now),
},
MTP_messageMediaEmpty(),
HistoryMessageMarkupData(),
uint64(0)))
, _replyItem(_history->addNewLocalMessage(
_history->nextNonHistoryEntryId(),
(MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| MessageFlag::HasReplyInfo
| MessageFlag::Post),
UserId(), // via
FullReplyTo{ .messageId = _replyToItem->fullId() },
base::unixtime::now(), // date
_fake->id,
QString(), // postAuthor
TextWithEntities{ _peer->isSelf()
? tr::lng_settings_color_text(tr::now)
: tr::lng_settings_color_text_channel(tr::now),
},
MTP_messageMediaWebPage(
MTP_flags(0),
MTP_webPagePending(
MTP_flags(0),
MTP_long(_webpage->id),
MTPstring(),
MTP_int(0))),
HistoryMessageMarkupData(),
uint64(0)))
, _element(_replyItem->createView(_delegate.get()))
, _position(0, st::msgMargin.bottom()) {
_style->apply(_theme.get());
_fake->setName(peer->name(), QString());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
_fake->changeColorIndex(index);
update();
}, lifetime());
const auto session = &_history->session();
session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> view) {
if (view == _element.get()) {
update();
}
}, lifetime());
initElements();
}
PreviewWrap::~PreviewWrap() {
_element = nullptr;
_replyItem->destroy();
_replyToItem->destroy();
}
void PreviewWrap::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto clip = e->rect();
p.setClipRect(clip);
Window::SectionWidget::PaintBackground(
p,
_theme.get(),
QSize(_box->width(), _box->window()->height()),
clip);
auto context = _theme->preparePaintContext(
_style.get(),
rect(),
clip,
!window()->isActiveWindow());
p.translate(_position);
_element->draw(p, context);
if (_element->displayFromPhoto()) {
auto userpicMinBottomSkip = st::historyPaddingBottom
+ st::msgMargin.bottom();
auto userpicBottom = height()
- _element->marginBottom()
- _element->marginTop();
const auto item = _element->data();
const auto userpicTop = userpicBottom - st::msgPhotoSize;
_peer->paintUserpicLeft(
p,
_userpic,
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
}
}
void PreviewWrap::initElements() {
_element->initDimensions();
widthValue(
) | rpl::filter([=](int width) {
return width > st::msgMinWidth;
}) | rpl::start_with_next([=](int width) {
const auto height = _position.y()
+ _element->resizeGetHeight(width)
+ st::msgMargin.top();
resize(width, height);
}, lifetime());
}
PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update)
: _parent(parent)
, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
}
bool PreviewDelegate::elementAnimationsPaused() {
return _parent->window()->isActiveWindow();
}
auto PreviewDelegate::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog;
}
void Set(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex) {
const auto was = peer->colorIndex();
peer->changeColorIndex(colorIndex);
peer->session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::Color);
const auto done = [=] {
show->showToast(peer->isSelf()
? tr::lng_settings_color_changed(tr::now)
: tr::lng_settings_color_changed_channel(tr::now));
};
const auto fail = [=](const MTP::Error &error) {
peer->changeColorIndex(was);
peer->session().changes().peerUpdated(
peer,
Data::PeerUpdate::Flag::Color);
show->showToast(error.type());
};
const auto send = [&](auto &&request) {
peer->session().api().request(
std::move(request)
).done(done).fail(fail).send();
};
if (peer->isSelf()) {
send(MTPaccount_UpdateColor(
MTP_flags(
MTPaccount_UpdateColor::Flag::f_background_emoji_id),
MTP_int(colorIndex),
MTP_long(peer->backgroundEmojiId())));
} else if (const auto channel = peer->asChannel()) {
send(MTPchannels_UpdateColor(
MTP_flags(
MTPchannels_UpdateColor::Flag::f_background_emoji_id),
channel->inputChannel,
MTP_int(colorIndex),
MTP_long(peer->backgroundEmojiId())));
} else {
Unexpected("Invalid peer type in Set(colorIndex).");
}
}
void Apply(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
Fn<void()> close,
Fn<void()> cancel) {
const auto session = &peer->session();
if (peer->colorIndex() == colorIndex) {
close();
} else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast(
show,
tr::lng_settings_color_subscribe(
tr::now,
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_as_premium_required_link(tr::now))),
Ui::Text::WithEntities),
u"name_color"_q);
cancel();
} else if (peer->isSelf()) {
Set(show, peer, colorIndex);
close();
} else {
session->api().request(MTPpremium_GetBoostsStatus(
peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
const auto required = session->account().appConfig().get<int>(
"channel_color_level_min",
5);
if (data.vlevel().v >= required) {
Set(show, peer, colorIndex);
close();
return;
}
const auto next = data.vnext_level_boosts().value_or_empty();
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
}
};
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = {
.level = data.vlevel().v,
.boosts = data.vboosts().v,
.thisLevelBoosts = data.vcurrent_level_boosts().v,
.nextLevelBoosts = next,
},
.requiredLevel = required,
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
}
}
class ColorSelector final : public Ui::RpWidget {
public:
ColorSelector(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<std::vector<uint8>> indices,
uint8 index,
Fn<void(uint8)> callback);
private:
void fillFrom(std::vector<uint8> indices);
int resizeGetHeight(int newWidth) override;
const std::shared_ptr<Ui::ChatStyle> _style;
std::vector<std::unique_ptr<ColorSample>> _samples;
const Fn<void(uint8)> _callback;
uint8 _index = 0;
};
ColorSelector::ColorSelector(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<std::vector<uint8>> indices,
uint8 index,
Fn<void(uint8)> callback)
: RpWidget(box)
, _style(style)
, _callback(std::move(callback))
, _index(index) {
std::move(
indices
) | rpl::start_with_next([=](std::vector<uint8> indices) {
fillFrom(std::move(indices));
}, lifetime());
}
void ColorSelector::fillFrom(std::vector<uint8> indices) {
auto samples = std::vector<std::unique_ptr<ColorSample>>();
const auto add = [&](uint8 index) {
auto i = ranges::find(_samples, index, &ColorSample::index);
if (i != end(_samples)) {
samples.push_back(std::move(*i));
_samples.erase(i);
} else {
samples.push_back(std::make_unique<ColorSample>(
this,
_style,
index,
index == _index));
samples.back()->show();
samples.back()->setClickedCallback([=] {
if (_index != index) {
_callback(index);
ranges::find(
_samples,
_index,
&ColorSample::index
)->get()->setSelected(false);
_index = index;
ranges::find(
_samples,
_index,
&ColorSample::index
)->get()->setSelected(true);
}
});
}
};
for (const auto index : indices) {
add(index);
}
if (!ranges::contains(indices, _index)) {
add(_index);
}
_samples = std::move(samples);
if (width() > 0) {
resizeToWidth(width());
}
}
int ColorSelector::resizeGetHeight(int newWidth) {
if (newWidth <= 0) {
return 0;
}
const auto count = int(_samples.size());
const auto columns = Ui::kSimpleColorIndexCount;
const auto rows = (count + columns - 1) / columns;
const auto skip = st::settingsColorRadioSkip;
const auto size = (newWidth - skip * (columns - 1)) / float64(columns);
const auto isize = int(base::SafeRound(size));
auto top = 0;
auto left = 0.;
for (auto i = 0; i != count; ++i) {
const auto row = i / columns;
const auto column = i % columns;
_samples[i]->resize(isize, isize);
_samples[i]->move(int(base::SafeRound(left)), top);
left += size + skip;
if (!((i + 1) % columns)) {
top += isize + skip;
left = 0.;
}
}
return (top - skip) + ((count % columns) ? (isize + skip) : 0);
}
} // namespace
void EditPeerColorBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> st) {
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme) {
box->setTitle(tr::lng_settings_color_title());
box->setWidth(st::boxWideWidth);
struct State {
rpl::variable<uint8> index;
bool changing = false;
bool applying = false;
};
const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex();
box->addRow(object_ptr<PreviewWrap>(
box,
style,
theme,
peer,
state->index.value()
), {});
const auto appConfig = &peer->session().account().appConfig();
auto indices = rpl::single(
rpl::empty
) | rpl::then(
appConfig->refreshed()
) | rpl::map([=] {
const auto list = appConfig->get<std::vector<int>>(
"peer_colors_available",
{ 0, 1, 2, 3, 4, 5, 6 });
return list | ranges::views::transform([](int i) {
return uint8(i);
}) | ranges::to_vector;
});
const auto margin = st::settingsColorRadioMargin;
const auto skip = st::settingsColorRadioSkip;
box->addRow(
object_ptr<ColorSelector>(
box,
style,
std::move(indices),
state->index.current(),
[=](uint8 index) { state->index = index; }),
{ margin, skip, margin, skip });
const auto container = box->verticalLayout();
AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_about()
: tr::lng_settings_color_about_channel());
box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) {
return;
}
state->applying = true;
Apply(show, peer, state->index.current(), crl::guard(box, [=] {
box->closeBox();
}), crl::guard(box, [=] {
state->applying = false;
}));
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
void AddPeerColorButton(
@ -144,11 +726,16 @@ void AddPeerColorButton(
return peer->colorIndex();
});
const auto name = peer->shortName();
const auto st = std::make_shared<Ui::ChatStyle>(
const auto style = std::make_shared<Ui::ChatStyle>(
peer->session().colorIndicesValue());
const auto theme = std::shared_ptr<Ui::ChatTheme>(
Window::Theme::DefaultChatThemeOn(button->lifetime()));
style->apply(theme.get());
const auto sample = Ui::CreateChild<ColorSample>(
button.get(),
st,
style,
rpl::duplicate(colorIndexValue),
name);
sample->show();
@ -167,7 +754,7 @@ void AddPeerColorButton(
- (st::settingsColorButton.padding.right() - sampleSize)
- st::settingsButton.style.font->width(button)
- st::settingsButtonRightSkip;
if (st->colorPatternIndex(colorIndex)) {
if (style->colorPatternIndex(colorIndex)) {
sample->resize(sampleSize, sampleSize);
} else {
const auto padding = st::settingsColorSamplePadding;
@ -188,7 +775,7 @@ void AddPeerColorButton(
const auto right = st::settingsColorButton.padding.right()
- st::settingsColorSampleSkip
- st::settingsColorSampleSize
- (st->colorPatternIndex(colorIndex)
- (style->colorPatternIndex(colorIndex)
? 0
: st::settingsColorSamplePadding.right());
sample->move(
@ -197,4 +784,8 @@ void AddPeerColorButton(
}, sample->lifetime());
sample->setAttribute(Qt::WA_TransparentForMouseEvents);
button->setClickedCallback([=] {
show->show(Box(EditPeerColorBox, show, peer, style, theme));
});
}

View File

@ -14,6 +14,7 @@ class Show;
namespace Ui {
class GenericBox;
class ChatStyle;
class ChatTheme;
class VerticalLayout;
} // namespace Ui
@ -21,7 +22,8 @@ void EditPeerColorBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> st = nullptr);
std::shared_ptr<Ui::ChatStyle> style = nullptr,
std::shared_ptr<Ui::ChatTheme> theme = nullptr);
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,

View File

@ -838,6 +838,8 @@ void HistoryMessageReply::paint(
? resolvedMessage->displayFrom()
: resolvedStory
? resolvedStory->peer().get()
: _externalSender
? _externalSender
: nullptr;
const auto backgroundEmojiId = colorPeer
? colorPeer->backgroundEmojiId()

View File

@ -70,39 +70,6 @@ private:
};
[[nodiscard]] std::unique_ptr<Ui::ChatTheme> DefaultThemeOn(
rpl::lifetime &lifetime) {
auto result = std::make_unique<Ui::ChatTheme>();
using namespace Window::Theme;
const auto push = [=, raw = result.get()] {
const auto background = Background();
const auto &paper = background->paper();
raw->setBackground({
.prepared = background->prepared(),
.preparedForTiled = background->preparedForTiled(),
.gradientForFill = background->gradientForFill(),
.colorForFill = background->colorForFill(),
.colors = paper.backgroundColors(),
.patternOpacity = paper.patternOpacity(),
.gradientRotation = paper.gradientRotation(),
.isPattern = paper.isPattern(),
.tile = background->tile(),
});
};
push();
Background()->updates(
) | rpl::start_with_next([=](const BackgroundUpdate &update) {
if (update.type == BackgroundUpdate::Type::New
|| update.type == BackgroundUpdate::Type::Changed) {
push();
}
}, lifetime);
return result;
}
[[nodiscard]] TextWithEntities HighlightParsedLinks(
TextWithEntities text,
const std::vector<MessageLinkRange> &links) {
@ -124,7 +91,6 @@ private:
return text;
}
class PreviewWrap final : public Ui::RpWidget {
public:
PreviewWrap(
@ -195,7 +161,7 @@ PreviewWrap::PreviewWrap(
: RpWidget(box)
, _box(box)
, _history(history)
, _theme(DefaultThemeOn(lifetime()))
, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
, _style(std::make_unique<Ui::ChatStyle>(
history->session().colorIndicesValue()))
, _delegate(std::make_unique<PreviewDelegate>(
@ -376,7 +342,6 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}
auto p = Painter(this);
auto hq = PainterHighQualityEnabler(p);
auto context = _theme->preparePaintContext(
_style.get(),

View File

@ -210,16 +210,14 @@ void InnerWidget::fill() {
fakeShowed->events(),
rpl::single(status.overview.isBoosted),
dividerContent.data(),
Ui::BoostBoxData{
.boost = Ui::BoostCounters{
.level = status.overview.level,
.boosts = status.overview.boostCount,
.thisLevelBoosts
= status.overview.currentLevelBoostCount,
.nextLevelBoosts
= status.overview.nextLevelBoostCount,
.mine = status.overview.isBoosted,
}
Ui::BoostCounters{
.level = status.overview.level,
.boosts = status.overview.boostCount,
.thisLevelBoosts
= status.overview.currentLevelBoostCount,
.nextLevelBoosts
= status.overview.nextLevelBoostCount,
.mine = status.overview.isBoosted,
},
st::statisticsLimitsLinePadding);
inner->add(object_ptr<Ui::DividerLabel>(

View File

@ -567,10 +567,13 @@ filterLinkChatsList: PeerList(peerListBox) {
}
settingsColorSampleSize: 20px;
settingsColorSampleCenter: 8px;
settingsColorSampleCenter: 6px;
settingsColorSampleCenterRadius: 2px;
settingsColorSamplePadding: margins(8px, 2px, 8px, 2px);
settingsColorSampleSkip: 6px;
settingsColorButton: SettingsButton(settingsButton) {
padding: margins(60px, 10px, 48px, 10px);
}
}
settingsColorRadioMargin: 17px;
settingsColorRadioSkip: 13px;
settingsColorRadioStroke: 2px;

View File

@ -41,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup.
#include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/labels.h"
@ -1466,6 +1467,36 @@ QString LookupPremiumRef(PremiumPreview section) {
return QString();
}
void ShowPremiumPromoToast(
std::shared_ptr<ChatHelpers::Show> show,
TextWithEntities textWithLink,
const QString &ref) {
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
const auto toast = std::make_shared<WeakToast>();
(*toast) = show->showToast({
.text = std::move(textWithLink),
.st = &st::defaultMultilineToast,
.duration = Ui::Toast::kDefaultDuration * 2,
.multiline = true,
.filter = crl::guard(&show->session(), [=](
const ClickHandlerPtr &,
Qt::MouseButton button) {
if (button == Qt::LeftButton) {
if (const auto strong = toast->get()) {
strong->hideAnimated();
(*toast) = nullptr;
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
Settings::ShowPremium(controller, ref);
}
return true;
}
}
return false;
}),
});
}
not_null<Ui::GradientButton*> CreateSubscribeButton(
SubscribeButtonArgs &&args) {
Expects(args.show || args.controller);

View File

@ -51,6 +51,11 @@ void StartPremiumPayment(
[[nodiscard]] QString LookupPremiumRef(PremiumPreview section);
void ShowPremiumPromoToast(
std::shared_ptr<ChatHelpers::Show> show,
TextWithEntities textWithLink,
const QString &ref);
struct SubscribeButtonArgs final {
Window::SessionController *controller = nullptr;
not_null<Ui::RpWidget*> parent;

View File

@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_layers.h"
#include "styles/style_premium.h"
#include <QtGui/QGuiApplication>
namespace Ui {
void StartFireworks(not_null<QWidget*> parent) {
@ -57,7 +59,7 @@ void BoostBox(
BoxShowFinishes(box),
state->you.value(),
box->verticalLayout(),
data,
data.boost,
st::boxRowPadding);
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
@ -170,31 +172,188 @@ void BoostBox(
}, button->lifetime());
}
object_ptr<Ui::RpWidget> MakeLinkLabel(
not_null<QWidget*> parent,
rpl::producer<QString> text,
rpl::producer<QString> link,
std::shared_ptr<Ui::Show> show,
object_ptr<Ui::RpWidget> right) {
auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data();
const auto rawRight = right.release();
if (rawRight) {
rawRight->setParent(raw);
rawRight->show();
}
struct State {
State(
not_null<QWidget*> parent,
rpl::producer<QString> value,
rpl::producer<QString> link)
: text(std::move(value))
, link(std::move(link))
, label(parent, text.value(), st::giveawayGiftCodeLink)
, bg(st::roundRadiusLarge, st::windowBgOver) {
}
rpl::variable<QString> text;
rpl::variable<QString> link;
Ui::FlatLabel label;
Ui::RoundRect bg;
};
const auto state = raw->lifetime().make_state<State>(
raw,
rpl::duplicate(text),
std::move(link));
state->label.setSelectable(true);
rpl::combine(
raw->widthValue(),
std::move(text)
) | rpl::start_with_next([=](int outer, const auto&) {
const auto textWidth = state->label.textMaxWidth();
const auto skipLeft = st::giveawayGiftCodeLink.margin.left();
const auto skipRight = rawRight
? rawRight->width()
: st::giveawayGiftCodeLink.margin.right();
const auto available = outer - skipRight - skipLeft;
const auto use = std::min(textWidth, available);
state->label.resizeToWidth(use);
const auto forCenter = (outer - use) / 2;
const auto x = (forCenter < skipLeft)
? skipLeft
: (forCenter > outer - skipRight - use)
? (outer - skipRight - use)
: forCenter;
state->label.moveToLeft(x, st::giveawayGiftCodeLink.margin.top());
}, raw->lifetime());
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
state->bg.paint(p, raw->rect());
}, raw->lifetime());
state->label.setAttribute(Qt::WA_TransparentForMouseEvents);
raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight);
if (rawRight) {
raw->widthValue() | rpl::start_with_next([=](int width) {
rawRight->move(width - rawRight->width(), 0);
}, raw->lifetime());
}
raw->setClickedCallback([=] {
QGuiApplication::clipboard()->setText(state->link.current());
show->showToast(tr::lng_username_copied(tr::now));
});
return result;
}
void AskBoostBox(
not_null<GenericBox*> box,
AskBoostBoxData data,
Fn<void()> openStatistics,
Fn<void()> startGiveaway) {
box->setWidth(st::boxWideWidth);
box->setStyle(st::boostBox);
const auto full = !data.boost.nextLevelBoosts;
struct State {
rpl::variable<bool> you = false;
bool submitted = false;
};
const auto state = box->lifetime().make_state<State>(State{
.you = data.boost.mine,
});
FillBoostLimit(
BoxShowFinishes(box),
state->you.value(),
box->verticalLayout(),
data.boost,
st::boxRowPadding);
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
auto title = tr::lng_boost_channel_title_color();
auto text = rpl::combine(
tr::lng_boost_channel_needs_level_color(
lt_count,
rpl::single(float64(data.requiredLevel)),
Ui::Text::RichLangValue),
tr::lng_boost_channel_ask(Ui::Text::RichLangValue)
) | rpl::map([](TextWithEntities &&text, TextWithEntities &&ask) {
return text.append(u"\n\n"_q).append(std::move(ask));
});
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
std::move(title),
st::boostTitle),
st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
std::move(text),
st::boostText),
(st::boxRowPadding
+ QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
auto stats = object_ptr<Ui::IconButton>(box, st::boostLinkStatsButton);
stats->setClickedCallback(openStatistics);
box->addRow(MakeLinkLabel(
box,
rpl::single(data.link),
rpl::single(data.link),
box->uiShow(),
std::move(stats)));
auto submit = tr::lng_boost_channel_ask_button();
const auto button = box->addButton(rpl::duplicate(submit), [=] {
QGuiApplication::clipboard()->setText(data.link);
box->uiShow()->showToast(tr::lng_username_copied(tr::now));
});
rpl::combine(
std::move(submit),
box->widthValue()
) | rpl::start_with_next([=](const QString &, int width) {
const auto &padding = st::boostBox.buttonPadding;
button->resizeToWidth(width
- padding.left()
- padding.right());
button->moveToLeft(padding.left(), button->y());
}, button->lifetime());
}
void FillBoostLimit(
rpl::producer<> showFinished,
rpl::producer<bool> you,
not_null<VerticalLayout*> container,
BoostBoxData data,
BoostCounters data,
style::margins limitLinePadding) {
const auto full = !data.boost.nextLevelBoosts;
const auto full = !data.nextLevelBoosts;
if (data.boost.mine && data.boost.boosts > 0) {
--data.boost.boosts;
if (data.mine && data.boosts > 0) {
--data.boosts;
}
if (full) {
data.boost.nextLevelBoosts = data.boost.boosts
+ (data.boost.mine ? 1 : 0);
data.boost.thisLevelBoosts = 0;
if (data.boost.level > 0) {
--data.boost.level;
data.nextLevelBoosts = data.boosts
+ (data.mine ? 1 : 0);
data.thisLevelBoosts = 0;
if (data.level > 0) {
--data.level;
}
} else if (data.boost.mine
&& data.boost.level > 0
&& data.boost.boosts < data.boost.thisLevelBoosts) {
--data.boost.level;
data.boost.nextLevelBoosts = data.boost.thisLevelBoosts;
data.boost.thisLevelBoosts = 0;
} else if (data.mine
&& data.level > 0
&& data.boosts < data.thisLevelBoosts) {
--data.level;
data.nextLevelBoosts = data.thisLevelBoosts;
data.thisLevelBoosts = 0;
}
const auto addSkip = [&](int skip) {
@ -205,18 +364,18 @@ void FillBoostLimit(
const auto levelWidth = [&](int add) {
return st::normalFont->width(
tr::lng_boost_level(tr::now, lt_count, data.boost.level + add));
tr::lng_boost_level(tr::now, lt_count, data.level + add));
};
const auto paddings = 2 * st::premiumLineTextSkip;
const auto labelLeftWidth = paddings + levelWidth(0);
const auto labelRightWidth = paddings + levelWidth(1);
const auto ratio = [=](int boosts) {
const auto min = std::min(
data.boost.boosts,
data.boost.thisLevelBoosts);
data.boosts,
data.thisLevelBoosts);
const auto max = std::max({
data.boost.boosts,
data.boost.nextLevelBoosts,
data.boosts,
data.nextLevelBoosts,
1,
});
Assert(boosts >= min && boosts <= max);
@ -239,12 +398,12 @@ void FillBoostLimit(
return (first + (index - 1) * other) / available;
};
const auto min = std::min(data.boost.boosts, data.boost.thisLevelBoosts);
const auto now = data.boost.boosts;
const auto max = (data.boost.nextLevelBoosts > min)
? (data.boost.nextLevelBoosts)
: (data.boost.boosts > 0)
? data.boost.boosts
const auto min = std::min(data.boosts, data.thisLevelBoosts);
const auto now = data.boosts;
const auto max = (data.nextLevelBoosts > min)
? (data.nextLevelBoosts)
: (data.boosts > 0)
? data.boosts
: 1;
auto bubbleRowState = (
std::move(you)
@ -280,8 +439,8 @@ void FillBoostLimit(
container,
st::boostLimits,
Premium::LimitRowLabels{
.leftLabel = level(data.boost.level),
.rightLabel = level(data.boost.level + 1),
.leftLabel = level(data.level),
.rightLabel = level(data.level + 1),
.dynamic = true,
},
std::move(ratioValue),

View File

@ -7,10 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/object_ptr.h"
namespace Ui {
void StartFireworks(not_null<QWidget*> parent);
class Show;
class RpWidget;
class GenericBox;
class VerticalLayout;
@ -32,11 +36,30 @@ void BoostBox(
BoostBoxData data,
Fn<void(Fn<void(bool)>)> boost);
struct AskBoostBoxData {
QString link;
BoostCounters boost;
int requiredLevel = 0;
};
void AskBoostBox(
not_null<GenericBox*> box,
AskBoostBoxData data,
Fn<void()> openStatistics,
Fn<void()> startGiveaway);
[[nodiscard]] object_ptr<RpWidget> MakeLinkLabel(
not_null<QWidget*> parent,
rpl::producer<QString> text,
rpl::producer<QString> link,
std::shared_ptr<Show> show,
object_ptr<RpWidget> right);
void FillBoostLimit(
rpl::producer<> showFinished,
rpl::producer<bool> you,
not_null<VerticalLayout*> container,
BoostBoxData data,
BoostCounters data,
style::margins limitLinePadding);
} // namespace Ui

View File

@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "ui/controls/send_as_button.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/painter.h"
#include "window/window_session_controller.h"
#include "main/main_session.h"
@ -168,39 +167,6 @@ rpl::producer<not_null<PeerData*>> ListController::clicked() const {
return _clicked.events();
}
void ShowPremiumPromoToast(not_null<Window::SessionController*> controller) {
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
const auto toast = std::make_shared<WeakToast>();
auto link = Ui::Text::Link(
tr::lng_send_as_premium_required_link(tr::now));
link.entities.push_back(
EntityInText(EntityType::Semibold, 0, link.text.size()));
(*toast) = controller->showToast({
.text = tr::lng_send_as_premium_required(
tr::now,
lt_link,
link,
Ui::Text::WithEntities),
.st = &st::defaultMultilineToast,
.duration = Ui::Toast::kDefaultDuration * 2,
.multiline = true,
.filter = crl::guard(&controller->session(), [=](
const ClickHandlerPtr &,
Qt::MouseButton button) {
if (button == Qt::LeftButton) {
if (const auto strong = toast->get()) {
strong->hideAnimated();
(*toast) = nullptr;
Settings::ShowPremium(controller, "send_as");
return true;
}
}
return false;
}),
});
}
} // namespace
void ChooseSendAsBox(
@ -272,7 +238,17 @@ void SetupSendAsButton(
if (i != end(list)
&& i->premiumRequired
&& !sendAs->session().premium()) {
ShowPremiumPromoToast(window);
Settings::ShowPremiumPromoToast(
window->uiShow(),
tr::lng_send_as_premium_required(
tr::now,
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_as_premium_required_link(
tr::now))),
Ui::Text::WithEntities),
u"send_as"_q);
return false;
}
session->sendAsPeers().saveChosen(peer, sendAs);

View File

@ -288,6 +288,14 @@ giveawayGiftCodeLinkHeight: 42px;
giveawayGiftCodeLinkCopyWidth: 40px;
giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px);
boostLinkStatsButton: IconButton(defaultIconButton) {
width: giveawayGiftCodeLinkCopyWidth;
height: giveawayGiftCodeLinkHeight;
icon: icon{{ "menu/stats", menuIconColor }};
iconOver: icon{{ "menu/stats", menuIconColor }};
ripple: emptyRippleAnimation;
}
giveawayGiftCodeTable: Table(defaultTable) {
labelMinWidth: 91px;
}

View File

@ -1591,5 +1591,36 @@ SendMediaReady PrepareWallPaper(MTP::DcId dcId, const QImage &image) {
QByteArray());
}
std::unique_ptr<Ui::ChatTheme> DefaultChatThemeOn(rpl::lifetime &lifetime) {
auto result = std::make_unique<Ui::ChatTheme>();
const auto push = [=, raw = result.get()] {
const auto background = Background();
const auto &paper = background->paper();
raw->setBackground({
.prepared = background->prepared(),
.preparedForTiled = background->preparedForTiled(),
.gradientForFill = background->gradientForFill(),
.colorForFill = background->colorForFill(),
.colors = paper.backgroundColors(),
.patternOpacity = paper.patternOpacity(),
.gradientRotation = paper.gradientRotation(),
.isPattern = paper.isPattern(),
.tile = background->tile(),
});
};
push();
Background()->updates(
) | rpl::start_with_next([=](const BackgroundUpdate &update) {
if (update.type == BackgroundUpdate::Type::New
|| update.type == BackgroundUpdate::Type::Changed) {
push();
}
}, lifetime);
return result;
}
} // namespace Theme
} // namespace Window

View File

@ -28,6 +28,7 @@ class Controller;
namespace Ui {
struct ChatThemeBackground;
class ChatTheme;
} // namespace Ui
namespace Webview {
@ -309,5 +310,8 @@ bool ReadPaletteValues(
[[nodiscard]] Webview::ThemeParams WebViewParams();
[[nodiscard]] std::unique_ptr<Ui::ChatTheme> DefaultChatThemeOn(
rpl::lifetime &lifetime);
} // namespace Theme
} // namespace Window