mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-27 03:28:19 +02:00
822 lines
22 KiB
C++
822 lines
22 KiB
C++
/*
|
|
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 "settings/business/settings_chat_links.h"
|
|
|
|
#include "api/api_chat_links.h"
|
|
#include "apiwrap.h"
|
|
#include "base/event_filter.h"
|
|
#include "boxes/peers/edit_peer_invite_link.h"
|
|
#include "boxes/peers/edit_peer_invite_links.h"
|
|
#include "boxes/premium_preview_box.h"
|
|
#include "boxes/peer_list_box.h"
|
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
|
#include "chat_helpers/message_field.h"
|
|
#include "chat_helpers/tabbed_panel.h"
|
|
#include "chat_helpers/tabbed_selector.h"
|
|
#include "core/application.h"
|
|
#include "core/ui_integration.h"
|
|
#include "core/core_settings.h"
|
|
#include "data/stickers/data_custom_emoji.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_user.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "main/main_app_config.h"
|
|
#include "main/main_session.h"
|
|
#include "settings/business/settings_recipients_helper.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "ui/controls/emoji_button.h"
|
|
#include "ui/text/text_utilities.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/fields/input_field.h"
|
|
#include "ui/widgets/popup_menu.h"
|
|
#include "ui/wrap/slide_wrap.h"
|
|
#include "ui/wrap/vertical_layout.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/rect.h"
|
|
#include "ui/vertical_list.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "styles/style_chat.h"
|
|
#include "styles/style_chat_helpers.h"
|
|
#include "styles/style_info.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_menu_icons.h"
|
|
#include "styles/style_settings.h"
|
|
|
|
#include <QtGui/QGuiApplication>
|
|
|
|
namespace Settings {
|
|
namespace {
|
|
|
|
constexpr auto kChangesDebounceTimeout = crl::time(1000);
|
|
|
|
using ChatLinkData = Api::ChatLink;
|
|
|
|
class ChatLinks final : public BusinessSection<ChatLinks> {
|
|
public:
|
|
ChatLinks(
|
|
QWidget *parent,
|
|
not_null<Window::SessionController*> controller);
|
|
~ChatLinks();
|
|
|
|
[[nodiscard]] rpl::producer<QString> title() override;
|
|
|
|
const Ui::RoundRect *bottomSkipRounding() const override {
|
|
return &_bottomSkipRounding;
|
|
}
|
|
|
|
private:
|
|
void setupContent(not_null<Window::SessionController*> controller);
|
|
|
|
Ui::RoundRect _bottomSkipRounding;
|
|
|
|
};
|
|
|
|
struct ChatLinkAction {
|
|
enum class Type {
|
|
Copy,
|
|
Share,
|
|
Rename,
|
|
Delete,
|
|
};
|
|
QString link;
|
|
Type type = Type::Copy;
|
|
};
|
|
|
|
class Row;
|
|
|
|
class RowDelegate {
|
|
public:
|
|
virtual not_null<Main::Session*> rowSession() = 0;
|
|
virtual void rowUpdateRow(not_null<Row*> row) = 0;
|
|
virtual void rowPaintIcon(
|
|
QPainter &p,
|
|
int x,
|
|
int y,
|
|
int size) = 0;
|
|
};
|
|
|
|
class Row final : public PeerListRow {
|
|
public:
|
|
Row(not_null<RowDelegate*> delegate, const ChatLinkData &data);
|
|
|
|
void update(const ChatLinkData &data);
|
|
|
|
[[nodiscard]] ChatLinkData data() const;
|
|
|
|
QString generateName() override;
|
|
QString generateShortName() override;
|
|
PaintRoundImageCallback generatePaintUserpicCallback(
|
|
bool forceRound) override;
|
|
|
|
QSize rightActionSize() const override;
|
|
QMargins rightActionMargins() const override;
|
|
void rightActionPaint(
|
|
Painter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
bool selected,
|
|
bool actionSelected) override;
|
|
bool rightActionDisabled() const override {
|
|
return true;
|
|
}
|
|
|
|
void paintStatusText(
|
|
Painter &p,
|
|
const style::PeerListItem &st,
|
|
int x,
|
|
int y,
|
|
int availableWidth,
|
|
int outerWidth,
|
|
bool selected) override;
|
|
|
|
private:
|
|
void updateStatus(const ChatLinkData &data);
|
|
|
|
const not_null<RowDelegate*> _delegate;
|
|
ChatLinkData _data;
|
|
Ui::Text::String _status;
|
|
Ui::Text::String _clicks;
|
|
|
|
};
|
|
|
|
[[nodiscard]] uint64 ComputeRowId(const ChatLinkData &data) {
|
|
return UniqueRowIdFromString(data.link);
|
|
}
|
|
|
|
[[nodiscard]] QString ComputeClicks(const ChatLinkData &link) {
|
|
return link.clicks
|
|
? tr::lng_chat_links_clicks(tr::now, lt_count, link.clicks)
|
|
: tr::lng_chat_links_no_clicks(tr::now);
|
|
}
|
|
|
|
Row::Row(not_null<RowDelegate*> delegate, const ChatLinkData &data)
|
|
: PeerListRow(ComputeRowId(data))
|
|
, _delegate(delegate)
|
|
, _data(data) {
|
|
setCustomStatus(QString());
|
|
updateStatus(data);
|
|
}
|
|
|
|
void Row::updateStatus(const ChatLinkData &data) {
|
|
const auto context = Core::MarkedTextContext{
|
|
.session = _delegate->rowSession(),
|
|
.customEmojiRepaint = [=] { _delegate->rowUpdateRow(this); },
|
|
};
|
|
_status.setMarkedText(
|
|
st::messageTextStyle,
|
|
data.message,
|
|
kMarkupTextOptions,
|
|
context);
|
|
_clicks.setText(st::messageTextStyle, ComputeClicks(data));
|
|
}
|
|
|
|
void Row::update(const ChatLinkData &data) {
|
|
_data = data;
|
|
updateStatus(data);
|
|
refreshName(st::inviteLinkList.item);
|
|
_delegate->rowUpdateRow(this);
|
|
}
|
|
|
|
ChatLinkData Row::data() const {
|
|
return _data;
|
|
}
|
|
|
|
QString Row::generateName() {
|
|
if (!_data.title.isEmpty()) {
|
|
return _data.title;
|
|
}
|
|
auto result = _data.link;
|
|
return result.replace(
|
|
u"https://"_q,
|
|
QString()
|
|
);
|
|
}
|
|
|
|
QString Row::generateShortName() {
|
|
return generateName();
|
|
}
|
|
|
|
PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) {
|
|
return [=](
|
|
QPainter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
int size) {
|
|
_delegate->rowPaintIcon(p, x, y, size);
|
|
};
|
|
}
|
|
|
|
QSize Row::rightActionSize() const {
|
|
return QSize(
|
|
_clicks.maxWidth(),
|
|
st::inviteLinkThreeDotsIcon.height());
|
|
}
|
|
|
|
QMargins Row::rightActionMargins() const {
|
|
return QMargins(
|
|
0,
|
|
(st::inviteLinkList.item.height - rightActionSize().height()) / 2,
|
|
st::inviteLinkThreeDotsSkip,
|
|
0);
|
|
}
|
|
|
|
void Row::rightActionPaint(
|
|
Painter &p,
|
|
int x,
|
|
int y,
|
|
int outerWidth,
|
|
bool selected,
|
|
bool actionSelected) {
|
|
p.setPen(selected ? st::windowSubTextFgOver : st::windowSubTextFg);
|
|
_clicks.draw(p, x, y, outerWidth);
|
|
}
|
|
|
|
void Row::paintStatusText(
|
|
Painter &p,
|
|
const style::PeerListItem &st,
|
|
int x,
|
|
int y,
|
|
int availableWidth,
|
|
int outerWidth,
|
|
bool selected) {
|
|
p.setPen(selected ? st.statusFgOver : st.statusFg);
|
|
_status.draw(p, {
|
|
.position = { x, y },
|
|
.outerWidth = outerWidth,
|
|
.availableWidth = availableWidth,
|
|
.palette = &st::defaultTextPalette,
|
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
|
.now = crl::now(),
|
|
.elisionLines = 1,
|
|
});
|
|
}
|
|
|
|
class LinksController final
|
|
: public PeerListController
|
|
, public RowDelegate
|
|
, public base::has_weak_ptr {
|
|
public:
|
|
explicit LinksController(not_null<Window::SessionController*> window);
|
|
|
|
[[nodiscard]] rpl::producer<int> fullCountValue() const {
|
|
return _count.value();
|
|
}
|
|
|
|
void prepare() override;
|
|
void rowClicked(not_null<PeerListRow*> row) override;
|
|
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
|
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row) override;
|
|
Main::Session &session() const override;
|
|
|
|
not_null<Main::Session*> rowSession() override;
|
|
void rowUpdateRow(not_null<Row*> row) override;
|
|
void rowPaintIcon(
|
|
QPainter &p,
|
|
int x,
|
|
int y,
|
|
int size) override;
|
|
|
|
private:
|
|
void appendRow(const ChatLinkData &data);
|
|
void prependRow(const ChatLinkData &data);
|
|
void updateRow(const ChatLinkData &data);
|
|
bool removeRow(const QString &link);
|
|
|
|
void showRowMenu(
|
|
not_null<PeerListRow*> row,
|
|
bool highlightRow);
|
|
|
|
[[nodiscard]] base::unique_qptr<Ui::PopupMenu> createRowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row);
|
|
|
|
const not_null<Window::SessionController*> _window;
|
|
const not_null<Main::Session*> _session;
|
|
rpl::variable<int> _count;
|
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
|
|
|
QImage _icon;
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
struct LinksList {
|
|
not_null<Ui::RpWidget*> widget;
|
|
not_null<LinksController*> controller;
|
|
};
|
|
|
|
LinksList AddLinksList(
|
|
not_null<Window::SessionController*> window,
|
|
not_null<Ui::VerticalLayout*> container) {
|
|
auto &lifetime = container->lifetime();
|
|
const auto delegate = lifetime.make_state<PeerListContentDelegateShow>(
|
|
window->uiShow());
|
|
const auto controller = lifetime.make_state<LinksController>(window);
|
|
controller->setStyleOverrides(&st::inviteLinkList);
|
|
const auto content = container->add(object_ptr<PeerListContent>(
|
|
container,
|
|
controller));
|
|
delegate->setContent(content);
|
|
controller->setDelegate(delegate);
|
|
|
|
return { content, controller };
|
|
}
|
|
|
|
void EditChatLinkBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Window::SessionController*> controller,
|
|
ChatLinkData data,
|
|
Fn<void(ChatLinkData, Fn<void()> close)> submit) {
|
|
box->setTitle(data.link.isEmpty()
|
|
? tr::lng_chat_link_new_title()
|
|
: tr::lng_chat_link_edit_title());
|
|
|
|
box->setWidth(st::boxWideWidth);
|
|
|
|
Ui::AddDividerText(
|
|
box->verticalLayout(),
|
|
tr::lng_chat_link_description());
|
|
|
|
const auto peer = controller->session().user();
|
|
const auto outer = box->getDelegate()->outerContainer();
|
|
const auto field = box->addRow(
|
|
object_ptr<Ui::InputField>(
|
|
box.get(),
|
|
st::settingsChatLinkField,
|
|
Ui::InputField::Mode::MultiLine,
|
|
tr::lng_chat_link_placeholder()));
|
|
box->setFocusCallback([=] {
|
|
field->setFocusFast();
|
|
});
|
|
|
|
Ui::AddDivider(box->verticalLayout());
|
|
Ui::AddSkip(box->verticalLayout());
|
|
|
|
const auto title = box->addRow(object_ptr<Ui::InputField>(
|
|
box.get(),
|
|
st::defaultInputField,
|
|
tr::lng_chat_link_name(),
|
|
data.title));
|
|
|
|
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
|
|
field->parentWidget(),
|
|
st::defaultComposeFiles.emoji);
|
|
|
|
using Selector = ChatHelpers::TabbedSelector;
|
|
auto &lifetime = box->lifetime();
|
|
const auto emojiPanel = lifetime.make_state<ChatHelpers::TabbedPanel>(
|
|
outer,
|
|
controller,
|
|
object_ptr<Selector>(
|
|
nullptr,
|
|
controller->uiShow(),
|
|
Window::GifPauseReason::Layer,
|
|
Selector::Mode::EmojiOnly));
|
|
emojiPanel->setDesiredHeightValues(
|
|
1.,
|
|
st::emojiPanMinHeight / 2,
|
|
st::emojiPanMinHeight);
|
|
emojiPanel->hide();
|
|
emojiPanel->selector()->setCurrentPeer(peer);
|
|
emojiPanel->selector()->emojiChosen(
|
|
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
|
|
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
|
|
}, field->lifetime());
|
|
emojiPanel->selector()->customEmojiChosen(
|
|
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
|
|
Data::InsertCustomEmoji(field, data.document);
|
|
}, field->lifetime());
|
|
|
|
emojiToggle->installEventFilter(emojiPanel);
|
|
emojiToggle->addClickHandler([=] {
|
|
emojiPanel->toggleAnimated();
|
|
});
|
|
|
|
const auto allow = [](not_null<DocumentData*>) { return true; };
|
|
InitMessageFieldHandlers(
|
|
controller,
|
|
field,
|
|
Window::GifPauseReason::Layer,
|
|
allow);
|
|
Ui::Emoji::SuggestionsController::Init(
|
|
outer,
|
|
field,
|
|
&controller->session(),
|
|
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
|
|
|
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
|
field->setMaxHeight(st::defaultComposeFiles.caption.heightMax);
|
|
|
|
const auto save = [=] {
|
|
auto copy = data;
|
|
copy.title = title->getLastText().trimmed();
|
|
auto textWithTags = field->getTextWithAppliedMarkdown();
|
|
copy.message = TextWithEntities{
|
|
textWithTags.text,
|
|
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
|
|
};
|
|
submit(copy, crl::guard(box, [=] {
|
|
box->closeBox();
|
|
}));
|
|
};
|
|
const auto updateEmojiPanelGeometry = [=] {
|
|
const auto parent = emojiPanel->parentWidget();
|
|
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
|
|
const auto local = parent->mapFromGlobal(global);
|
|
emojiPanel->moveBottomRight(
|
|
local.y(),
|
|
local.x() + emojiToggle->width() * 3);
|
|
};
|
|
const auto filterCallback = [=](not_null<QEvent*> event) {
|
|
const auto type = event->type();
|
|
if (type == QEvent::Move || type == QEvent::Resize) {
|
|
// updateEmojiPanelGeometry uses not only container geometry, but
|
|
// also container children geometries that will be updated later.
|
|
crl::on_main(emojiPanel, updateEmojiPanelGeometry);
|
|
}
|
|
return base::EventFilterResult::Continue;
|
|
};
|
|
base::install_event_filter(emojiPanel, outer, filterCallback);
|
|
|
|
field->submits(
|
|
) | rpl::start_with_next([=] {
|
|
title->setFocus();
|
|
}, field->lifetime());
|
|
field->cancelled(
|
|
) | rpl::start_with_next([=] {
|
|
box->closeBox();
|
|
}, field->lifetime());
|
|
|
|
title->submits(
|
|
) | rpl::start_with_next(save, title->lifetime());
|
|
|
|
rpl::combine(
|
|
box->sizeValue(),
|
|
field->geometryValue()
|
|
) | rpl::start_with_next([=](QSize outer, QRect inner) {
|
|
emojiToggle->moveToLeft(
|
|
inner.x() + inner.width() - emojiToggle->width(),
|
|
inner.y() + st::settingsChatLinkEmojiTop);
|
|
emojiToggle->update();
|
|
crl::on_main(emojiPanel, updateEmojiPanelGeometry);
|
|
}, emojiToggle->lifetime());
|
|
|
|
const auto initial = TextWithTags{
|
|
data.message.text,
|
|
TextUtilities::ConvertEntitiesToTextTags(data.message.entities)
|
|
};
|
|
field->setTextWithTags(initial, Ui::InputField::HistoryAction::Clear);
|
|
auto cursor = field->textCursor();
|
|
cursor.movePosition(QTextCursor::End);
|
|
field->setTextCursor(cursor);
|
|
|
|
const auto checkChangedTimer = lifetime.make_state<base::Timer>([=] {
|
|
if (field->getTextWithAppliedMarkdown() == initial) {
|
|
box->setCloseByOutsideClick(true);
|
|
}
|
|
});
|
|
field->changes(
|
|
) | rpl::start_with_next([=] {
|
|
checkChangedTimer->callOnce(kChangesDebounceTimeout);
|
|
box->setCloseByOutsideClick(false);
|
|
}, field->lifetime());
|
|
|
|
box->addButton(tr::lng_settings_save(), save);
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}
|
|
|
|
void EditChatLink(
|
|
not_null<Window::SessionController*> window,
|
|
not_null<Main::Session*> session,
|
|
ChatLinkData data) {
|
|
const auto submitting = std::make_shared<bool>();
|
|
const auto submit = [=](ChatLinkData data, Fn<void()> close) {
|
|
if (std::exchange(*submitting, true)) {
|
|
return;
|
|
}
|
|
const auto done = crl::guard(window, [=](const auto&) {
|
|
window->showToast(tr::lng_chat_link_saved(tr::now));
|
|
close();
|
|
});
|
|
session->api().chatLinks().edit(
|
|
data.link,
|
|
data.title,
|
|
data.message,
|
|
done);
|
|
};
|
|
window->show(Box(
|
|
EditChatLinkBox,
|
|
window,
|
|
data,
|
|
crl::guard(window, submit)));
|
|
}
|
|
|
|
LinksController::LinksController(
|
|
not_null<Window::SessionController*> window)
|
|
: _window(window)
|
|
, _session(&window->session()) {
|
|
style::PaletteChanged(
|
|
) | rpl::start_with_next([=] {
|
|
_icon = QImage();
|
|
}, _lifetime);
|
|
|
|
_session->api().chatLinks().updates(
|
|
) | rpl::start_with_next([=](const Api::ChatLinkUpdate &update) {
|
|
if (!update.now) {
|
|
if (removeRow(update.was)) {
|
|
delegate()->peerListRefreshRows();
|
|
}
|
|
} else if (update.was.isEmpty()) {
|
|
prependRow(*update.now);
|
|
delegate()->peerListRefreshRows();
|
|
} else {
|
|
updateRow(*update.now);
|
|
}
|
|
}, _lifetime);
|
|
}
|
|
|
|
void LinksController::prepare() {
|
|
auto &&list = _session->api().chatLinks().list()
|
|
| ranges::views::reverse;
|
|
for (const auto &link : list) {
|
|
appendRow(link);
|
|
}
|
|
delegate()->peerListRefreshRows();
|
|
}
|
|
|
|
void LinksController::rowClicked(not_null<PeerListRow*> row) {
|
|
showRowMenu(row, true);
|
|
}
|
|
|
|
void LinksController::showRowMenu(
|
|
not_null<PeerListRow*> row,
|
|
bool highlightRow) {
|
|
delegate()->peerListShowRowMenu(row, highlightRow);
|
|
}
|
|
|
|
void LinksController::rowRightActionClicked(not_null<PeerListRow*> row) {
|
|
delegate()->peerListShowRowMenu(row, true);
|
|
}
|
|
|
|
base::unique_qptr<Ui::PopupMenu> LinksController::rowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row) {
|
|
auto result = createRowContextMenu(parent, row);
|
|
|
|
if (result) {
|
|
// First clear _menu value, so that we don't check row positions yet.
|
|
base::take(_menu);
|
|
|
|
// Here unique_qptr is used like a shared pointer, where
|
|
// not the last destroyed pointer destroys the object, but the first.
|
|
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
|
QWidget *parent,
|
|
not_null<PeerListRow*> row) {
|
|
const auto real = static_cast<Row*>(row.get());
|
|
const auto data = real->data();
|
|
const auto link = data.link;
|
|
auto result = base::make_unique_q<Ui::PopupMenu>(
|
|
parent,
|
|
st::popupMenuWithIcons);
|
|
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
|
|
QGuiApplication::clipboard()->setText(link);
|
|
delegate()->peerListUiShow()->showToast(
|
|
tr::lng_chat_link_copied(tr::now));
|
|
}, &st::menuIconCopy);
|
|
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
|
|
delegate()->peerListUiShow()->showBox(ShareInviteLinkBox(
|
|
_session,
|
|
link,
|
|
tr::lng_chat_link_copied(tr::now)));
|
|
}, &st::menuIconShare);
|
|
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
|
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
|
nullptr,
|
|
link,
|
|
tr::lng_chat_link_qr_title(),
|
|
tr::lng_chat_link_qr_about()));
|
|
}, &st::menuIconQrCode);
|
|
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
|
EditChatLink(_window, _session, data);
|
|
}, &st::menuIconEdit);
|
|
result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] {
|
|
const auto sure = [=](Fn<void()> &&close) {
|
|
_window->session().api().chatLinks().destroy(link, close);
|
|
};
|
|
_window->show(Ui::MakeConfirmBox({
|
|
.text = tr::lng_chat_link_delete_sure(tr::now),
|
|
.confirmed = sure,
|
|
.confirmText = tr::lng_box_delete(tr::now),
|
|
}));
|
|
}, &st::menuIconDelete);
|
|
return result;
|
|
}
|
|
|
|
Main::Session &LinksController::session() const {
|
|
return *_session;
|
|
}
|
|
|
|
void LinksController::appendRow(const ChatLinkData &data) {
|
|
delegate()->peerListAppendRow(std::make_unique<Row>(this, data));
|
|
_count = _count.current() + 1;
|
|
}
|
|
|
|
void LinksController::prependRow(const ChatLinkData &data) {
|
|
delegate()->peerListPrependRow(std::make_unique<Row>(this, data));
|
|
_count = _count.current() + 1;
|
|
}
|
|
|
|
void LinksController::updateRow(const ChatLinkData &data) {
|
|
if (const auto row = delegate()->peerListFindRow(ComputeRowId(data))) {
|
|
const auto real = static_cast<Row*>(row);
|
|
real->update(data);
|
|
delegate()->peerListUpdateRow(row);
|
|
}
|
|
}
|
|
|
|
bool LinksController::removeRow(const QString &link) {
|
|
const auto id = UniqueRowIdFromString(link);
|
|
if (const auto row = delegate()->peerListFindRow(id)) {
|
|
delegate()->peerListRemoveRow(row);
|
|
_count = std::max(_count.current() - 1, 0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
not_null<Main::Session*> LinksController::rowSession() {
|
|
return _session;
|
|
}
|
|
|
|
void LinksController::rowUpdateRow(not_null<Row*> row) {
|
|
delegate()->peerListUpdateRow(row);
|
|
}
|
|
|
|
void LinksController::rowPaintIcon(
|
|
QPainter &p,
|
|
int x,
|
|
int y,
|
|
int size) {
|
|
const auto skip = st::inviteLinkIconSkip;
|
|
const auto inner = size - 2 * skip;
|
|
const auto bg = &st::msgFile1Bg;
|
|
if (_icon.isNull()) {
|
|
_icon = QImage(
|
|
QSize(inner, inner) * style::DevicePixelRatio(),
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
_icon.fill(Qt::transparent);
|
|
_icon.setDevicePixelRatio(style::DevicePixelRatio());
|
|
|
|
auto p = QPainter(&_icon);
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(*bg);
|
|
{
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
auto rect = QRect(0, 0, inner, inner);
|
|
p.drawEllipse(rect);
|
|
}
|
|
st::inviteLinkIcon.paintInCenter(p, Rect(Size(inner)));
|
|
}
|
|
p.drawImage(x + skip, y + skip, _icon);
|
|
}
|
|
|
|
ChatLinks::ChatLinks(
|
|
QWidget *parent,
|
|
not_null<Window::SessionController*> controller)
|
|
: BusinessSection(parent, controller)
|
|
, _bottomSkipRounding(st::boxRadius, st::boxDividerBg) {
|
|
setupContent(controller);
|
|
}
|
|
|
|
ChatLinks::~ChatLinks() = default;
|
|
|
|
rpl::producer<QString> ChatLinks::title() {
|
|
return tr::lng_chat_links_title();
|
|
}
|
|
|
|
void ChatLinks::setupContent(
|
|
not_null<Window::SessionController*> controller) {
|
|
using namespace rpl::mappers;
|
|
|
|
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
|
|
|
AddDividerTextWithLottie(content, {
|
|
.lottie = u"chat_link"_q,
|
|
.lottieSize = st::settingsCloudPasswordIconSize,
|
|
.lottieMargins = st::peerAppearanceIconPadding,
|
|
.showFinished = showFinishes() | rpl::take(1),
|
|
.about = tr::lng_chat_links_about(Ui::Text::WithEntities),
|
|
.aboutMargins = st::peerAppearanceCoverLabelMargin,
|
|
});
|
|
|
|
Ui::AddSkip(content);
|
|
|
|
const auto limit = controller->session().appConfig().get<int>(
|
|
u"business_chat_links_limit"_q,
|
|
100);
|
|
const auto add = content->add(
|
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
|
content,
|
|
MakeCreateLinkButton(
|
|
content,
|
|
tr::lng_chat_links_create_link()))
|
|
)->setDuration(0);
|
|
|
|
const auto list = AddLinksList(controller, content);
|
|
add->toggleOn(list.controller->fullCountValue() | rpl::map(_1 < limit));
|
|
add->finishAnimating();
|
|
|
|
add->entity()->setClickedCallback([=] {
|
|
if (!controller->session().premium()) {
|
|
ShowPremiumPreviewToBuy(
|
|
controller,
|
|
PremiumFeature::ChatLinks);
|
|
return;
|
|
}
|
|
const auto submitting = std::make_shared<bool>();
|
|
const auto submit = [=](ChatLinkData data, Fn<void()> close) {
|
|
if (std::exchange(*submitting, true)) {
|
|
return;
|
|
}
|
|
const auto done = [=](const auto&) {
|
|
controller->showToast(tr::lng_chat_link_saved(tr::now));
|
|
close();
|
|
};
|
|
controller->session().api().chatLinks().create(
|
|
data.title,
|
|
data.message,
|
|
done);
|
|
};
|
|
controller->show(Box(
|
|
EditChatLinkBox,
|
|
controller,
|
|
ChatLinkData(),
|
|
crl::guard(this, submit)));
|
|
});
|
|
|
|
Ui::AddSkip(content);
|
|
|
|
const auto self = controller->session().user();
|
|
const auto username = self->username();
|
|
const auto make = [&](std::vector<QString> links) {
|
|
Expects(!links.empty());
|
|
|
|
for (auto &link : links) {
|
|
link = controller->session().createInternalLink(link);
|
|
}
|
|
return (links.size() > 1)
|
|
? tr::lng_chat_links_footer_both(
|
|
tr::now,
|
|
lt_username,
|
|
Ui::Text::Link(links[0], "https://" + links[0]),
|
|
lt_link,
|
|
Ui::Text::Link(links[1], "https://" + links[1]),
|
|
Ui::Text::WithEntities)
|
|
: Ui::Text::Link(links[0], "https://" + links[0]);
|
|
};
|
|
auto links = !username.isEmpty()
|
|
? make({ username, '+' + self->phone() })
|
|
: make({ '+' + self->phone() });
|
|
auto label = object_ptr<Ui::FlatLabel>(
|
|
content,
|
|
tr::lng_chat_links_footer(
|
|
lt_links,
|
|
rpl::single(std::move(links)),
|
|
Ui::Text::WithEntities),
|
|
st::boxDividerLabel);
|
|
label->setClickHandlerFilter([=](ClickHandlerPtr handler, auto) {
|
|
QGuiApplication::clipboard()->setText(handler->url());
|
|
controller->showToast(tr::lng_chat_link_copied(tr::now));
|
|
return false;
|
|
});
|
|
content->add(object_ptr<Ui::DividerLabel>(
|
|
content,
|
|
std::move(label),
|
|
st::settingsChatbotsBottomTextMargin,
|
|
RectPart::Top));
|
|
|
|
Ui::ResizeFitChild(this, content);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Type ChatLinksId() {
|
|
return ChatLinks::Id();
|
|
}
|
|
|
|
} // namespace Settings
|