Initial chat links edition implementation.
This commit is contained in:
parent
6fe0c60204
commit
3d54f8ec49
|
@ -110,6 +110,8 @@ PRIVATE
|
||||||
api/api_chat_filters.h
|
api/api_chat_filters.h
|
||||||
api/api_chat_invite.cpp
|
api/api_chat_invite.cpp
|
||||||
api/api_chat_invite.h
|
api/api_chat_invite.h
|
||||||
|
api/api_chat_links.cpp
|
||||||
|
api/api_chat_links.h
|
||||||
api/api_chat_participants.cpp
|
api/api_chat_participants.cpp
|
||||||
api/api_chat_participants.h
|
api/api_chat_participants.h
|
||||||
api/api_cloud_password.cpp
|
api/api_cloud_password.cpp
|
||||||
|
@ -1297,6 +1299,8 @@ PRIVATE
|
||||||
settings/business/settings_shortcut_messages.h
|
settings/business/settings_shortcut_messages.h
|
||||||
settings/business/settings_chat_intro.cpp
|
settings/business/settings_chat_intro.cpp
|
||||||
settings/business/settings_chat_intro.h
|
settings/business/settings_chat_intro.h
|
||||||
|
settings/business/settings_chat_links.cpp
|
||||||
|
settings/business/settings_chat_links.h
|
||||||
settings/business/settings_chatbots.cpp
|
settings/business/settings_chatbots.cpp
|
||||||
settings/business/settings_chatbots.h
|
settings/business/settings_chatbots.h
|
||||||
settings/business/settings_greeting.cpp
|
settings/business/settings_greeting.cpp
|
||||||
|
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 610 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -2211,8 +2211,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_business_about_away_messages" = "Define messages that are automatically sent when you are off.";
|
"lng_business_about_away_messages" = "Define messages that are automatically sent when you are off.";
|
||||||
"lng_business_subtitle_chatbots" = "Chatbots";
|
"lng_business_subtitle_chatbots" = "Chatbots";
|
||||||
"lng_business_about_chatbots" = "Add any third party chatbots that will process customer interactions.";
|
"lng_business_about_chatbots" = "Add any third party chatbots that will process customer interactions.";
|
||||||
"lng_business_subtitle_chat_intro" = "Intro";
|
"lng_business_subtitle_chat_intro" = "Custom Intro";
|
||||||
"lng_business_about_chat_intro" = "Customize the message people see before they start a chat with you.";
|
"lng_business_about_chat_intro" = "Customize the message people see before they start a chat with you.";
|
||||||
|
"lng_business_subtitle_chat_links" = "Links to Chat";
|
||||||
|
"lng_business_about_chat_links" = "Create links that start a chat with you, suggesting the first message.";
|
||||||
|
|
||||||
"lng_location_title" = "Location";
|
"lng_location_title" = "Location";
|
||||||
"lng_location_about" = "Display the location of your business on your account.";
|
"lng_location_about" = "Display the location of your business on your account.";
|
||||||
|
@ -2325,7 +2327,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_chatbot_menu_remove" = "Remove bot from this chat";
|
"lng_chatbot_menu_remove" = "Remove bot from this chat";
|
||||||
"lng_chatbot_menu_revoke" = "Revoke access to this chat";
|
"lng_chatbot_menu_revoke" = "Revoke access to this chat";
|
||||||
|
|
||||||
"lng_chat_intro_title" = "Intro";
|
"lng_chat_intro_title" = "Custom Intro";
|
||||||
"lng_chat_intro_subtitle" = "Customize your intro";
|
"lng_chat_intro_subtitle" = "Customize your intro";
|
||||||
"lng_chat_intro_default_title" = "No messages here yet...";
|
"lng_chat_intro_default_title" = "No messages here yet...";
|
||||||
"lng_chat_intro_default_message" = "Send a message or click on the greeting below";
|
"lng_chat_intro_default_message" = "Send a message or click on the greeting below";
|
||||||
|
@ -2336,6 +2338,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_chat_intro_about" = "You can customize the message people see before they start a chat with you.";
|
"lng_chat_intro_about" = "You can customize the message people see before they start a chat with you.";
|
||||||
"lng_chat_intro_reset" = "Reset to Default";
|
"lng_chat_intro_reset" = "Reset to Default";
|
||||||
|
|
||||||
|
"lng_chat_links_title" = "Links to Chat";
|
||||||
|
"lng_chat_links_about" = "Give your customers short links that start a chat with you – and suggest the first message from them to you.";
|
||||||
|
"lng_chat_links_create_link" = "Create a Link to Chat";
|
||||||
|
"lng_chat_links_footer" = "You can also use a simple link for a chat with you – {links}";
|
||||||
|
"lng_chat_links_footer_both" = "{username} or {link}";
|
||||||
|
"lng_chat_links_no_clicks" = "no clicks";
|
||||||
|
"lng_chat_links_clicks#one" = "{count} click";
|
||||||
|
"lng_chat_links_clicks#other" = "{count} clicks";
|
||||||
|
"lng_chat_link_new_title" = "New Link";
|
||||||
|
"lng_chat_link_edit_title" = "Edit Link";
|
||||||
|
"lng_chat_link_description" = "Add a message that will be entered in the message field for anyone who starts a chat with you using this link.";
|
||||||
|
"lng_chat_link_placeholder" = "Add Preset Message";
|
||||||
|
"lng_chat_link_saved" = "Chat link saved.";
|
||||||
|
"lng_chat_link_copy" = "Copy";
|
||||||
|
"lng_chat_link_share" = "Share";
|
||||||
|
"lng_chat_link_rename" = "Rename";
|
||||||
|
"lng_chat_link_delete" = "Delete";
|
||||||
|
"lng_chat_link_name" = "Link Name (optional)";
|
||||||
|
"lng_chat_link_name_about" = "Add a name for this link that only you will see.";
|
||||||
|
"lng_chat_link_delete_sure" = "Are you sure you want to delete this chat link?";
|
||||||
|
"lng_chat_link_qr_title" = "Chat Link QR Code";
|
||||||
|
"lng_chat_link_qr_about" = "Everyone on Telegram can scan this code to contact you.";
|
||||||
|
"lng_chat_link_copied" = "Chat link copied to clipboard.";
|
||||||
|
|
||||||
"lng_boost_channel_button" = "Boost Channel";
|
"lng_boost_channel_button" = "Boost Channel";
|
||||||
"lng_boost_group_button" = "Boost Group";
|
"lng_boost_group_button" = "Boost Group";
|
||||||
"lng_boost_again_button" = "Boost Again";
|
"lng_boost_again_button" = "Boost Again";
|
||||||
|
|
|
@ -21,5 +21,6 @@
|
||||||
<file alias="writing.tgs">../../animations/writing.tgs</file>
|
<file alias="writing.tgs">../../animations/writing.tgs</file>
|
||||||
<file alias="hours.tgs">../../animations/hours.tgs</file>
|
<file alias="hours.tgs">../../animations/hours.tgs</file>
|
||||||
<file alias="phone.tgs">../../animations/phone.tgs</file>
|
<file alias="phone.tgs">../../animations/phone.tgs</file>
|
||||||
|
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
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 "api/api_chat_links.h"
|
||||||
|
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] ChatLink FromMTP(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const MTPBusinessChatLink &link) {
|
||||||
|
const auto &data = link.data();
|
||||||
|
return {
|
||||||
|
.link = qs(data.vlink()),
|
||||||
|
.title = qs(data.vtitle().value_or_empty()),
|
||||||
|
.message = {
|
||||||
|
qs(data.vmessage()),
|
||||||
|
EntitiesFromMTP(
|
||||||
|
session,
|
||||||
|
data.ventities().value_or_empty())
|
||||||
|
},
|
||||||
|
.clicks = data.vviews().v,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MTPInputBusinessChatLink ToMTP(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const QString &title,
|
||||||
|
const TextWithEntities &message) {
|
||||||
|
auto entities = EntitiesToMTP(
|
||||||
|
session,
|
||||||
|
message.entities,
|
||||||
|
ConvertOption::SkipLocal);
|
||||||
|
using Flag = MTPDinputBusinessChatLink::Flag;
|
||||||
|
const auto flags = (title.isEmpty() ? Flag() : Flag::f_title)
|
||||||
|
| (entities.v.isEmpty() ? Flag() : Flag::f_entities);
|
||||||
|
return MTP_inputBusinessChatLink(
|
||||||
|
MTP_flags(flags),
|
||||||
|
MTP_string(message.text),
|
||||||
|
std::move(entities),
|
||||||
|
MTP_string(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ChatLinks::ChatLinks(not_null<ApiWrap*> api) : _api(api) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ChatLinks::create(
|
||||||
|
const QString &title,
|
||||||
|
const TextWithEntities &message,
|
||||||
|
Fn<void(Link)> done) {
|
||||||
|
const auto session = &_api->session();
|
||||||
|
_api->request(MTPaccount_CreateBusinessChatLink(
|
||||||
|
ToMTP(session, title, message)
|
||||||
|
)).done([=](const MTPBusinessChatLink &result) {
|
||||||
|
const auto link = FromMTP(session, result);
|
||||||
|
_list.push_back(link);
|
||||||
|
_updates.fire({ .was = QString(), .now = link });
|
||||||
|
if (done) done(link);
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
const auto type = error.type();
|
||||||
|
if (done) done(Link());
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatLinks::edit(
|
||||||
|
const QString &link,
|
||||||
|
const QString &title,
|
||||||
|
const TextWithEntities &message,
|
||||||
|
Fn<void(Link)> done) {
|
||||||
|
const auto session = &_api->session();
|
||||||
|
_api->request(MTPaccount_EditBusinessChatLink(
|
||||||
|
MTP_string(link),
|
||||||
|
ToMTP(session, title, message)
|
||||||
|
)).done([=](const MTPBusinessChatLink &result) {
|
||||||
|
const auto parsed = FromMTP(session, result);
|
||||||
|
if (parsed.link != link) {
|
||||||
|
LOG(("API Error: EditBusinessChatLink changed the link."));
|
||||||
|
if (done) done(Link());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto i = ranges::find(_list, link, &Link::link);
|
||||||
|
if (i != end(_list)) {
|
||||||
|
*i = parsed;
|
||||||
|
_updates.fire({ .was = link, .now = parsed });
|
||||||
|
if (done) done(parsed);
|
||||||
|
} else {
|
||||||
|
LOG(("API Error: EditBusinessChatLink link not found."));
|
||||||
|
if (done) done(Link());
|
||||||
|
}
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
const auto type = error.type();
|
||||||
|
if (done) done(Link());
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatLinks::destroy(
|
||||||
|
const QString &link,
|
||||||
|
Fn<void()> done) {
|
||||||
|
_api->request(MTPaccount_DeleteBusinessChatLink(
|
||||||
|
MTP_string(link)
|
||||||
|
)).done([=] {
|
||||||
|
const auto i = ranges::find(_list, link, &Link::link);
|
||||||
|
if (i != end(_list)) {
|
||||||
|
_list.erase(i);
|
||||||
|
_updates.fire({ .was = link });
|
||||||
|
if (done) done();
|
||||||
|
} else {
|
||||||
|
LOG(("API Error: DeleteBusinessChatLink link not found."));
|
||||||
|
if (done) done();
|
||||||
|
}
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
const auto type = error.type();
|
||||||
|
if (done) done();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatLinks::preload() {
|
||||||
|
if (_loaded || _requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_requestId = _api->request(MTPaccount_GetBusinessChatLinks(
|
||||||
|
)).done([=](const MTPaccount_BusinessChatLinks &result) {
|
||||||
|
const auto &data = result.data();
|
||||||
|
const auto session = &_api->session();
|
||||||
|
const auto owner = &session->data();
|
||||||
|
owner->processUsers(data.vusers());
|
||||||
|
owner->processChats(data.vchats());
|
||||||
|
auto links = std::vector<Link>();
|
||||||
|
links.reserve(data.vlinks().v.size());
|
||||||
|
for (const auto &link : data.vlinks().v) {
|
||||||
|
links.push_back(FromMTP(session, link));
|
||||||
|
}
|
||||||
|
_list = std::move(links);
|
||||||
|
_loaded = true;
|
||||||
|
_loadedUpdates.fire({});
|
||||||
|
}).fail([=] {
|
||||||
|
_requestId = 0;
|
||||||
|
_loaded = true;
|
||||||
|
_loadedUpdates.fire({});
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<ChatLink> &ChatLinks::list() const {
|
||||||
|
return _list;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatLinks::loaded() const {
|
||||||
|
return _loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> ChatLinks::loadedUpdates() const {
|
||||||
|
return _loadedUpdates.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<ChatLinks::Update> ChatLinks::updates() const {
|
||||||
|
return _updates.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
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 ApiWrap;
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
|
||||||
|
struct ChatLink {
|
||||||
|
QString link;
|
||||||
|
QString title;
|
||||||
|
TextWithEntities message;
|
||||||
|
int clicks = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChatLinkUpdate {
|
||||||
|
QString was;
|
||||||
|
std::optional<ChatLink> now;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ChatLinks final {
|
||||||
|
public:
|
||||||
|
explicit ChatLinks(not_null<ApiWrap*> api);
|
||||||
|
|
||||||
|
using Link = ChatLink;
|
||||||
|
using Update = ChatLinkUpdate;
|
||||||
|
|
||||||
|
void create(
|
||||||
|
const QString &title,
|
||||||
|
const TextWithEntities &message,
|
||||||
|
Fn<void(Link)> done = nullptr);
|
||||||
|
void edit(
|
||||||
|
const QString &link,
|
||||||
|
const QString &title,
|
||||||
|
const TextWithEntities &message,
|
||||||
|
Fn<void(Link)> done = nullptr);
|
||||||
|
void destroy(
|
||||||
|
const QString &link,
|
||||||
|
Fn<void()> done = nullptr);
|
||||||
|
|
||||||
|
void preload();
|
||||||
|
[[nodiscard]] const std::vector<ChatLink> &list() const;
|
||||||
|
[[nodiscard]] bool loaded() const;
|
||||||
|
[[nodiscard]] rpl::producer<> loadedUpdates() const;
|
||||||
|
[[nodiscard]] rpl::producer<Update> updates() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<ApiWrap*> _api;
|
||||||
|
|
||||||
|
std::vector<Link> _list;
|
||||||
|
rpl::event_stream<> _loadedUpdates;
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
bool _loaded = false;
|
||||||
|
|
||||||
|
rpl::event_stream<Update> _updates;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Api
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_authorizations.h"
|
#include "api/api_authorizations.h"
|
||||||
#include "api/api_attached_stickers.h"
|
#include "api/api_attached_stickers.h"
|
||||||
#include "api/api_blocked_peers.h"
|
#include "api/api_blocked_peers.h"
|
||||||
|
#include "api/api_chat_links.h"
|
||||||
#include "api/api_chat_participants.h"
|
#include "api/api_chat_participants.h"
|
||||||
#include "api/api_cloud_password.h"
|
#include "api/api_cloud_password.h"
|
||||||
#include "api/api_hash.h"
|
#include "api/api_hash.h"
|
||||||
|
@ -163,6 +164,7 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||||
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
|
, _globalPrivacy(std::make_unique<Api::GlobalPrivacy>(this))
|
||||||
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
|
, _userPrivacy(std::make_unique<Api::UserPrivacy>(this))
|
||||||
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
|
, _inviteLinks(std::make_unique<Api::InviteLinks>(this))
|
||||||
|
, _chatLinks(std::make_unique<Api::ChatLinks>(this))
|
||||||
, _views(std::make_unique<Api::ViewsManager>(this))
|
, _views(std::make_unique<Api::ViewsManager>(this))
|
||||||
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
|
, _confirmPhone(std::make_unique<Api::ConfirmPhone>(this))
|
||||||
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
|
, _peerPhoto(std::make_unique<Api::PeerPhoto>(this))
|
||||||
|
@ -4424,6 +4426,10 @@ Api::InviteLinks &ApiWrap::inviteLinks() {
|
||||||
return *_inviteLinks;
|
return *_inviteLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Api::ChatLinks &ApiWrap::chatLinks() {
|
||||||
|
return *_chatLinks;
|
||||||
|
}
|
||||||
|
|
||||||
Api::ViewsManager &ApiWrap::views() {
|
Api::ViewsManager &ApiWrap::views() {
|
||||||
return *_views;
|
return *_views;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ class SensitiveContent;
|
||||||
class GlobalPrivacy;
|
class GlobalPrivacy;
|
||||||
class UserPrivacy;
|
class UserPrivacy;
|
||||||
class InviteLinks;
|
class InviteLinks;
|
||||||
|
class ChatLinks;
|
||||||
class ViewsManager;
|
class ViewsManager;
|
||||||
class ConfirmPhone;
|
class ConfirmPhone;
|
||||||
class PeerPhoto;
|
class PeerPhoto;
|
||||||
|
@ -384,6 +385,7 @@ public:
|
||||||
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
[[nodiscard]] Api::GlobalPrivacy &globalPrivacy();
|
||||||
[[nodiscard]] Api::UserPrivacy &userPrivacy();
|
[[nodiscard]] Api::UserPrivacy &userPrivacy();
|
||||||
[[nodiscard]] Api::InviteLinks &inviteLinks();
|
[[nodiscard]] Api::InviteLinks &inviteLinks();
|
||||||
|
[[nodiscard]] Api::ChatLinks &chatLinks();
|
||||||
[[nodiscard]] Api::ViewsManager &views();
|
[[nodiscard]] Api::ViewsManager &views();
|
||||||
[[nodiscard]] Api::ConfirmPhone &confirmPhone();
|
[[nodiscard]] Api::ConfirmPhone &confirmPhone();
|
||||||
[[nodiscard]] Api::PeerPhoto &peerPhoto();
|
[[nodiscard]] Api::PeerPhoto &peerPhoto();
|
||||||
|
@ -703,6 +705,7 @@ private:
|
||||||
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
const std::unique_ptr<Api::GlobalPrivacy> _globalPrivacy;
|
||||||
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
|
const std::unique_ptr<Api::UserPrivacy> _userPrivacy;
|
||||||
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
|
const std::unique_ptr<Api::InviteLinks> _inviteLinks;
|
||||||
|
const std::unique_ptr<Api::ChatLinks> _chatLinks;
|
||||||
const std::unique_ptr<Api::ViewsManager> _views;
|
const std::unique_ptr<Api::ViewsManager> _views;
|
||||||
const std::unique_ptr<Api::ConfirmPhone> _confirmPhone;
|
const std::unique_ptr<Api::ConfirmPhone> _confirmPhone;
|
||||||
const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
|
const std::unique_ptr<Api::PeerPhoto> _peerPhoto;
|
||||||
|
|
|
@ -580,8 +580,10 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
|
||||||
ShareInviteLinkBox(&_window->session(), link));
|
ShareInviteLinkBox(&_window->session(), link));
|
||||||
});
|
});
|
||||||
const auto getLinkQr = crl::guard(weak, [=] {
|
const auto getLinkQr = crl::guard(weak, [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||||
InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
|
link,
|
||||||
|
tr::lng_group_invite_qr_title(),
|
||||||
|
tr::lng_filters_link_qr_about()));
|
||||||
});
|
});
|
||||||
const auto editLink = crl::guard(weak, [=] {
|
const auto editLink = crl::guard(weak, [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(
|
||||||
|
@ -886,8 +888,10 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||||
ShareInviteLinkBox(&_window->session(), link));
|
ShareInviteLinkBox(&_window->session(), link));
|
||||||
};
|
};
|
||||||
const auto getLinkQr = [=] {
|
const auto getLinkQr = [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||||
InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
|
link,
|
||||||
|
tr::lng_group_invite_qr_title(),
|
||||||
|
tr::lng_filters_link_qr_about()));
|
||||||
};
|
};
|
||||||
const auto editLink = [=] {
|
const auto editLink = [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(
|
||||||
|
|
|
@ -272,9 +272,10 @@ QImage QrForShare(const QString &text) {
|
||||||
void QrBox(
|
void QrBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
const QString &link,
|
const QString &link,
|
||||||
|
rpl::producer<QString> title,
|
||||||
rpl::producer<QString> about,
|
rpl::producer<QString> about,
|
||||||
Fn<void(QImage, std::shared_ptr<Ui::Show>)> share) {
|
Fn<void(QImage, std::shared_ptr<Ui::Show>)> share) {
|
||||||
box->setTitle(tr::lng_group_invite_qr_title());
|
box->setTitle(std::move(title));
|
||||||
|
|
||||||
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
||||||
|
|
||||||
|
@ -350,8 +351,10 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
|
||||||
delegate()->peerListUiShow()->showBox(ShareInviteLinkBox(peer, link));
|
delegate()->peerListUiShow()->showBox(ShareInviteLinkBox(peer, link));
|
||||||
});
|
});
|
||||||
const auto getLinkQr = crl::guard(weak, [=] {
|
const auto getLinkQr = crl::guard(weak, [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||||
InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
|
link,
|
||||||
|
tr::lng_group_invite_qr_title(),
|
||||||
|
tr::lng_group_invite_qr_about()));
|
||||||
});
|
});
|
||||||
const auto revokeLink = crl::guard(weak, [=] {
|
const auto revokeLink = crl::guard(weak, [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(
|
||||||
|
@ -976,6 +979,7 @@ void AddPermanentLinkBlock(
|
||||||
if (const auto current = value->current(); !current.link.isEmpty()) {
|
if (const auto current = value->current(); !current.link.isEmpty()) {
|
||||||
show->showBox(InviteLinkQrBox(
|
show->showBox(InviteLinkQrBox(
|
||||||
current.link,
|
current.link,
|
||||||
|
tr::lng_group_invite_qr_title(),
|
||||||
tr::lng_group_invite_qr_about()));
|
tr::lng_group_invite_qr_about()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1130,13 +1134,15 @@ void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link) {
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &link) {
|
const QString &link,
|
||||||
return ShareInviteLinkBox(&peer->session(), link);
|
const QString &copied) {
|
||||||
|
return ShareInviteLinkBox(&peer->session(), link, copied);
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const QString &link) {
|
const QString &link,
|
||||||
|
const QString &copied) {
|
||||||
const auto sending = std::make_shared<bool>();
|
const auto sending = std::make_shared<bool>();
|
||||||
const auto box = std::make_shared<QPointer<ShareBox>>();
|
const auto box = std::make_shared<QPointer<ShareBox>>();
|
||||||
|
|
||||||
|
@ -1148,7 +1154,9 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
|
|
||||||
auto copyCallback = [=] {
|
auto copyCallback = [=] {
|
||||||
QGuiApplication::clipboard()->setText(link);
|
QGuiApplication::clipboard()->setText(link);
|
||||||
showToast(tr::lng_group_invite_copied(tr::now));
|
showToast(copied.isEmpty()
|
||||||
|
? tr::lng_group_invite_copied(tr::now)
|
||||||
|
: copied);
|
||||||
};
|
};
|
||||||
auto submitCallback = [=](
|
auto submitCallback = [=](
|
||||||
std::vector<not_null<Data::Thread*>> &&result,
|
std::vector<not_null<Data::Thread*>> &&result,
|
||||||
|
@ -1228,8 +1236,9 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
|
|
||||||
object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
||||||
const QString &link,
|
const QString &link,
|
||||||
|
rpl::producer<QString> title,
|
||||||
rpl::producer<QString> about) {
|
rpl::producer<QString> about) {
|
||||||
return Box(QrBox, link, std::move(about), [=](
|
return Box(QrBox, link, std::move(title), std::move(about), [=](
|
||||||
const QImage &image,
|
const QImage &image,
|
||||||
std::shared_ptr<Ui::Show> show) {
|
std::shared_ptr<Ui::Show> show) {
|
||||||
auto mime = std::make_unique<QMimeData>();
|
auto mime = std::make_unique<QMimeData>();
|
||||||
|
|
|
@ -41,12 +41,15 @@ void AddPermanentLinkBlock(
|
||||||
void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link);
|
void CopyInviteLink(std::shared_ptr<Ui::Show> show, const QString &link);
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const QString &link);
|
const QString &link,
|
||||||
|
const QString &copied = {});
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const QString &link);
|
const QString &link,
|
||||||
|
const QString &copied = {});
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
||||||
const QString &link,
|
const QString &link,
|
||||||
|
rpl::producer<QString> title,
|
||||||
rpl::producer<QString> about);
|
rpl::producer<QString> about);
|
||||||
[[nodiscard]] object_ptr<Ui::BoxContent> RevokeLinkBox(
|
[[nodiscard]] object_ptr<Ui::BoxContent> RevokeLinkBox(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
|
|
@ -214,38 +214,11 @@ object_ptr<Ui::BoxContent> DeleteAllRevokedBox(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<Ui::SettingsButton*> AddCreateLinkButton(
|
[[nodiscard]] not_null<Ui::SettingsButton*> AddCreateLinkButton(
|
||||||
not_null<Ui::VerticalLayout*> container) {
|
not_null<Ui::VerticalLayout*> container) {
|
||||||
const auto result = container->add(
|
return container->add(
|
||||||
object_ptr<Ui::SettingsButton>(
|
MakeCreateLinkButton(container, tr::lng_group_invite_add()),
|
||||||
container,
|
|
||||||
tr::lng_group_invite_add(),
|
|
||||||
st::inviteLinkCreate),
|
|
||||||
style::margins(0, st::inviteLinkCreateSkip, 0, 0));
|
style::margins(0, st::inviteLinkCreateSkip, 0, 0));
|
||||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(result);
|
|
||||||
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
||||||
const auto size = st::inviteLinkCreateIconSize;
|
|
||||||
icon->resize(size, size);
|
|
||||||
result->heightValue(
|
|
||||||
) | rpl::start_with_next([=](int height) {
|
|
||||||
const auto &st = st::inviteLinkList.item;
|
|
||||||
icon->move(
|
|
||||||
st.photoPosition.x() + (st.photoSize - size) / 2,
|
|
||||||
(height - size) / 2);
|
|
||||||
}, icon->lifetime());
|
|
||||||
icon->paintRequest(
|
|
||||||
) | rpl::start_with_next([=] {
|
|
||||||
auto p = QPainter(icon);
|
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(st::windowBgActive);
|
|
||||||
const auto rect = icon->rect();
|
|
||||||
{
|
|
||||||
auto hq = PainterHighQualityEnabler(p);
|
|
||||||
p.drawEllipse(rect);
|
|
||||||
}
|
|
||||||
st::inviteLinkCreateIcon.paintInCenter(p, rect);
|
|
||||||
}, icon->lifetime());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row::Row(
|
Row::Row(
|
||||||
|
@ -584,8 +557,10 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
|
||||||
ShareInviteLinkBox(_peer, link));
|
ShareInviteLinkBox(_peer, link));
|
||||||
}, &st::menuIconShare);
|
}, &st::menuIconShare);
|
||||||
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
|
||||||
delegate()->peerListUiShow()->showBox(
|
delegate()->peerListUiShow()->showBox(InviteLinkQrBox(
|
||||||
InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
|
link,
|
||||||
|
tr::lng_group_invite_qr_title(),
|
||||||
|
tr::lng_group_invite_qr_about()));
|
||||||
}, &st::menuIconQrCode);
|
}, &st::menuIconQrCode);
|
||||||
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
|
||||||
delegate()->peerListUiShow()->showBox(EditLinkBox(_peer, data));
|
delegate()->peerListUiShow()->showBox(EditLinkBox(_peer, data));
|
||||||
|
@ -1014,3 +989,42 @@ void ManageInviteLinksBox(
|
||||||
|
|
||||||
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object_ptr<Ui::SettingsButton> MakeCreateLinkButton(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
rpl::producer<QString> text) {
|
||||||
|
auto result = object_ptr<Ui::SettingsButton>(
|
||||||
|
parent,
|
||||||
|
std::move(text),
|
||||||
|
st::inviteLinkCreate);
|
||||||
|
const auto raw = result.data();
|
||||||
|
|
||||||
|
const auto icon = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||||
|
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
const auto size = st::inviteLinkCreateIconSize;
|
||||||
|
icon->resize(size, size);
|
||||||
|
|
||||||
|
raw->heightValue(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
const auto &st = st::inviteLinkList.item;
|
||||||
|
icon->move(
|
||||||
|
st.photoPosition.x() + (st.photoSize - size) / 2,
|
||||||
|
(height - size) / 2);
|
||||||
|
}, icon->lifetime());
|
||||||
|
|
||||||
|
icon->paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(icon);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(st::windowBgActive);
|
||||||
|
const auto rect = icon->rect();
|
||||||
|
{
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.drawEllipse(rect);
|
||||||
|
}
|
||||||
|
st::inviteLinkCreateIcon.paintInCenter(p, rect);
|
||||||
|
}, icon->lifetime());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -11,9 +11,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class PeerData;
|
class PeerData;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class SettingsButton;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
void ManageInviteLinksBox(
|
void ManageInviteLinksBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<UserData*> admin,
|
not_null<UserData*> admin,
|
||||||
int count,
|
int count,
|
||||||
int revokedCount);
|
int revokedCount);
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::SettingsButton> MakeCreateLinkButton(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
rpl::producer<QString> text);
|
||||||
|
|
|
@ -146,6 +146,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||||
return tr::lng_business_subtitle_chatbots();
|
return tr::lng_business_subtitle_chatbots();
|
||||||
case PremiumFeature::ChatIntro:
|
case PremiumFeature::ChatIntro:
|
||||||
return tr::lng_business_subtitle_chat_intro();
|
return tr::lng_business_subtitle_chat_intro();
|
||||||
|
case PremiumFeature::ChatLinks:
|
||||||
|
return tr::lng_business_subtitle_chat_links();
|
||||||
}
|
}
|
||||||
Unexpected("PremiumFeature in SectionTitle.");
|
Unexpected("PremiumFeature in SectionTitle.");
|
||||||
}
|
}
|
||||||
|
@ -205,6 +207,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||||
return tr::lng_business_about_chatbots();
|
return tr::lng_business_about_chatbots();
|
||||||
case PremiumFeature::ChatIntro:
|
case PremiumFeature::ChatIntro:
|
||||||
return tr::lng_business_about_chat_intro();
|
return tr::lng_business_about_chat_intro();
|
||||||
|
case PremiumFeature::ChatLinks:
|
||||||
|
return tr::lng_business_about_chat_links();
|
||||||
}
|
}
|
||||||
Unexpected("PremiumFeature in SectionTitle.");
|
Unexpected("PremiumFeature in SectionTitle.");
|
||||||
}
|
}
|
||||||
|
@ -533,6 +537,7 @@ struct VideoPreviewDocument {
|
||||||
case PremiumFeature::AwayMessage: return "away_message";
|
case PremiumFeature::AwayMessage: return "away_message";
|
||||||
case PremiumFeature::BusinessBots: return "business_bots";
|
case PremiumFeature::BusinessBots: return "business_bots";
|
||||||
case PremiumFeature::ChatIntro: return "business_intro";
|
case PremiumFeature::ChatIntro: return "business_intro";
|
||||||
|
case PremiumFeature::ChatLinks: return "business_links";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}();
|
}();
|
||||||
|
|
|
@ -75,6 +75,7 @@ enum class PremiumFeature {
|
||||||
AwayMessage,
|
AwayMessage,
|
||||||
BusinessBots,
|
BusinessBots,
|
||||||
ChatIntro,
|
ChatIntro,
|
||||||
|
ChatLinks,
|
||||||
|
|
||||||
kCount,
|
kCount,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,813 @@
|
||||||
|
/*
|
||||||
|
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_account.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/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(
|
||||||
|
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;
|
||||||
|
const auto stroke = st::inviteLinkIconStroke;
|
||||||
|
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, { 0, 0, inner, 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().account().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() });
|
||||||
|
Ui::AddDividerText(
|
||||||
|
content,
|
||||||
|
tr::lng_chat_links_footer(
|
||||||
|
lt_links,
|
||||||
|
rpl::single(std::move(links)),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
st::settingsChatbotsBottomTextMargin,
|
||||||
|
RectPart::Top);
|
||||||
|
|
||||||
|
Ui::ResizeFitChild(this, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Type ChatLinksId() {
|
||||||
|
return ChatLinks::Id();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Settings
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
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 "settings/settings_type.h"
|
||||||
|
|
||||||
|
namespace Settings {
|
||||||
|
|
||||||
|
[[nodiscard]] Type ChatLinksId();
|
||||||
|
|
||||||
|
} // namespace Settings
|
|
@ -111,8 +111,10 @@ void QuickReplies::setupContent(
|
||||||
showOther(ShortcutMessagesId(id));
|
showOther(ShortcutMessagesId(id));
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
controller->show(
|
controller->show(Box(
|
||||||
Box(EditShortcutNameBox, QString(), crl::guard(this, submit)));
|
EditShortcutNameBox,
|
||||||
|
QString(),
|
||||||
|
crl::guard(this, submit)));
|
||||||
});
|
});
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
AddSkip(addWrap);
|
AddSkip(addWrap);
|
||||||
|
|
|
@ -111,6 +111,7 @@ settingsBusinessIconGreeting: icon {{ "settings/premium/status", settingsIconFg
|
||||||
settingsBusinessIconAway: icon {{ "settings/premium/business/business_away", settingsIconFg }};
|
settingsBusinessIconAway: icon {{ "settings/premium/business/business_away", settingsIconFg }};
|
||||||
settingsBusinessIconChatbots: icon {{ "settings/premium/business/business_chatbots", settingsIconFg }};
|
settingsBusinessIconChatbots: icon {{ "settings/premium/business/business_chatbots", settingsIconFg }};
|
||||||
settingsBusinessIconChatIntro: icon {{ "settings/premium/intro", settingsIconFg }};
|
settingsBusinessIconChatIntro: icon {{ "settings/premium/intro", settingsIconFg }};
|
||||||
|
settingsBusinessIconChatLinks: icon {{ "settings/premium/links", settingsIconFg }};
|
||||||
|
|
||||||
settingsPremiumNewBadge: FlatLabel(defaultFlatLabel) {
|
settingsPremiumNewBadge: FlatLabel(defaultFlatLabel) {
|
||||||
style: TextStyle(semiboldTextStyle) {
|
style: TextStyle(semiboldTextStyle) {
|
||||||
|
@ -648,3 +649,23 @@ settingsChatIntroField: InputField(defaultMultiSelectSearchField) {
|
||||||
textMargins: margins(2px, 0px, 32px, 0px);
|
textMargins: margins(2px, 0px, 32px, 0px);
|
||||||
}
|
}
|
||||||
settingsChatIntroFieldMargins: margins(20px, 15px, 20px, 8px);
|
settingsChatIntroFieldMargins: margins(20px, 15px, 20px, 8px);
|
||||||
|
|
||||||
|
settingsChatLinkEmojiTop: 2px;
|
||||||
|
settingsChatLinkField: InputField(defaultInputField) {
|
||||||
|
textBg: transparent;
|
||||||
|
textMargins: margins(2px, 8px, 2px, 8px);
|
||||||
|
|
||||||
|
placeholderFg: placeholderFg;
|
||||||
|
placeholderFgActive: placeholderFgActive;
|
||||||
|
placeholderFgError: placeholderFgActive;
|
||||||
|
placeholderMargins: margins(0px, 0px, 0px, 0px);
|
||||||
|
placeholderScale: 0.;
|
||||||
|
placeholderFont: normalFont;
|
||||||
|
|
||||||
|
border: 0px;
|
||||||
|
borderActive: 0px;
|
||||||
|
|
||||||
|
heightMin: 32px;
|
||||||
|
|
||||||
|
font: normalFont;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "settings/settings_business.h"
|
#include "settings/settings_business.h"
|
||||||
|
|
||||||
|
#include "api/api_chat_links.h"
|
||||||
#include "boxes/premium_preview_box.h"
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "data/business/data_business_info.h"
|
#include "data/business/data_business_info.h"
|
||||||
|
@ -24,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "settings/business/settings_away_message.h"
|
#include "settings/business/settings_away_message.h"
|
||||||
#include "settings/business/settings_chat_intro.h"
|
#include "settings/business/settings_chat_intro.h"
|
||||||
|
#include "settings/business/settings_chat_links.h"
|
||||||
#include "settings/business/settings_chatbots.h"
|
#include "settings/business/settings_chatbots.h"
|
||||||
#include "settings/business/settings_greeting.h"
|
#include "settings/business/settings_greeting.h"
|
||||||
#include "settings/business/settings_location.h"
|
#include "settings/business/settings_location.h"
|
||||||
|
@ -41,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/wrap/fade_wrap.h"
|
#include "ui/wrap/fade_wrap.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "ui/new_badges.h"
|
||||||
#include "ui/vertical_list.h"
|
#include "ui/vertical_list.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
@ -58,6 +61,7 @@ struct Entry {
|
||||||
rpl::producer<QString> title;
|
rpl::producer<QString> title;
|
||||||
rpl::producer<QString> description;
|
rpl::producer<QString> description;
|
||||||
PremiumFeature feature = PremiumFeature::BusinessLocation;
|
PremiumFeature feature = PremiumFeature::BusinessLocation;
|
||||||
|
bool newBadge = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
using Order = std::vector<QString>;
|
using Order = std::vector<QString>;
|
||||||
|
@ -70,7 +74,8 @@ using Order = std::vector<QString>;
|
||||||
u"business_hours"_q,
|
u"business_hours"_q,
|
||||||
u"business_location"_q,
|
u"business_location"_q,
|
||||||
u"business_bots"_q,
|
u"business_bots"_q,
|
||||||
u"intro"_q,
|
u"business_intro"_q,
|
||||||
|
u"business_links"_q,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,12 +136,23 @@ using Order = std::vector<QString>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
u"intro"_q,
|
u"business_intro"_q,
|
||||||
Entry{
|
Entry{
|
||||||
&st::settingsBusinessIconChatIntro,
|
&st::settingsBusinessIconChatIntro,
|
||||||
tr::lng_business_subtitle_chat_intro(),
|
tr::lng_business_subtitle_chat_intro(),
|
||||||
tr::lng_business_about_chat_intro(),
|
tr::lng_business_about_chat_intro(),
|
||||||
PremiumFeature::ChatIntro,
|
PremiumFeature::ChatIntro,
|
||||||
|
true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
u"business_links"_q,
|
||||||
|
Entry{
|
||||||
|
&st::settingsBusinessIconChatLinks,
|
||||||
|
tr::lng_business_subtitle_chat_links(),
|
||||||
|
tr::lng_business_about_chat_links(),
|
||||||
|
PremiumFeature::ChatLinks,
|
||||||
|
true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -177,6 +193,9 @@ void AddBusinessSummary(
|
||||||
descriptionPadding);
|
descriptionPadding);
|
||||||
description->setAttribute(Qt::WA_TransparentForMouseEvents);
|
description->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
if (entry.newBadge) {
|
||||||
|
Ui::NewBadge::AddAfterLabel(content, label);
|
||||||
|
}
|
||||||
const auto dummy = Ui::CreateChild<Ui::AbstractButton>(content.get());
|
const auto dummy = Ui::CreateChild<Ui::AbstractButton>(content.get());
|
||||||
dummy->setAttribute(Qt::WA_TransparentForMouseEvents);
|
dummy->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
@ -374,6 +393,7 @@ void Business::setupContent() {
|
||||||
owner->chatbots().preload();
|
owner->chatbots().preload();
|
||||||
owner->businessInfo().preload();
|
owner->businessInfo().preload();
|
||||||
owner->shortcutMessages().preloadShortcuts();
|
owner->shortcutMessages().preloadShortcuts();
|
||||||
|
owner->session().api().chatLinks().preload();
|
||||||
|
|
||||||
Ui::AddSkip(content, st::settingsFromFileTop);
|
Ui::AddSkip(content, st::settingsFromFileTop);
|
||||||
|
|
||||||
|
@ -387,6 +407,7 @@ void Business::setupContent() {
|
||||||
case PremiumFeature::QuickReplies: return QuickRepliesId();
|
case PremiumFeature::QuickReplies: return QuickRepliesId();
|
||||||
case PremiumFeature::BusinessBots: return ChatbotsId();
|
case PremiumFeature::BusinessBots: return ChatbotsId();
|
||||||
case PremiumFeature::ChatIntro: return ChatIntroId();
|
case PremiumFeature::ChatIntro: return ChatIntroId();
|
||||||
|
case PremiumFeature::ChatLinks: return ChatLinksId();
|
||||||
}
|
}
|
||||||
Unexpected("Feature in showFeature.");
|
Unexpected("Feature in showFeature.");
|
||||||
}());
|
}());
|
||||||
|
@ -410,6 +431,8 @@ void Business::setupContent() {
|
||||||
return owner->chatbots().loaded();
|
return owner->chatbots().loaded();
|
||||||
case PremiumFeature::ChatIntro:
|
case PremiumFeature::ChatIntro:
|
||||||
return owner->session().user()->isFullLoaded();
|
return owner->session().user()->isFullLoaded();
|
||||||
|
case PremiumFeature::ChatLinks:
|
||||||
|
return owner->session().api().chatLinks().loaded();
|
||||||
}
|
}
|
||||||
Unexpected("Feature in isReady.");
|
Unexpected("Feature in isReady.");
|
||||||
};
|
};
|
||||||
|
@ -429,7 +452,8 @@ void Business::setupContent() {
|
||||||
owner->chatbots().changes() | rpl::to_empty,
|
owner->chatbots().changes() | rpl::to_empty,
|
||||||
owner->session().changes().peerUpdates(
|
owner->session().changes().peerUpdates(
|
||||||
owner->session().user(),
|
owner->session().user(),
|
||||||
Data::PeerUpdate::Flag::FullInfo) | rpl::to_empty
|
Data::PeerUpdate::Flag::FullInfo) | rpl::to_empty,
|
||||||
|
owner->session().api().chatLinks().loadedUpdates()
|
||||||
) | rpl::start_with_next(check, content->lifetime());
|
) | rpl::start_with_next(check, content->lifetime());
|
||||||
|
|
||||||
AddBusinessSummary(content, _controller, [=](PremiumFeature feature) {
|
AddBusinessSummary(content, _controller, [=](PremiumFeature feature) {
|
||||||
|
@ -686,6 +710,8 @@ std::vector<PremiumFeature> BusinessFeaturesOrder(
|
||||||
return PremiumFeature::BusinessBots;
|
return PremiumFeature::BusinessBots;
|
||||||
} else if (s == u"business_intro"_q) {
|
} else if (s == u"business_intro"_q) {
|
||||||
return PremiumFeature::ChatIntro;
|
return PremiumFeature::ChatIntro;
|
||||||
|
} else if (s == "business_links"_q) {
|
||||||
|
return PremiumFeature::ChatLinks;
|
||||||
}
|
}
|
||||||
return PremiumFeature::kCount;
|
return PremiumFeature::kCount;
|
||||||
}) | ranges::views::filter([](PremiumFeature feature) {
|
}) | ranges::views::filter([](PremiumFeature feature) {
|
||||||
|
|
Loading…
Reference in New Issue