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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
/*
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 "menu/gift_resale_filter.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/painter.h"
#include "styles/style_credits.h" // giftBoxResaleColorSize
#include "styles/style_media_player.h" // mediaPlayerMenuCheck
namespace Ui {
namespace {
[[nodiscard]] Text::MarkedContext WithRepaint(
const Text::MarkedContext &context,
Fn<void()> repaint) {
auto result = context;
result.repaint = std::move(repaint);
return result;
}
[[nodiscard]] QString SerializeColorData(const QColor &color) {
return u"color:%1,%2,%3,%4"_q
.arg(color.red())
.arg(color.green())
.arg(color.blue())
.arg(color.alpha());
}
[[nodiscard]] bool IsColorData(QStringView data) {
return data.startsWith(u"color:"_q);
}
[[nodiscard]] QColor ParseColorData(QStringView data) {
Expects(data.size() > 12);
const auto parts = data.mid(6).split(',');
Assert(parts.size() == 4);
return QColor(
parts[0].toInt(),
parts[1].toInt(),
parts[2].toInt(),
parts[3].toInt());
}
} // namespace
GiftResaleFilterAction::GiftResaleFilterAction(
not_null<RpWidget*> parent,
const style::Menu &st,
const TextWithEntities &text,
const Text::MarkedContext &context,
QString iconEmojiData,
const style::icon *icon)
: Action(parent, st, new QAction(parent), icon, icon)
, _iconEmoji(iconEmojiData.isEmpty()
? nullptr
: context.customEmojiFactory(
iconEmojiData,
WithRepaint(context, [=] { update(); }))) {
setMarkedText(text, QString(), context);
}
void GiftResaleFilterAction::paintEvent(QPaintEvent *e) {
Action::paintEvent(e);
Painter p(this);
const auto enabled = isEnabled();
const auto selected = isSelected();
const auto fg = selected
? st().itemFgOver
: enabled
? st().itemFg
: st().itemFgDisabled;
if (const auto emoji = _iconEmoji.get()) {
const auto x = st().itemIconPosition.x();
const auto y = (height() - st::emojiSize) / 2;
emoji->paint(p, {
.textColor = fg->c,
.position = { x, y },
});
}
if (_checked) {
const auto &icon = st::mediaPlayerMenuCheck;
const auto skip = st().itemRightSkip;
const auto left = width() - skip - icon.width();
const auto top = (height() - icon.height()) / 2;
icon.paint(p, left, top, width());
}
}
void GiftResaleFilterAction::setChecked(bool checked) {
if (_checked != checked) {
_checked = checked;
update();
}
}
GiftResaleColorEmoji::GiftResaleColorEmoji(QStringView data)
: _color(ParseColorData(data)) {
}
bool GiftResaleColorEmoji::Owns(QStringView data) {
return IsColorData(data);
}
QString GiftResaleColorEmoji::DataFor(QColor color) {
return SerializeColorData(color);
}
int GiftResaleColorEmoji::width() {
return st::giftBoxResaleColorSize;
}
QString GiftResaleColorEmoji::entityData() {
return DataFor(_color);
}
void GiftResaleColorEmoji::paint(QPainter &p, const Context &context) {
auto hq = PainterHighQualityEnabler(p);
p.setBrush(_color);
p.setPen(Qt::NoPen);
p.drawEllipse(
context.position.x(),
context.position.y() + st::giftBoxResaleColorTop,
width(),
width());
}
void GiftResaleColorEmoji::unload() {
}
bool GiftResaleColorEmoji::ready() {
return true;
}
bool GiftResaleColorEmoji::readyInDefaultState() {
return true;
}
} // namespace Ui

View File

@@ -0,0 +1,55 @@
/*
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/widgets/menu/menu_action.h"
#include "ui/text/text_custom_emoji.h"
namespace Ui {
class GiftResaleFilterAction final : public Menu::Action {
public:
GiftResaleFilterAction(
not_null<RpWidget*> parent,
const style::Menu &st,
const TextWithEntities &text,
const Text::MarkedContext &context,
QString iconEmojiData,
const style::icon *icon);
void setChecked(bool checked);
private:
void paintEvent(QPaintEvent *e) override;
const std::unique_ptr<Text::CustomEmoji> _iconEmoji;
bool _checked = false;
};
class GiftResaleColorEmoji final : public Text::CustomEmoji {
public:
GiftResaleColorEmoji(QStringView data);
[[nodiscard]] static bool Owns(QStringView data);
[[nodiscard]] static QString DataFor(QColor color);
int width() override;
QString entityData() override;
void paint(QPainter &p, const Context &context) override;
void unload() override;
bool ready() override;
bool readyInDefaultState() override;
private:
QColor _color;
};
} // namespace Ui

View File

@@ -0,0 +1,206 @@
/*
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 "menu/menu_antispam_validator.h"
#include "apiwrap.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/settings_common.h" // IconDescriptor.
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
namespace AntiSpamMenu {
namespace {
[[nodiscard]] int EnableAntiSpamMinMembers(not_null<ChannelData*> channel) {
return channel->session().appConfig().get<int>(
u"telegram_antispam_group_size_min"_q,
100);
}
[[nodiscard]] UserId AntiSpamUserId(not_null<ChannelData*> channel) {
const auto id = channel->session().appConfig().get<QString>(
u"telegram_antispam_user_id"_q,
QString());
return UserId(id.toULongLong());
}
} // namespace
AntiSpamValidator::AntiSpamValidator(
not_null<Window::SessionController*> controller,
not_null<ChannelData*> channel)
: _channel(channel)
, _controller(controller) {
}
object_ptr<Ui::RpWidget> AntiSpamValidator::createButton() const {
const auto channel = _channel;
auto container = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
(QWidget*)nullptr,
object_ptr<Ui::VerticalLayout>((QWidget*)nullptr));
struct State {
rpl::variable<bool> locked;
rpl::event_stream<bool> toggled;
};
Ui::AddSkip(container->entity());
const auto state = container->lifetime().make_state<State>();
const auto button = container->entity()->add(
EditPeerInfoBox::CreateButton(
container->entity(),
tr::lng_manage_peer_antispam(),
rpl::single(QString()),
[] {},
st::manageGroupTopicsButton,
{ &st::menuIconAntispam }
))->toggleOn(rpl::single(
_channel->antiSpamMode()
) | rpl::then(state->toggled.events()));
container->show(anim::type::instant);
Ui::AddSkip(container->entity());
Ui::AddDividerText(
container->entity(),
tr::lng_manage_peer_antispam_about());
const auto updateLocked = [=] {
const auto min = EnableAntiSpamMinMembers(channel);
const auto locked = (channel->membersCount() < min);
state->locked = locked;
button->setToggleLocked(locked);
};
using UpdateFlag = Data::PeerUpdate::Flag;
_channel->session().changes().peerUpdates(
_channel,
UpdateFlag::Members | UpdateFlag::Admins
) | rpl::on_next(updateLocked, button->lifetime());
updateLocked();
button->toggledValue(
) | rpl::on_next([=, controller = _controller](bool toggled) {
if (state->locked.current() && toggled) {
state->toggled.fire(false);
controller->showToast(tr::lng_manage_peer_antispam_not_enough(
tr::now,
lt_count,
EnableAntiSpamMinMembers(channel),
tr::rich));
} else {
channel->session().api().request(MTPchannels_ToggleAntiSpam(
channel->inputChannel(),
MTP_bool(toggled)
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
}).send();
}
}, button->lifetime());
return container;
}
void AntiSpamValidator::resolveUser(Fn<void()> finish) const {
if (_channel->antiSpamMode()) {
const auto mtpUserId = peerToBareMTPInt(AntiSpamUserId(_channel));
_channel->session().api().request(MTPusers_GetUsers(
MTP_vector<MTPInputUser>(1, MTP_inputUser(mtpUserId, MTPlong()))
)).done([=, channel = _channel](const MTPVector<MTPUser> &result) {
channel->owner().processUsers(result);
finish();
}).fail([=] {
finish();
}).send();
} else {
finish();
}
}
UserData *AntiSpamValidator::maybeAppendUser() const {
if (_channel->antiSpamMode()) {
const auto userId = AntiSpamUserId(_channel);
if (const auto user = _channel->owner().user(userId)) {
return user;
}
}
return nullptr;
}
UserId AntiSpamValidator::userId() const {
return AntiSpamUserId(_channel);
}
void AntiSpamValidator::addAction(
not_null<Ui::PopupMenu*> menu,
FullMsgId fakeId) const {
if (!fakeId) {
return;
}
const auto suggestReport = [&](MsgId eventId) {
const auto text = tr::lng_admin_log_antispam_menu_report_toast(
tr::now,
lt_link,
tr::link(
tr::lng_admin_log_antispam_menu_report_toast_link(
tr::now),
"internal:show"),
tr::rich);
const auto showToast = [=,
window = _controller,
channel = _channel] {
window->showToast({
.text = text,
.filter = [=](
const ClickHandlerPtr&,
Qt::MouseButton) {
ParticipantsBoxController::Start(
window,
channel,
ParticipantsRole::Admins);
return true;
},
.duration = ApiWrap::kJoinErrorDuration,
});
};
menu->addAction(
tr::lng_admin_log_antispam_menu_report(tr::now),
[=, channel = _channel] {
_channel->session().api().request(
MTPchannels_ReportAntiSpamFalsePositive(
channel->inputChannel(),
MTP_int(eventId)
)).done(showToast).send();
},
&st::menuIconReportAntiSpam);
};
{
const auto it = _itemEventMsgIds.find(fakeId);
if (it != end(_itemEventMsgIds)) {
suggestReport(it->second);
menu->addSeparator();
}
}
}
void AntiSpamValidator::addEventMsgId(FullMsgId fakeId, MsgId realId) {
_itemEventMsgIds.emplace(fakeId, realId);
}
} // namespace AntiSpamMenu

View File

@@ -0,0 +1,49 @@
/*
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
template <typename Object>
class object_ptr;
class ChannelData;
class UserData;
namespace Ui {
class PopupMenu;
class RpWidget;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
namespace AntiSpamMenu {
class AntiSpamValidator final {
public:
AntiSpamValidator(
not_null<Window::SessionController*> controller,
not_null<ChannelData*> channel);
[[nodiscard]] object_ptr<Ui::RpWidget> createButton() const;
void resolveUser(Fn<void()> finish) const;
[[nodiscard]] UserData *maybeAppendUser() const;
[[nodiscard]] UserId userId() const;
void addAction(not_null<Ui::PopupMenu*> menu, FullMsgId fakeId) const;
void addEventMsgId(FullMsgId fakeId, MsgId realId);
private:
const not_null<ChannelData*> _channel;
const not_null<Window::SessionController*> _controller;
base::flat_map<FullMsgId, MsgId> _itemEventMsgIds;
};
} // namespace AntiSpamMenu

View File

@@ -0,0 +1,39 @@
/*
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 "menu/menu_check_item.h"
#include "ui/widgets/checkbox.h"
#include "styles/style_media_player.h"
namespace Menu {
void ItemWithCheck::init(bool checked) {
enableMouseSelecting();
AbstractButton::setDisabled(true);
_checkView = std::make_unique<Ui::CheckView>(st::defaultCheck, false);
_checkView->checkedChanges(
) | rpl::on_next([=](bool checked) {
setIcon(checked ? &st::mediaPlayerMenuCheck : nullptr);
}, lifetime());
_checkView->setChecked(checked, anim::type::normal);
AbstractButton::clicks(
) | rpl::on_next([=] {
_checkView->setChecked(
!_checkView->checked(),
anim::type::normal);
}, lifetime());
}
not_null<Ui::CheckView*> ItemWithCheck::checkView() const {
return _checkView.get();
}
} // namespace Menu

View File

@@ -0,0 +1,30 @@
/*
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/widgets/menu/menu_action.h"
namespace Ui {
class CheckView;
} // namespace Ui
namespace Menu {
class ItemWithCheck final : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void init(bool checked);
not_null<Ui::CheckView*> checkView() const;
private:
std::unique_ptr<Ui::CheckView> _checkView;
};
} // namespace Menu

View File

@@ -0,0 +1,264 @@
/*
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 "menu/menu_item_download_files.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/file_utilities.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_session.h"
#include "history/history_inner_widget.h"
#include "history/history_item.h"
#include "history/view/history_view_list_widget.h" // HistoryView::SelectedItem.
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwindow.h"
#include "storage/storage_account.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "styles/style_menu_icons.h"
#include "styles/style_widgets.h"
namespace Menu {
namespace {
using Documents = std::vector<std::pair<not_null<DocumentData*>, FullMsgId>>;
using Photos = std::vector<std::pair<not_null<PhotoData*>, FullMsgId>>;
[[nodiscard]] bool Added(
HistoryItem *item,
Documents &documents,
Photos &photos) {
if (item && !item->forbidsForward()) {
if (const auto media = item->media()) {
if (const auto photo = media->photo()) {
photos.emplace_back(photo, item->fullId());
return true;
} else if (const auto document = media->document()) {
documents.emplace_back(document, item->fullId());
return true;
}
}
}
return false;
}
void AddAction(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> controller,
Documents &&documents,
Photos &&photos,
Fn<void()> callback) {
const auto text = documents.empty()
? tr::lng_context_save_images_selected(tr::now)
: tr::lng_context_save_documents_selected(tr::now);
const auto icon = documents.empty()
? &st::menuIconSaveImage
: &st::menuIconDownload;
const auto shouldShowToast = documents.empty();
const auto weak = base::make_weak(controller);
const auto saveImages = [=](const QString &folderPath) {
const auto controller = weak.get();
if (!controller) {
return;
}
const auto session = &controller->session();
const auto downloadPath = folderPath.isEmpty()
? Core::App().settings().downloadPath()
: folderPath;
const auto path = downloadPath.isEmpty()
? File::DefaultDownloadPath(session)
: (downloadPath == FileDialog::Tmp())
? session->local().tempDirectory()
: downloadPath;
if (path.isEmpty()) {
return;
}
QDir().mkpath(path);
const auto showToast = !shouldShowToast
? Fn<void(const QString &)>(nullptr)
: [=](const QString &lastPath) {
const auto filter = [lastPath](const auto ...) {
File::ShowInFolder(lastPath);
return false;
};
controller->showToast({
.text = (photos.size() > 1
? tr::lng_mediaview_saved_images_to
: tr::lng_mediaview_saved_to)(
tr::now,
lt_downloads,
tr::link(
tr::lng_mediaview_downloads(tr::now),
"internal:show_saved_message"),
tr::marked),
.filter = filter,
.st = &st::defaultToast,
});
};
auto views = std::vector<std::shared_ptr<Data::PhotoMedia>>();
for (const auto &[photo, fullId] : photos) {
if (const auto view = photo->createMediaView()) {
view->wanted(Data::PhotoSize::Large, fullId);
views.push_back(view);
}
}
const auto finalCheck = [=] {
for (const auto &[photo, _] : photos) {
if (photo->loading()) {
return false;
}
}
return true;
};
const auto saveToFiles = [=] {
const auto fullPath = [&](int i) {
return filedialogDefaultName(
u"photo_"_q + QString::number(i),
u".jpg"_q,
path);
};
auto lastPath = QString();
for (auto i = 0; i < views.size(); i++) {
lastPath = fullPath(i + 1);
views[i]->saveToFile(lastPath);
}
if (showToast) {
showToast(lastPath);
}
};
if (finalCheck()) {
saveToFiles();
} else {
auto lifetime = std::make_shared<rpl::lifetime>();
session->downloaderTaskFinished(
) | rpl::on_next([=]() mutable {
if (finalCheck()) {
saveToFiles();
base::take(lifetime)->destroy();
}
}, *lifetime);
}
};
const auto saveDocuments = [=](const QString &folderPath) {
for (const auto &[document, origin] : documents) {
if (!folderPath.isEmpty()) {
document->save(origin, folderPath + document->filename());
} else {
DocumentSaveClickHandler::SaveAndTrack(origin, document);
}
}
};
menu->addAction(text, [=] {
const auto save = [=](const QString &folderPath) {
saveImages(folderPath);
saveDocuments(folderPath);
callback();
};
const auto controller = weak.get();
if (!controller) {
return;
}
if (Core::App().settings().askDownloadPath()) {
const auto initialPath = [] {
const auto path = Core::App().settings().downloadPath();
if (!path.isEmpty() && path != FileDialog::Tmp()) {
return path.left(path.size()
- (path.endsWith('/') ? 1 : 0));
}
return QString();
}();
const auto handleFolder = [=](const QString &result) {
if (!result.isEmpty()) {
const auto folderPath = result.endsWith('/')
? result
: (result + '/');
save(folderPath);
}
};
FileDialog::GetFolder(
controller->window().widget().get(),
tr::lng_download_path_choose(tr::now),
initialPath,
handleFolder);
} else {
save(QString());
}
}, icon);
}
} // namespace
void AddDownloadFilesAction(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> window,
const std::vector<HistoryView::SelectedItem> &selectedItems,
not_null<HistoryView::ListWidget*> list) {
if (selectedItems.empty()) {
return;
}
auto docs = Documents();
auto photos = Photos();
for (const auto &selectedItem : selectedItems) {
const auto &id = selectedItem.msgId;
const auto item = window->session().data().message(id);
if (!Added(item, docs, photos)) {
return;
}
}
const auto done = [weak = base::make_weak(list)] {
if (const auto strong = weak.get()) {
strong->cancelSelection();
}
};
AddAction(menu, window, std::move(docs), std::move(photos), done);
}
void AddDownloadFilesAction(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> window,
const base::flat_map<HistoryItem*, TextSelection, std::less<>> &items,
not_null<HistoryInner*> list) {
if (items.empty()) {
return;
}
auto sortedItems = ranges::views::all(items)
| ranges::views::keys
| ranges::to<std::vector>();
ranges::sort(sortedItems, {}, &HistoryItem::fullId);
auto docs = Documents();
auto photos = Photos();
for (const auto &item : sortedItems) {
if (!Added(item, docs, photos)) {
return;
}
}
const auto done = [weak = base::make_weak(list)] {
if (const auto strong = weak.get()) {
strong->clearSelected();
}
};
AddAction(menu, window, std::move(docs), std::move(photos), done);
}
} // namespace Menu

View File

@@ -0,0 +1,41 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class HistoryInner;
class HistoryItem;
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace HistoryView {
class ListWidget;
struct SelectedItem;
} // namespace HistoryView
namespace Window {
class SessionController;
} // namespace Window
namespace Menu {
void AddDownloadFilesAction(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> window,
const std::vector<HistoryView::SelectedItem> &selectedItems,
not_null<HistoryView::ListWidget*> list);
void AddDownloadFilesAction(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> window,
// From the legacy history inner widget.
const base::flat_map<HistoryItem*, TextSelection, std::less<>> &items,
not_null<HistoryInner*> list);
} // namespace Menu

View File

@@ -0,0 +1,166 @@
/*
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 "menu/menu_item_rate_transcribe.h"
#include "base/call_delayed.h"
#include "lang/lang_keys.h"
#include "ui/rect.h"
#include "ui/toast/toast.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
namespace Menu {
namespace {
constexpr auto kDuration = crl::time(5000);
} // namespace
RateTranscribe::RateTranscribe(
not_null<Ui::PopupMenu*> parent,
const style::Menu &st,
Fn<void(bool)> rate)
: Ui::Menu::ItemBase(parent, st)
, _dummyAction(Ui::CreateChild<QAction>(this)) {
setAcceptBoth(true);
initResizeHook(parent->sizeValue());
enableMouseSelecting();
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
Ui::AddSkip(content);
const auto label = content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_context_rate_transcription(),
st::boxDividerLabel),
style::margins(),
style::al_top);
setMinWidth(
label->st().style.font->width(
tr::lng_context_rate_transcription(tr::now)));
widthValue() | rpl::on_next([=](int w) {
content->resizeToWidth(parentWidget()->width());
}, content->lifetime());
Ui::AddSkip(content);
// const auto leftButton = Ui::CreateChild<Ui::IconButton>(
// this,
// st::menuTranscribeDummyButton);
// const auto rightButton = Ui::CreateChild<Ui::IconButton>(
// this,
// st::menuTranscribeDummyButton);
const auto leftButton = Ui::CreateSimpleCircleButton(
this,
st::defaultRippleAnimation);
{
leftButton->resize(Size(st::menuTranscribeDummyButton.width));
const auto label = Ui::CreateChild<Ui::FlatLabel>(
leftButton,
QString::fromUtf8("\U0001F44D"));
label->setAttribute(Qt::WA_TransparentForMouseEvents, true);
leftButton->sizeValue() | rpl::on_next([=](QSize s) {
label->moveToLeft(
(s.width() - label->width()) / 2,
(s.height() - label->height()) / 2);
}, label->lifetime());
}
const auto rightButton = Ui::CreateSimpleCircleButton(
this,
st::defaultRippleAnimation);
{
rightButton->resize(Size(st::menuTranscribeDummyButton.width));
const auto label = Ui::CreateChild<Ui::FlatLabel>(
rightButton,
QString::fromUtf8("\U0001F44E"));
label->setAttribute(Qt::WA_TransparentForMouseEvents, true);
rightButton->sizeValue() | rpl::on_next([=](QSize s) {
label->moveToLeft(
(s.width() - label->width()) / 2,
(s.height() - label->height()) / 2);
}, label->lifetime());
}
{
const auto showToast = [=,
weak = base::make_weak(parent->parentWidget())]{
if (const auto strong = weak.get()) {
base::call_delayed(
st::universalDuration * 1.1,
crl::guard(strong, [=] {
Ui::Toast::Show(strong->window(), {
.text = tr::lng_toast_sent_rate_transcription(
tr::now,
TextWithEntities::Simple),
.duration = kDuration,
});
}));
}
};
const auto hideMenu = [=, weak = base::make_weak(parent)] {
if (const auto strong = weak.get()) {
base::call_delayed(
st::universalDuration,
crl::guard(strong, [=] { strong->hideMenu(false); }));
}
};
leftButton->setClickedCallback([=] {
rate(true);
showToast();
hideMenu();
});
rightButton->setClickedCallback([=] {
rate(false);
showToast();
hideMenu();
});
}
_desiredHeight = rect::m::sum::v(st::menuTranscribeItemPadding)
+ st::menuTranscribeDummyButton.height
+ label->st().style.font->height;
rpl::combine(
content->geometryValue(),
label->geometryValue()
) | rpl::on_next([=](
const QRect &contentRect,
const QRect &labelRect) {
leftButton->moveToLeft(
labelRect.x(),
rect::bottom(contentRect));
rightButton->moveToLeft(
rect::right(labelRect) - rightButton->width(),
rect::bottom(contentRect));
_desiredHeight = rect::m::sum::v(st::menuTranscribeItemPadding)
+ leftButton->height()
+ labelRect.height();
}, leftButton->lifetime());
leftButton->show();
rightButton->show();
}
not_null<QAction*> RateTranscribe::action() const {
return _dummyAction;
}
bool RateTranscribe::isEnabled() const {
return true;
}
int RateTranscribe::contentHeight() const {
return _desiredHeight;
}
} // namespace Menu

View File

@@ -0,0 +1,41 @@
/*
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/widgets/menu/menu_item_base.h"
class HistoryItem;
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Menu {
class RateTranscribe final : public Ui::Menu::ItemBase {
public:
RateTranscribe(
not_null<Ui::PopupMenu*> parent,
const style::Menu &st,
Fn<void(bool)> rate);
not_null<QAction*> action() const override;
bool isEnabled() const override;
protected:
int contentHeight() const override;
private:
int _desiredHeight = 0;
not_null<QAction*> _dummyAction;
};
bool HasRateTranscribeItem(not_null<HistoryItem*>);
} // namespace Menu

View File

@@ -0,0 +1,46 @@
/*
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 "menu/menu_item_rate_transcribe_session.h"
#include "api/api_transcribes.h"
#include "apiwrap.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
namespace Menu {
Fn<void(bool)> RateTranscribeCallbackFactory(
not_null<HistoryItem*> item) {
return [item](bool good) {
item->history()->peer->session().api().transcribes().rate(
item,
good);
};
}
bool HasRateTranscribeItem(not_null<HistoryItem*> item) {
const auto &peer = item->history()->peer;
if (!peer->session().api().transcribes().entry(
item).result.isEmpty()) {
return !peer->session().api().transcribes().isRated(item);
}
return false;
}
} // namespace Menu

View File

@@ -0,0 +1,19 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class HistoryItem;
namespace Menu {
[[nodiscard]] Fn<void(bool)> RateTranscribeCallbackFactory(
not_null<HistoryItem*>);
[[nodiscard]] bool HasRateTranscribeItem(not_null<HistoryItem*>);
} // namespace Menu

View File

@@ -0,0 +1,394 @@
/*
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 "menu/menu_mute.h"
#include "boxes/ringtones_box.h"
#include "data/data_session.h"
#include "data/data_thread.h"
#include "data/notify/data_notify_settings.h"
#include "data/notify/data_peer_notify_settings.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "ui/boxes/choose_time.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/time_picker_box.h"
#include "ui/effects/animation_value.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/painter.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h" // infoTopBarMenu
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace MuteMenu {
namespace {
constexpr auto kMuteDurSecondsDefault = crl::time(8) * 3600;
constexpr auto kMuteForeverValue = std::numeric_limits<TimeId>::max();
class IconWithText final : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void setData(const QString &text, const QPoint &iconPosition);
protected:
void paintEvent(QPaintEvent *e) override;
private:
QPoint _iconPosition;
QString _text;
};
void IconWithText::setData(const QString &text, const QPoint &iconPosition) {
_iconPosition = iconPosition;
_text = text;
}
void IconWithText::paintEvent(QPaintEvent *e) {
Ui::Menu::Action::paintEvent(e);
auto p = QPainter(this);
p.setFont(st::menuIconMuteForAnyTextFont);
p.setPen(st::menuIconColor);
p.drawText(_iconPosition, _text);
}
class MuteItem final : public Ui::Menu::Action {
public:
MuteItem(
not_null<RpWidget*> parent,
const style::Menu &st,
Descriptor descriptor);
protected:
void paintEvent(QPaintEvent *e) override;
private:
const QPoint _itemIconPosition;
Ui::Animations::Simple _animation;
bool _isMuted = false;
bool _inited;
};
MuteItem::MuteItem(
not_null<RpWidget*> parent,
const style::Menu &st,
Descriptor descriptor)
: Ui::Menu::Action(
parent,
st,
Ui::CreateChild<QAction>(parent.get()),
nullptr,
nullptr)
, _itemIconPosition(st.itemIconPosition) {
descriptor.isMutedValue(
) | rpl::on_next([=](bool isMuted) {
action()->setText(isMuted
? tr::lng_mute_menu_duration_unmute(tr::now)
: tr::lng_mute_menu_duration_forever(tr::now));
if (_inited && isMuted == _isMuted) {
return;
}
_inited = true;
_isMuted = isMuted;
_animation.start(
[=] { update(); },
isMuted ? 0. : 1.,
isMuted ? 1. : 0.,
st::defaultPopupMenu.showDuration);
}, lifetime());
_animation.stop();
setClickedCallback([=] {
descriptor.updateMutePeriod(_isMuted ? 0 : kMuteForeverValue);
});
}
void MuteItem::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto progress = _animation.value(_isMuted ? 1. : 0.);
const auto color = anim::color(
st::menuIconAttentionColor,
st::boxTextFgGood,
progress);
p.setPen(color);
Action::paintBackground(p, Action::isSelected());
RippleButton::paintRipple(p, 0, 0);
Action::paintText(p);
const auto &icon = _isMuted ? st::menuIconUnmute : st::menuIconMute;
icon.paint(p, _itemIconPosition, width(), color);
}
void MuteBox(not_null<Ui::GenericBox*> box, Descriptor descriptor) {
struct State {
int lastSeconds = 0;
};
auto chooseTimeResult = ChooseTimeWidget(box, kMuteDurSecondsDefault);
box->addRow(std::move(chooseTimeResult.widget));
const auto state = box->lifetime().make_state<State>();
box->setTitle(tr::lng_mute_box_title());
auto confirmText = std::move(
chooseTimeResult.secondsValue
) | rpl::map([=](int seconds) {
state->lastSeconds = seconds;
return !seconds
? tr::lng_mute_menu_unmute()
: tr::lng_mute_menu_mute();
}) | rpl::flatten_latest();
Ui::ConfirmBox(box, {
.confirmed = [=] {
descriptor.updateMutePeriod(state->lastSeconds);
box->getDelegate()->hideLayer();
},
.confirmText = std::move(confirmText),
.cancelText = tr::lng_cancel(),
});
}
void PickMuteBox(
not_null<Ui::GenericBox*> box,
Descriptor descriptor) {
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
};
const auto seconds = Ui::DefaultTimePickerValues();
const auto phrases = ranges::views::all(
seconds
) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector;
const auto state = box->lifetime().make_state<State>();
const auto pickerCallback = TimePickerBox(box, seconds, phrases, 0);
Ui::ConfirmBox(box, {
.confirmed = [=] {
const auto muteFor = pickerCallback();
descriptor.updateMutePeriod(muteFor);
descriptor.session->settings().addMutePeriod(muteFor);
descriptor.session->saveSettings();
box->closeBox();
},
.confirmText = tr::lng_mute_menu_mute(),
.cancelText = tr::lng_cancel(),
});
box->setTitle(tr::lng_mute_box_title());
const auto top = box->addTopButton(st::infoTopBarMenu);
top->setClickedCallback([=] {
if (state->menu) {
return;
}
state->menu = base::make_unique_q<Ui::PopupMenu>(
top,
st::popupMenuWithIcons);
state->menu->addAction(
tr::lng_manage_messages_ttl_after_custom(tr::now),
[=] { box->getDelegate()->show(Box(MuteBox, descriptor)); },
&st::menuIconCustomize);
state->menu->setDestroyedCallback(crl::guard(top, [=] {
top->setForceRippled(false);
}));
top->setForceRippled(true);
state->menu->popup(QCursor::pos());
});
}
} // namespace
Descriptor ThreadDescriptor(not_null<Data::Thread*> thread) {
const auto weak = base::make_weak(thread);
const auto isMutedValue = [=]() -> rpl::producer<bool> {
if (const auto strong = weak.get()) {
return Info::Profile::NotificationsEnabledValue(
strong
) | rpl::map(!rpl::mappers::_1);
}
return rpl::single(false);
};
const auto currentSound = [=] {
const auto strong = weak.get();
return strong
? strong->owner().notifySettings().sound(strong)
: std::optional<Data::NotifySound>();
};
const auto updateSound = crl::guard(weak, [=](Data::NotifySound sound) {
thread->owner().notifySettings().update(thread, {}, {}, sound);
});
const auto updateMutePeriod = crl::guard(weak, [=](TimeId mute) {
const auto settings = &thread->owner().notifySettings();
if (!mute) {
settings->update(thread, { .unmute = true });
} else if (mute == kMuteForeverValue) {
settings->update(thread, { .forever = true });
} else {
settings->update(thread, { .period = mute });
}
});
return {
.session = &thread->session(),
.isMutedValue = isMutedValue,
.currentSound = currentSound,
.updateSound = updateSound,
.updateMutePeriod = updateMutePeriod,
.volumeController = Data::ThreadRingtonesVolumeController(thread),
};
}
Descriptor DefaultDescriptor(
not_null<Main::Session*> session,
Data::DefaultNotify type) {
const auto settings = &session->data().notifySettings();
const auto isMutedValue = [=]() -> rpl::producer<bool> {
return rpl::single(
rpl::empty
) | rpl::then(
settings->defaultUpdates(type)
) | rpl::map([=] {
return settings->isMuted(type);
});
};
const auto currentSound = [=] {
return settings->defaultSettings(type).sound();
};
const auto updateSound = [=](Data::NotifySound sound) {
settings->defaultUpdate(type, {}, {}, sound);
};
const auto updateMutePeriod = [=](TimeId mute) {
if (!mute) {
settings->defaultUpdate(type, { .unmute = true });
} else if (mute == kMuteForeverValue) {
settings->defaultUpdate(type, { .forever = true });
} else {
settings->defaultUpdate(type, { .period = mute });
}
};
return {
.session = session,
.isMutedValue = isMutedValue,
.currentSound = currentSound,
.updateSound = updateSound,
.updateMutePeriod = updateMutePeriod,
.volumeController = DefaultRingtonesVolumeController(session, type),
};
}
void FillMuteMenu(
not_null<Ui::PopupMenu*> menu,
Descriptor descriptor,
std::shared_ptr<Ui::Show> show) {
const auto session = descriptor.session;
const auto soundSelect = [=] {
if (const auto currentSound = descriptor.currentSound()) {
show->showBox(Box(
RingtonesBox,
session,
*currentSound,
descriptor.updateSound,
descriptor.volumeController));
}
};
menu->addAction(
tr::lng_mute_menu_sound_select(tr::now),
soundSelect,
&st::menuIconSoundSelect);
const auto soundIsNone = descriptor.currentSound().value_or(
Data::NotifySound()
).none;
const auto toggleSound = [=] {
if (auto sound = descriptor.currentSound()) {
sound->none = !soundIsNone;
descriptor.updateSound(*sound);
}
};
menu->addAction(
(soundIsNone
? tr::lng_mute_menu_sound_on(tr::now)
: tr::lng_mute_menu_sound_off(tr::now)),
toggleSound,
soundIsNone ? &st::menuIconSoundOn : &st::menuIconSoundOff);
const auto &st = menu->st().menu;
const auto iconTextPosition = st.itemIconPosition
+ st::menuIconMuteForAnyTextPosition;
for (const auto muteFor : session->settings().mutePeriods()) {
const auto callback = [=, update = descriptor.updateMutePeriod] {
update(muteFor);
};
auto item = base::make_unique_q<IconWithText>(
menu,
st,
Ui::Menu::CreateAction(
menu->menu().get(),
tr::lng_mute_menu_duration_any(
tr::now,
lt_duration,
Ui::FormatMuteFor(muteFor)),
callback),
&st::menuIconMuteForAny,
&st::menuIconMuteForAny);
item->setData(Ui::FormatMuteForTiny(muteFor), iconTextPosition);
menu->addAction(std::move(item));
}
menu->addAction(
tr::lng_mute_menu_duration(tr::now),
[=] { show->showBox(Box(PickMuteBox, descriptor)); },
&st::menuIconMuteFor);
menu->addAction(
base::make_unique_q<MuteItem>(menu, menu->st().menu, descriptor));
}
void SetupMuteMenu(
not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers,
Fn<std::optional<Descriptor>()> makeDescriptor,
std::shared_ptr<Ui::Show> show,
Fn<QPoint()> positionCallback) {
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
};
const auto state = parent->lifetime().make_state<State>();
std::move(
triggers
) | rpl::on_next([=] {
if (state->menu) {
return;
} else if (const auto descriptor = makeDescriptor()) {
state->menu = base::make_unique_q<Ui::PopupMenu>(
parent,
st::popupMenuWithIcons);
FillMuteMenu(state->menu.get(), *descriptor, show);
state->menu->popup(positionCallback
? positionCallback()
: QCursor::pos());
}
}, parent->lifetime());
}
} // namespace MuteMenu

View File

@@ -0,0 +1,78 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/notify/data_peer_notify_volume.h" // VolumeController
namespace Data {
class Thread;
struct NotifySound;
enum class DefaultNotify : uint8_t;
struct VolumeController;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class PopupMenu;
class RpWidget;
class Show;
} // namespace Ui
namespace MuteMenu {
struct Descriptor {
not_null<Main::Session*> session;
Fn<rpl::producer<bool>()> isMutedValue;
Fn<std::optional<Data::NotifySound>()> currentSound;
Fn<void(Data::NotifySound)> updateSound;
Fn<void(TimeId)> updateMutePeriod;
Data::VolumeController volumeController;
};
[[nodiscard]] Descriptor ThreadDescriptor(not_null<Data::Thread*> thread);
[[nodiscard]] Descriptor DefaultDescriptor(
not_null<Main::Session*> session,
Data::DefaultNotify type);
void FillMuteMenu(
not_null<Ui::PopupMenu*> menu,
Descriptor descriptor,
std::shared_ptr<Ui::Show> show);
void SetupMuteMenu(
not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers,
Fn<std::optional<Descriptor>()> makeDescriptor,
std::shared_ptr<Ui::Show> show,
Fn<QPoint()> positionCallback = nullptr);
inline void FillMuteMenu(
not_null<Ui::PopupMenu*> menu,
not_null<Data::Thread*> thread,
std::shared_ptr<Ui::Show> show) {
FillMuteMenu(menu, ThreadDescriptor(thread), std::move(show));
}
inline void SetupMuteMenu(
not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers,
Fn<Data::Thread*()> makeThread,
std::shared_ptr<Ui::Show> show,
Fn<QPoint()> positionCallback = nullptr) {
SetupMuteMenu(parent, std::move(triggers), [=] {
const auto thread = makeThread();
return thread
? ThreadDescriptor(thread)
: std::optional<Descriptor>();
}, std::move(show), positionCallback);
}
} // namespace MuteMenu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
/*
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 "api/api_common.h"
namespace style {
struct ComposeIcons;
struct PopupMenu;
} // namespace style
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui {
class PopupMenu;
class RpWidget;
class Show;
} // namespace Ui
namespace Data {
class Thread;
} // namespace Data
namespace SendMenu {
enum class Type : uchar {
Disabled,
SilentOnly,
Scheduled,
ScheduledToUser, // For "Send when online".
Reminder,
EditCommentPrice,
};
enum class SpoilerState : uchar {
None,
Enabled,
Possible,
};
enum class CaptionState : uchar {
None,
Below,
Above,
};
struct Details {
Type type = Type::Disabled;
SpoilerState spoiler = SpoilerState::None;
CaptionState caption = CaptionState::None;
TextWithTags commentPreview;
QString commentStreamerName;
std::optional<uint64> price;
std::optional<uint64> commentPriceMin;
bool effectAllowed = false;
};
enum class FillMenuResult : uchar {
Prepared,
Skipped,
Failed,
};
enum class ActionType : uchar {
Send,
Schedule,
SpoilerOn,
SpoilerOff,
CaptionUp,
CaptionDown,
ChangePrice,
};
struct Action {
using Type = ActionType;
Api::SendOptions options;
Type type = Type::Send;
};
[[nodiscard]] Fn<void(Action, Details)> DefaultCallback(
std::shared_ptr<ChatHelpers::Show> show,
Fn<void(Api::SendOptions)> send);
FillMenuResult FillSendMenu(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> maybeShow,
Details details,
Fn<void(Action, Details)> action,
const style::ComposeIcons *iconsOverride = nullptr,
std::optional<QPoint> desiredPositionOverride = std::nullopt);
FillMenuResult AttachSendMenuEffect(
not_null<Ui::PopupMenu*> menu,
std::shared_ptr<ChatHelpers::Show> show,
Details details,
Fn<void(Action, Details)> action,
std::optional<QPoint> desiredPositionOverride = std::nullopt);
void SetupMenuAndShortcuts(
not_null<Ui::RpWidget*> button,
std::shared_ptr<ChatHelpers::Show> maybeShow,
Fn<Details()> details,
Fn<void(Action, Details)> action,
const style::PopupMenu *stOverride = nullptr,
const style::ComposeIcons *iconsOverride = nullptr);
void SetupUnreadMentionsMenu(
not_null<Ui::RpWidget*> button,
Fn<Data::Thread*()> currentThread);
void SetupUnreadReactionsMenu(
not_null<Ui::RpWidget*> button,
Fn<Data::Thread*()> currentThread);
} // namespace SendMenu

View File

@@ -0,0 +1,514 @@
/*
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 "menu/menu_sponsored.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "core/ui_integration.h" // TextContext
#include "data/components/sponsored_messages.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/report_box_graphics.h" // AddReportOptionButton.
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
#include "styles/style_channel_earn.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_media_view.h"
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
namespace Menu {
namespace {
[[nodiscard]] SponsoredPhrases PhrasesForMessage(FullMsgId fullId) {
return peerIsChannel(fullId.peer)
? SponsoredPhrases::Channel
: SponsoredPhrases::Bot;
}
void AboutBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
SponsoredPhrases phrases,
const Data::SponsoredMessages::Details &details,
Data::SponsoredReportAction report) {
constexpr auto kUrl = "https://promote.telegram.org"_cs;
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
const auto isChannel = (phrases == SponsoredPhrases::Channel);
const auto isSearch = (phrases == SponsoredPhrases::Search);
const auto session = &show->session();
const auto content = box->verticalLayout().get();
const auto levels = Data::LevelLimits(session)
.channelRestrictSponsoredLevelMin();
Ui::AddSkip(content);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto &icon = st::sponsoredAboutTitleIcon;
const auto rect = Rect(icon.size() * 1.4);
auto owned = object_ptr<Ui::RpWidget>(content);
owned->resize(rect.size());
owned->setNaturalWidth(rect.width());
const auto widget = box->addRow(std::move(owned), style::al_top);
widget->paintRequest(
) | rpl::on_next([=] {
auto p = Painter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg);
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}, widget->lifetime());
}
Ui::AddSkip(content);
Ui::AddSkip(content);
box->addRow(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_sponsored_menu_revenued_about(),
st::boxTitle),
style::al_top);
Ui::AddSkip(content);
box->addRow(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_sponsored_revenued_subtitle(),
st::channelEarnLearnDescription),
style::al_top);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto padding = QMargins(
st::settingsButton.padding.left(),
st::boxRowPadding.top(),
st::boxRowPadding.right(),
st::boxRowPadding.bottom());
const auto addEntry = [&](
rpl::producer<QString> title,
rpl::producer<TextWithEntities> about,
const style::icon &icon) {
const auto top = content->add(
object_ptr<Ui::FlatLabel>(
content,
std::move(title),
st::channelEarnSemiboldLabel),
padding);
Ui::AddSkip(content, st::channelEarnHistoryThreeSkip);
const auto label = content->add(
object_ptr<Ui::FlatLabel>(
content,
std::move(about),
st::channelEarnHistoryRecipientLabel),
padding);
const auto left = Ui::CreateChild<Ui::RpWidget>(
box->verticalLayout().get());
left->paintRequest(
) | rpl::on_next([=] {
auto p = Painter(left);
icon.paint(p, 0, 0, left->width());
}, left->lifetime());
left->resize(icon.size());
top->geometryValue(
) | rpl::on_next([=](const QRect &g) {
left->moveToLeft(
(g.left() - left->width()) / 2,
g.top() + st::channelEarnHistoryThreeSkip);
}, left->lifetime());
return label;
};
addEntry(
tr::lng_sponsored_revenued_info1_title(),
(isChannel
? tr::lng_sponsored_revenued_info1_description
: isSearch
? tr::lng_sponsored_revenued_info1_search_description
: tr::lng_sponsored_revenued_info1_bot_description)(
tr::rich),
st::sponsoredAboutPrivacyIcon);
if (!isSearch) {
Ui::AddSkip(content);
Ui::AddSkip(content);
addEntry(
(isChannel
? tr::lng_sponsored_revenued_info2_title
: tr::lng_sponsored_revenued_info2_bot_title)(),
(isChannel
? tr::lng_sponsored_revenued_info2_description
: tr::lng_sponsored_revenued_info2_bot_description)(
tr::rich),
st::sponsoredAboutSplitIcon);
}
Ui::AddSkip(content);
Ui::AddSkip(content);
auto link = tr::lng_settings_privacy_premium_link(
) | rpl::map([](QString t) {
return tr::link(std::move(t), u"internal:"_q);
});
addEntry(
tr::lng_sponsored_revenued_info3_title(),
(isChannel
? tr::lng_sponsored_revenued_info3_description(
lt_count,
rpl::single(float64(levels)),
lt_link,
std::move(link),
tr::rich)
: isSearch
? tr::lng_sponsored_revenued_info3_search_description(
lt_link,
tr::lng_sponsored_revenued_info3_search_link(
lt_arrow,
rpl::single(
Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
tr::marked
) | rpl::map([](TextWithEntities &&link) {
return Ui::Text::Wrapped(
std::move(link),
EntityType::CustomUrl,
u"internal:"_q);
}),
tr::rich)
: tr::lng_sponsored_revenued_info3_bot_description(
lt_link,
std::move(link),
tr::rich)),
st::sponsoredAboutRemoveIcon)->setClickHandlerFilter([=](
const auto &...) {
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
return true;
});
Ui::AddSkip(content);
Ui::AddSkip(content);
}
Ui::AddSkip(content);
Ui::AddSkip(content);
{
box->addRow(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_sponsored_revenued_footer_title(),
st::boxTitle),
style::al_top);
}
Ui::AddSkip(content);
{
const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji);
const auto available = box->width()
- rect::m::sum::h(st::boxRowPadding);
box->addRow(
object_ptr<Ui::FlatLabel>(
content,
(isChannel
? tr::lng_sponsored_revenued_footer_description
: isSearch
? tr::lng_sponsored_revenued_footer_search_description
: tr::lng_sponsored_revenued_footer_bot_description)(
lt_link,
tr::lng_channel_earn_about_link(
lt_emoji,
rpl::single(arrow),
tr::rich
) | rpl::map([=](TextWithEntities t) {
return tr::link(std::move(t), kUrl.utf16());
}),
tr::rich),
st::channelEarnLearnDescription))->resizeToWidth(available);
}
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto &st = st::premiumPreviewDoubledLimitsBox;
box->setStyle(st);
auto button = object_ptr<Ui::RoundButton>(
box,
tr::lng_box_ok(),
st::defaultActiveButton);
button->resizeToWidth(box->width()
- st.buttonPadding.left()
- st.buttonPadding.left());
button->setClickedCallback([=] { box->closeBox(); });
box->addButton(std::move(button));
}
if (!isChannel) {
const auto top = Ui::CreateChild<Ui::IconButton>(
box,
st::infoTopBarMenu);
box->widthValue(
) | rpl::on_next([=](int width) {
top->raise();
top->moveToLeft(
width - top->width() - st::defaultScrollArea.width,
0);
}, top->lifetime());
using MenuPtr = base::unique_qptr<Ui::PopupMenu>;
const auto menu = top->lifetime().make_state<MenuPtr>();
top->setClickedCallback([=] {
*menu = base::make_unique_q<Ui::PopupMenu>(
box->window(),
st::popupMenuWithIcons);
const auto raw = menu->get();
raw->animatePhaseValue(
) | rpl::on_next([=](Ui::PopupMenu::AnimatePhase phase) {
top->setForceRippled(false
|| phase == Ui::PopupMenu::AnimatePhase::Shown
|| phase == Ui::PopupMenu::AnimatePhase::StartShow);
}, top->lifetime());
raw->setDestroyedCallback([=] {
top->setForceRippled(false);
});
FillSponsored(
Ui::Menu::CreateAddActionCallback(menu->get()),
show,
phrases,
details,
report,
{ .skipAbout = true });
const auto global = top->mapToGlobal(
QPoint(top->width() / 4 * 3, top->height() / 2));
raw->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
raw->popup(
QPoint(
global.x(),
std::max(global.y(), QCursor::pos().y())));
return true;
});
}
}
void ShowReportSponsoredBox(
std::shared_ptr<ChatHelpers::Show> show,
Data::SponsoredReportAction report) {
const auto guideLink = tr::link(
tr::lng_report_sponsored_reported_link(tr::now),
u"https://promote.telegram.org/guidelines"_q);
auto performRequest = [=](
const auto &repeatRequest,
Data::SponsoredReportResult::Id id) -> void {
report.callback(id, [=](const Data::SponsoredReportResult &result) {
if (!result.error.isEmpty()) {
show->showToast(result.error);
}
if (!result.options.empty()) {
show->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(rpl::single(result.title));
for (const auto &option : result.options) {
const auto button = Ui::AddReportOptionButton(
box->verticalLayout(),
option.text,
nullptr);
button->setClickedCallback([=] {
repeatRequest(repeatRequest, option.id);
});
}
if (!id.isNull()) {
box->addLeftButton(
tr::lng_create_group_back(),
[=] { box->closeBox(); });
} else {
const auto container = box->verticalLayout();
Ui::AddSkip(container);
container->add(object_ptr<Ui::DividerLabel>(
container,
object_ptr<Ui::FlatLabel>(
container,
tr::lng_report_sponsored_reported_learn(
lt_link,
rpl::single(guideLink),
tr::marked),
st::boxDividerLabel),
st::defaultBoxDividerLabelPadding));
}
box->addButton(
tr::lng_close(),
[=] { show->hideLayer(); });
}));
} else {
constexpr auto kToastDuration = crl::time(4000);
switch (result.result) {
case Data::SponsoredReportResult::FinalStep::Hidden: {
show->showToast(
tr::lng_report_sponsored_hidden(tr::now),
kToastDuration);
} break;
case Data::SponsoredReportResult::FinalStep::Reported: {
auto text = tr::lng_report_sponsored_reported(
tr::now,
lt_link,
guideLink,
tr::marked);
show->showToast({
.text = std::move(text),
.duration = kToastDuration,
});
} break;
case Data::SponsoredReportResult::FinalStep::Premium: {
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
} break;
}
show->hideLayer();
}
});
};
performRequest(performRequest, Data::SponsoredReportResult::Id());
}
} // namespace
void FillSponsored(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
SponsoredPhrases phrases,
const Data::SponsoredMessages::Details &details,
Data::SponsoredReportAction report,
SponsoredMenuSettings settings) {
const auto session = &show->session();
const auto &info = details.info;
const auto dark = settings.dark;
if (!settings.skipInfo && !info.empty()) {
auto fillSubmenu = [&](not_null<Ui::PopupMenu*> menu) {
const auto allText = ranges::accumulate(
info,
TextWithEntities(),
[](TextWithEntities a, TextWithEntities b) {
return a.text.isEmpty() ? b : a.append('\n').append(b);
}).text;
const auto callback = [=] {
TextUtilities::SetClipboardText({ allText });
show->showToast(tr::lng_text_copied(tr::now));
};
for (const auto &i : info) {
auto item = base::make_unique_q<Ui::Menu::MultilineAction>(
menu,
dark ? st::storiesMenu : st::defaultMenu,
(dark
? st::historySponsorInfoItemDark
: st::historySponsorInfoItem),
st::historyHasCustomEmojiPosition,
base::duplicate(i));
item->clicks(
) | rpl::on_next(callback, menu->lifetime());
menu->addAction(std::move(item));
if (i != details.info.back()) {
menu->addSeparator();
}
}
};
addAction({
.text = tr::lng_sponsored_info_menu(tr::now),
.handler = nullptr,
.icon = (dark
? &st::mediaMenuIconChannel
: &st::menuIconChannel),
.fillSubmenu = std::move(fillSubmenu),
});
addAction({
.separatorSt = (dark
? &st::mediaviewMenuSeparator
: &st::expandedMenuSeparator),
.isSeparator = true,
});
}
if (details.canReport) {
if (!settings.skipAbout) {
addAction(tr::lng_sponsored_menu_revenued_about(tr::now), [=] {
show->show(Box(AboutBox, show, phrases, details, report));
}, (dark ? &st::mediaMenuIconInfo : &st::menuIconInfo));
}
addAction(tr::lng_sponsored_menu_revenued_report(tr::now), [=] {
ShowReportSponsoredBox(show, report);
}, (dark ? &st::mediaMenuIconBlock : &st::menuIconBlock));
addAction({
.separatorSt = (dark
? &st::mediaviewMenuSeparator
: &st::expandedMenuSeparator),
.isSeparator = true,
});
}
addAction(tr::lng_sponsored_hide_ads(tr::now), [=] {
if (session->premium()) {
using Result = Data::SponsoredReportResult;
report.callback(Result::Id("-1"), [](const auto &) {});
} else {
ShowPremiumPreviewBox(show, PremiumFeature::NoAds);
}
}, (dark ? &st::mediaMenuIconCancel : &st::menuIconCancel));
}
void FillSponsored(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId,
SponsoredMenuSettings settings) {
const auto session = &show->session();
FillSponsored(
addAction,
show,
PhrasesForMessage(fullId),
session->sponsoredMessages().lookupDetails(fullId),
session->sponsoredMessages().createReportCallback(fullId),
settings);
}
void ShowSponsored(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId) {
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
parent.get(),
st::popupMenuWithIcons);
FillSponsored(
Ui::Menu::CreateAddActionCallback(menu),
show,
fullId);
menu->popup(QCursor::pos());
}
void ShowSponsoredAbout(
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId) {
const auto session = &show->session();
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
AboutBox(
box,
show,
PhrasesForMessage(fullId),
session->sponsoredMessages().lookupDetails(fullId),
session->sponsoredMessages().createReportCallback(fullId));
}));
}
} // namespace Menu

View File

@@ -0,0 +1,65 @@
/*
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 ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct SponsoredMessageDetails;
struct SponsoredReportAction;
} // namespace Data
namespace Ui {
class RpWidget;
namespace Menu {
struct MenuCallback;
} // namespace Menu
} // namespace Ui
class HistoryItem;
namespace Menu {
enum class SponsoredPhrases {
Channel,
Bot,
Search,
};
struct SponsoredMenuSettings {
bool dark = false;
bool skipAbout = false;
bool skipInfo = false;
};
void FillSponsored(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
SponsoredPhrases phrases,
const Data::SponsoredMessageDetails &details,
Data::SponsoredReportAction report,
SponsoredMenuSettings settings = {});
void FillSponsored(
const Ui::Menu::MenuCallback &addAction,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId,
SponsoredMenuSettings settings = {});
void ShowSponsored(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId);
void ShowSponsoredAbout(
std::shared_ptr<ChatHelpers::Show> show,
const FullMsgId &fullId);
} // namespace Menu

View File

@@ -0,0 +1,277 @@
/*
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 "menu/menu_ttl.h"
#include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
#include "ui/boxes/time_picker_box.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/ui_utility.h"
#include "ui/widgets/labels.h"
#if 0
#include "ui/boxes/choose_time.h"
#include "ui/widgets/menu/menu_action.h"
#include "ui/widgets/popup_menu.h"
#include "styles/style_dialogs.h" // dialogsScamFont
#include "styles/style_menu_icons.h"
#endif
#include "styles/style_layers.h"
namespace TTLMenu {
namespace {
#if 0
constexpr auto kTTLDurHours1 = crl::time(1);
constexpr auto kTTLDurSeconds1 = kTTLDurHours1 * 3600;
constexpr auto kTTLDurHours2 = crl::time(24);
constexpr auto kTTLDurSeconds2 = kTTLDurHours2 * 3600;
constexpr auto kTTLDurHours3 = crl::time(24 * 7);
constexpr auto kTTLDurSeconds3 = kTTLDurHours3 * 3600;
constexpr auto kTTLDurHours4 = crl::time(24 * 30);
constexpr auto kTTLDurSeconds4 = kTTLDurHours4 * 3600;
// See menu_mute.cpp.
class IconWithText final : public Ui::Menu::Action {
public:
using Ui::Menu::Action::Action;
void setData(const QString &text, const QPoint &iconPosition);
protected:
void paintEvent(QPaintEvent *e) override;
private:
QPoint _iconPosition;
QString _text;
};
void IconWithText::setData(const QString &text, const QPoint &iconPosition) {
_iconPosition = iconPosition;
_text = text;
}
void IconWithText::paintEvent(QPaintEvent *e) {
Ui::Menu::Action::paintEvent(e);
Painter p(this);
p.setFont(st::dialogsScamFont);
p.setPen(st::menuIconColor);
p.drawText(_iconPosition, _text);
}
class TextItem final : public Ui::Menu::ItemBase {
public:
TextItem(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<QString> &&text);
not_null<QAction*> action() const override;
bool isEnabled() const override;
protected:
int contentHeight() const override;
private:
const base::unique_qptr<Ui::FlatLabel> _label;
const not_null<QAction*> _dummyAction;
};
TextItem::TextItem(
not_null<RpWidget*> parent,
const style::Menu &st,
rpl::producer<QString> &&text)
: ItemBase(parent, st)
, _label(base::make_unique_q<Ui::FlatLabel>(
this,
std::move(text),
st::historyMessagesTTLLabel))
, _dummyAction(Ui::CreateChild<QAction>(parent.get())) {
setAttribute(Qt::WA_TransparentForMouseEvents);
setMinWidth(st::historyMessagesTTLLabel.minWidth
+ st.itemIconPosition.x());
sizeValue(
) | rpl::on_next([=](const QSize &s) {
_label->moveToLeft(
st.itemIconPosition.x(),
(s.height() - _label->height()) / 2);
}, lifetime());
initResizeHook(parent->sizeValue());
}
not_null<QAction*> TextItem::action() const {
return _dummyAction;
}
bool TextItem::isEnabled() const {
return false;
}
int TextItem::contentHeight() const {
return _label->height();
}
void TTLBoxOld(
not_null<Ui::GenericBox*> box,
Fn<void(TimeId)> callback,
TimeId startTtlPeriod) {
struct State {
int lastSeconds = 0;
};
const auto startTtl = startTtlPeriod ? startTtlPeriod : kTTLDurSeconds2;
auto chooseTimeResult = ChooseTimeWidget(box, startTtl);
box->addRow(std::move(chooseTimeResult.widget));
const auto state = box->lifetime().make_state<State>();
box->setTitle(tr::lng_manage_messages_ttl_title());
auto confirmText = std::move(
chooseTimeResult.secondsValue
) | rpl::map([=](int seconds) {
state->lastSeconds = seconds;
return !seconds
? tr::lng_manage_messages_ttl_disable()
: tr::lng_enable_auto_delete();
}) | rpl::flatten_latest();
const auto confirm = box->addButton(std::move(confirmText), [=] {
callback(state->lastSeconds);
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
#endif
} // namespace
void TTLBox(not_null<Ui::GenericBox*> box, Args args) {
if (args.about) {
box->addRow(object_ptr<Ui::FlatLabel>(
box,
std::move(args.about),
st::boxLabel));
}
const auto ttls = std::vector<TimeId>{
(86400 * 1),
(86400 * 2),
(86400 * 3),
(86400 * 4),
(86400 * 5),
(86400 * 6),
(86400 * 7 * 1),
(86400 * 7 * 2),
(86400 * 7 * 3),
(86400 * 31 * 1),
(86400 * 31 * 2),
(86400 * 31 * 3),
(86400 * 31 * 4),
(86400 * 31 * 5),
(86400 * 31 * 6),
(86400 * 365),
};
const auto phrases = ranges::views::all(
ttls
) | ranges::views::transform(Ui::FormatTTL) | ranges::to_vector;
const auto pickerTtl = TimePickerBox(box, ttls, phrases, args.startTtl);
Ui::ConfirmBox(box, {
.confirmed = [=](Fn<void()> close) {
args.callback(pickerTtl(), std::move(close));
},
.confirmText = tr::lng_settings_save(),
.cancelText = tr::lng_cancel(),
});
box->setTitle(tr::lng_manage_messages_ttl_title());
if (args.startTtl && !args.hideDisable) {
box->addLeftButton(tr::lng_manage_messages_ttl_disable(), [=] {
args.callback(0, [=] { box->closeBox(); });
});
}
}
#if 0
void FillTTLMenu(not_null<Ui::PopupMenu*> menu, Args args) {
const auto &st = menu->st().menu;
const auto iconTextPosition = st.itemIconPosition
+ st::menuIconTTLAnyTextPosition;
const auto addAction = [&](const QString &text, TimeId ttl) {
auto item = base::make_unique_q<IconWithText>(
menu,
st,
Ui::Menu::CreateAction(
menu->menu().get(),
text,
[=] { args.callback(ttl); }),
&st::menuIconTTLAny,
&st::menuIconTTLAny);
item->setData(Ui::FormatTTLTiny(ttl), iconTextPosition);
menu->addAction(std::move(item));
};
addAction(tr::lng_manage_messages_ttl_after1(tr::now), kTTLDurSeconds1);
addAction(tr::lng_manage_messages_ttl_after2(tr::now), kTTLDurSeconds2);
addAction(tr::lng_manage_messages_ttl_after3(tr::now), kTTLDurSeconds3);
addAction(tr::lng_manage_messages_ttl_after4(tr::now), kTTLDurSeconds4);
menu->addAction(
tr::lng_manage_messages_ttl_after_custom(tr::now),
[a = args] { a.show->showBox(Box(TTLBox, a)); },
&st::menuIconCustomize);
if (args.startTtl) {
menu->addAction({
.text = tr::lng_manage_messages_ttl_disable(tr::now),
.handler = [=] { args.callback(0); },
.icon = &st::menuIconDisableAttention,
.isAttention = true,
});
}
menu->addSeparator();
menu->addAction(base::make_unique_q<TextItem>(
menu,
menu->st().menu,
std::move(args.about)));
}
void SetupTTLMenu(
not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers,
Args args) {
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
};
const auto state = parent->lifetime().make_state<State>();
std::move(
triggers
) | rpl::on_next([=] {
if (state->menu) {
return;
}
state->menu = base::make_unique_q<Ui::PopupMenu>(
parent,
st::popupMenuExpandedSeparator);
FillTTLMenu(state->menu.get(), args);
state->menu->popup(QCursor::pos());
}, parent->lifetime());
}
#endif
} // namespace TTLMenu

View File

@@ -0,0 +1,39 @@
/*
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 {
#if 0
class PopupMenu;
class RpWidget;
#endif
class Show;
class GenericBox;
} // namespace Ui
namespace TTLMenu {
struct Args {
std::shared_ptr<Ui::Show> show;
TimeId startTtl;
rpl::producer<TextWithEntities> about;
Fn<void(TimeId, Fn<void()>)> callback;
bool hideDisable = false;
};
void TTLBox(not_null<Ui::GenericBox*> box, Args args);
#if 0
void FillTTLMenu(not_null<Ui::PopupMenu*> menu, Args args);
void SetupTTLMenu(
not_null<Ui::RpWidget*> parent,
rpl::producer<> triggers,
Args args);
#endif
} // namespace TTLMenu

View File

@@ -0,0 +1,144 @@
/*
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 "menu/menu_ttl_validator.h"
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "menu/menu_ttl.h"
#include "ui/layers/generic_box.h"
#include "ui/layers/show.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
namespace TTLMenu {
namespace {
constexpr auto kToastDuration = crl::time(3500);
void ShowAutoDeleteToast(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer) {
const auto period = peer->messagesTTL();
if (!period) {
show->showToast(tr::lng_ttl_about_tooltip_off(tr::now));
return;
}
const auto duration = (period == 5)
? u"5 seconds"_q
: Ui::FormatTTL(period);
const auto text = peer->isBroadcast()
? tr::lng_ttl_about_tooltip_channel(tr::now, lt_duration, duration)
: tr::lng_ttl_about_tooltip(tr::now, lt_duration, duration);
show->showToast(text, kToastDuration);
}
} // namespace
TTLValidator::TTLValidator(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer)
: _peer(peer)
, _show(std::move(show)) {
}
Args TTLValidator::createArgs() const {
const auto peer = _peer;
const auto show = _show;
struct State {
TimeId savingPeriod = 0;
mtpRequestId savingRequestId = 0;
};
const auto state = std::make_shared<State>();
auto callback = [=](
TimeId period,
Fn<void()>) {
auto &api = peer->session().api();
if (state->savingRequestId) {
if (period == state->savingPeriod) {
return;
}
api.request(state->savingRequestId).cancel();
}
state->savingPeriod = period;
state->savingRequestId = api.request(MTPmessages_SetHistoryTTL(
peer->input(),
MTP_int(period)
)).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
ShowAutoDeleteToast(show, peer);
state->savingRequestId = 0;
}).fail([=] {
state->savingRequestId = 0;
}).send();
show->hideLayer();
};
auto about1 = peer->isUser()
? tr::lng_ttl_edit_about(lt_user, rpl::single(peer->shortName()))
: peer->isBroadcast()
? tr::lng_ttl_edit_about_channel()
: tr::lng_ttl_edit_about_group();
auto about2 = tr::lng_ttl_edit_about2(
lt_link,
tr::lng_ttl_edit_about2_link(
) | rpl::map([=](const QString &s) {
return tr::link(s, "tg://settings/auto_delete");
}),
tr::marked);
auto about = rpl::combine(
std::move(about1),
std::move(about2)
) | rpl::map([](const QString &s1, TextWithEntities &&s2) {
return TextWithEntities{ s1 }.append(u"\n\n"_q).append(std::move(s2));
});
const auto ttl = peer->messagesTTL();
return { std::move(show), ttl, std::move(about), std::move(callback) };
}
bool TTLValidator::can() const {
return (_peer->isUser()
&& !_peer->isSelf()
&& !_peer->isNotificationsUser()
&& !_peer->asUser()->isInaccessible()
&& !_peer->asUser()->starsPerMessage()
&& !_peer->asUser()->isVerifyCodes()
&& (!_peer->asUser()->requiresPremiumToWrite()
|| _peer->session().premium()))
|| (_peer->isChat()
&& _peer->asChat()->canEditInformation()
&& _peer->asChat()->amIn())
|| (_peer->isChannel()
&& _peer->asChannel()->canEditInformation()
&& _peer->asChannel()->amIn());
}
void TTLValidator::showToast() const {
ShowAutoDeleteToast(_show, _peer);
}
const style::icon *TTLValidator::icon() const {
return &st::menuIconTTL;
}
void TTLValidator::showBox() const {
if (Main::MakeSessionShow(_show, &_peer->session())->showFrozenError()) {
return;
}
_show->showBox(Box(TTLBox, createArgs()));
}
} // namespace TTLMenu

View File

@@ -0,0 +1,38 @@
/*
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 "menu/menu_ttl.h"
class PeerData;
namespace Ui {
class Show;
} // namespace Ui
namespace TTLMenu {
class TTLValidator final {
public:
TTLValidator(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer);
void showBox() const;
[[nodiscard]] bool can() const;
[[nodiscard]] Args createArgs() const;
void showToast() const;
const style::icon *icon() const;
private:
const not_null<PeerData*> _peer;
const std::shared_ptr<Ui::Show> _show;
};
} // namespace TTLMenu