init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
147
Telegram/SourceFiles/menu/gift_resale_filter.cpp
Normal file
147
Telegram/SourceFiles/menu/gift_resale_filter.cpp
Normal 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
|
||||
55
Telegram/SourceFiles/menu/gift_resale_filter.h
Normal file
55
Telegram/SourceFiles/menu/gift_resale_filter.h
Normal 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
|
||||
206
Telegram/SourceFiles/menu/menu_antispam_validator.cpp
Normal file
206
Telegram/SourceFiles/menu/menu_antispam_validator.cpp
Normal 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
|
||||
49
Telegram/SourceFiles/menu/menu_antispam_validator.h
Normal file
49
Telegram/SourceFiles/menu/menu_antispam_validator.h
Normal 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
|
||||
39
Telegram/SourceFiles/menu/menu_check_item.cpp
Normal file
39
Telegram/SourceFiles/menu/menu_check_item.cpp
Normal 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
|
||||
30
Telegram/SourceFiles/menu/menu_check_item.h
Normal file
30
Telegram/SourceFiles/menu/menu_check_item.h
Normal 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
|
||||
264
Telegram/SourceFiles/menu/menu_item_download_files.cpp
Normal file
264
Telegram/SourceFiles/menu/menu_item_download_files.cpp
Normal 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
|
||||
41
Telegram/SourceFiles/menu/menu_item_download_files.h
Normal file
41
Telegram/SourceFiles/menu/menu_item_download_files.h
Normal 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
|
||||
166
Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp
Normal file
166
Telegram/SourceFiles/menu/menu_item_rate_transcribe.cpp
Normal 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
|
||||
41
Telegram/SourceFiles/menu/menu_item_rate_transcribe.h
Normal file
41
Telegram/SourceFiles/menu/menu_item_rate_transcribe.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
394
Telegram/SourceFiles/menu/menu_mute.cpp
Normal file
394
Telegram/SourceFiles/menu/menu_mute.cpp
Normal 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
|
||||
78
Telegram/SourceFiles/menu/menu_mute.h
Normal file
78
Telegram/SourceFiles/menu/menu_mute.h
Normal 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
|
||||
1014
Telegram/SourceFiles/menu/menu_send.cpp
Normal file
1014
Telegram/SourceFiles/menu/menu_send.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
Telegram/SourceFiles/menu/menu_send.h
Normal file
121
Telegram/SourceFiles/menu/menu_send.h
Normal 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
|
||||
514
Telegram/SourceFiles/menu/menu_sponsored.cpp
Normal file
514
Telegram/SourceFiles/menu/menu_sponsored.cpp
Normal 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
|
||||
65
Telegram/SourceFiles/menu/menu_sponsored.h
Normal file
65
Telegram/SourceFiles/menu/menu_sponsored.h
Normal 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
|
||||
277
Telegram/SourceFiles/menu/menu_ttl.cpp
Normal file
277
Telegram/SourceFiles/menu/menu_ttl.cpp
Normal 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
|
||||
39
Telegram/SourceFiles/menu/menu_ttl.h
Normal file
39
Telegram/SourceFiles/menu/menu_ttl.h
Normal 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
|
||||
144
Telegram/SourceFiles/menu/menu_ttl_validator.cpp
Normal file
144
Telegram/SourceFiles/menu/menu_ttl_validator.cpp
Normal 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
|
||||
38
Telegram/SourceFiles/menu/menu_ttl_validator.h
Normal file
38
Telegram/SourceFiles/menu/menu_ttl_validator.h
Normal 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
|
||||
Reference in New Issue
Block a user