diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index fbfbb55f2..0afd120d0 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -1277,6 +1277,8 @@ PRIVATE
profile/profile_block_widget.h
profile/profile_cover_drop_area.cpp
profile/profile_cover_drop_area.h
+ settings/business/settings_chatbots.cpp
+ settings/business/settings_chatbots.h
settings/cloud_password/settings_cloud_password_common.cpp
settings/cloud_password/settings_cloud_password_common.h
settings/cloud_password/settings_cloud_password_email.cpp
diff --git a/Telegram/Resources/animations/robot.tgs b/Telegram/Resources/animations/robot.tgs
new file mode 100644
index 000000000..0076344f4
Binary files /dev/null and b/Telegram/Resources/animations/robot.tgs differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 8bded7e02..19ff138a1 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2172,6 +2172,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_business_subtitle_chatbots" = "Chatbots";
"lng_business_about_chatbots" = "Add any third party chatbots that will process customer interactions.";
+"lng_chatbots_title" = "Chatbots";
+"lng_chatbots_about" = "Add a bot to your account to help you automatically process and respond to the messages you receive. {link}";
+"lng_chatbots_about_link" = "Learn more...";
+"lng_chatbots_placeholder" = "Enter bot URL or username";
+"lng_chatbots_add_about" = "Enter the link to the Telegram bot that you want to automatically process your chats.";
+"lng_chatbots_access_title" = "Chats accessible for the bot";
+"lng_chatbots_all_except" = "All 1-to-1 Chats Except...";
+"lng_chatbots_selected" = "Only Selected Chats";
+"lng_chatbots_excluded_title" = "Excluded chats";
+"lng_chatbots_exclude_button" = "Exclude Chats";
+"lng_chatbots_included_title" = "Included chats";
+"lng_chatbots_include_button" = "Select Chats";
+"lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to.";
+"lng_chatbots_permissions_title" = "Bot permissions";
+"lng_chatbots_reply" = "Reply to Messages";
+"lng_chatbots_reply_about" = "The bot will be able to view all new incoming messages, but not the messages that had been sent before you added the bot.";
+"lng_chatbots_remove" = "Remove Bot";
+
"lng_boost_channel_button" = "Boost Channel";
"lng_boost_group_button" = "Boost Group";
"lng_boost_again_button" = "Boost Again";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index a129237ca..ede8feb2d 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -14,5 +14,6 @@
../../animations/voice_ttl_idle.tgs
../../animations/voice_ttl_start.tgs
../../animations/palette.tgs
+ ../../animations/robot.tgs
diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
index 1f24ecd76..f8a7ea6e8 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
@@ -260,11 +260,11 @@ void Controller::choose(not_null chat) {
const auto init = [=](not_null box) {
auto above = object_ptr(box);
- Settings::AddDividerTextWithLottie(
- above,
- box->showFinishes(),
- About(channel, chat),
- u"discussion"_q);
+ Settings::AddDividerTextWithLottie(above, {
+ .lottie = u"discussion"_q,
+ .showFinished = box->showFinishes(),
+ .about = About(channel, chat),
+ });
if (!chat) {
Assert(channel->isBroadcast());
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
index 8fccba41a..3f01fe269 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
@@ -1223,35 +1223,15 @@ void EditPeerColorBox(
state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId();
-
if (group) {
- const auto divider = Ui::CreateChild(
- box.get());
- const auto verticalLayout = box->verticalLayout()->add(
- object_ptr(box.get()));
-
- auto icon = CreateLottieIcon(
- verticalLayout,
- {
- .name = u"palette"_q,
- .sizeOverride = Size(st::settingsCloudPasswordIconSize),
- },
- st::peerAppearanceIconPadding);
- box->setShowFinishedCallback([animate = std::move(icon.animate)] {
- animate(anim::repeat::once);
+ Settings::AddDividerTextWithLottie(box->verticalLayout(), {
+ .lottie = u"palette"_q,
+ .lottieSize = st::settingsCloudPasswordIconSize,
+ .lottieMargins = st::peerAppearanceIconPadding,
+ .showFinished = box->showFinishes(),
+ .about = tr::lng_boost_group_about(Ui::Text::WithEntities),
+ .aboutMargins = st::peerAppearanceCoverLabelMargin,
});
- verticalLayout->add(std::move(icon.widget));
- verticalLayout->add(
- object_ptr(
- verticalLayout,
- tr::lng_boost_group_about(),
- st::peerAppearanceCoverLabel),
- st::peerAppearanceCoverLabelMargin);
-
- verticalLayout->geometryValue(
- ) | rpl::start_with_next([=](const QRect &r) {
- divider->setGeometry(r);
- }, divider->lifetime());
} else {
box->addRow(object_ptr(
box,
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index fbb9bb533..82c115774 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -669,6 +669,17 @@ bool ShowSearchTagsPromo(
return true;
}
+bool ShowAboutBusinessChatbots(
+ Window::SessionController *controller,
+ const Match &match,
+ const QVariant &context) {
+ if (!controller) {
+ return false;
+ }
+ controller->showToast(u"Cool feature, yeah.."_q); AssertIsDebug();
+ return true;
+}
+
void ExportTestChatTheme(
not_null controller,
not_null theme) {
@@ -1036,7 +1047,11 @@ const std::vector &InternalUrlHandlers() {
{
u"about_tags"_q,
ShowSearchTagsPromo
- }
+ },
+ {
+ u"about_business_chatbots"_q,
+ ShowAboutBusinessChatbots
+ },
};
return Result;
}
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index b85703cf0..0943e3ac1 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/history_inner_widget.h"
+#include "chat_helpers/stickers_emoji_pack.h"
#include "core/file_utilities.h"
#include "core/click_handler_types.h"
#include "history/history_item_helpers.h"
@@ -32,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/message_sending_animation_controller.h"
#include "ui/effects/reaction_fly_animation.h"
#include "ui/text/text_options.h"
+#include "ui/text/text_isolated_emoji.h"
#include "ui/boxes/report_box.h"
#include "ui/layers/generic_box.h"
#include "ui/controls/delete_message_context_action.h"
@@ -2239,6 +2241,17 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
};
+ if (const auto item = _dragStateItem) {
+ const auto emojiStickers = &session->emojiStickersPack();
+ if (const auto view = item->mainView()) {
+ if (const auto isolated = view->isolatedEmoji()) {
+ if (const auto sticker = emojiStickers->stickerForEmoji(isolated)) {
+ addDocumentActions(sticker.document, item);
+ }
+ }
+ }
+ }
+
const auto asGroup = !Element::Moused()
|| (Element::Moused() != Element::Hovered())
|| (Element::Moused()->pointState(
diff --git a/Telegram/SourceFiles/settings/business/settings_chatbots.cpp b/Telegram/SourceFiles/settings/business/settings_chatbots.cpp
new file mode 100644
index 000000000..34969c7d9
--- /dev/null
+++ b/Telegram/SourceFiles/settings/business/settings_chatbots.cpp
@@ -0,0 +1,201 @@
+/*
+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_chatbots.h"
+
+#include "lang/lang_keys.h"
+#include "settings/settings_common_session.h"
+#include "ui/text/text_utilities.h"
+#include "ui/widgets/fields/input_field.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/wrap/slide_wrap.h"
+#include "ui/wrap/vertical_layout.h"
+#include "ui/vertical_list.h"
+#include "styles/style_layers.h"
+#include "styles/style_settings.h"
+
+namespace Settings {
+namespace {
+
+constexpr auto kAllExcept = 0;
+constexpr auto kSelectedOnly = 1;
+
+class Chatbots : public Section {
+public:
+ Chatbots(
+ QWidget *parent,
+ not_null controller);
+
+ [[nodiscard]] rpl::producer title() override;
+
+ rpl::producer<> showFinishes() const {
+ return _showFinished.events();
+ }
+
+ const Ui::RoundRect *bottomSkipRounding() const {
+ return &_bottomSkipRounding;
+ }
+
+private:
+ void setupContent(not_null controller);
+
+ void showFinished() override {
+ _showFinished.fire({});
+ }
+
+ rpl::event_stream<> _showFinished;
+ Ui::RoundRect _bottomSkipRounding;
+
+};
+
+Chatbots::Chatbots(
+ QWidget *parent,
+ not_null controller)
+: Section(parent)
+, _bottomSkipRounding(st::boxRadius, st::boxDividerBg) {
+ setupContent(controller);
+}
+
+rpl::producer Chatbots::title() {
+ return tr::lng_chatbots_title();
+}
+
+void Chatbots::setupContent(
+ not_null controller) {
+ using namespace rpl::mappers;
+
+ const auto content = Ui::CreateChild(this);
+
+ struct State {
+ rpl::variable onlySelected = false;
+ rpl::variable replyAllowed = true;
+ };
+ const auto state = content->lifetime().make_state();
+
+ AddDividerTextWithLottie(content, {
+ .lottie = u"robot"_q,
+ .lottieSize = st::settingsCloudPasswordIconSize,
+ .lottieMargins = st::peerAppearanceIconPadding,
+ .showFinished = showFinishes(),
+ .about = tr::lng_chatbots_about(
+ lt_link,
+ tr::lng_chatbots_about_link(
+ ) | Ui::Text::ToLink(u"internal:about_business_chatbots"_q),
+ Ui::Text::WithEntities),
+ .aboutMargins = st::peerAppearanceCoverLabelMargin,
+ });
+
+ const auto username = content->add(
+ object_ptr(
+ content,
+ st::settingsChatbotsUsername,
+ tr::lng_chatbots_placeholder()),
+ st::settingsChatbotsUsernameMargins);
+
+ Ui::AddDividerText(
+ content,
+ tr::lng_chatbots_add_about(),
+ st::peerAppearanceDividerTextMargin);
+ Ui::AddSkip(content);
+ Ui::AddSubsectionTitle(content, tr::lng_chatbots_access_title());
+
+ const auto group = std::make_shared(
+ state->onlySelected.current() ? kSelectedOnly : kAllExcept);
+ const auto everyone = content->add(
+ object_ptr(
+ content,
+ group,
+ kAllExcept,
+ tr::lng_chatbots_all_except(tr::now),
+ st::settingsChatbotsAccess),
+ st::settingsChatbotsAccessMargins);
+ const auto selected = content->add(
+ object_ptr(
+ content,
+ group,
+ kSelectedOnly,
+ tr::lng_chatbots_selected(tr::now),
+ st::settingsChatbotsAccess),
+ st::settingsChatbotsAccessMargins);
+
+ Ui::AddSkip(content, st::settingsChatbotsAccessSkip);
+ Ui::AddDivider(content);
+
+ const auto excludeWrap = content->add(
+ object_ptr>(
+ content,
+ object_ptr(content))
+ )->setDuration(0);
+ const auto excludeInner = excludeWrap->entity();
+
+ Ui::AddSkip(excludeInner);
+ Ui::AddSubsectionTitle(excludeInner, tr::lng_chatbots_excluded_title());
+ const auto excludeAdd = AddButtonWithIcon(
+ excludeInner,
+ tr::lng_chatbots_exclude_button(),
+ st::settingsChatbotsAdd,
+ { &st::settingsIconRemove, IconType::Round, &st::windowBgActive });
+
+ excludeWrap->toggleOn(state->onlySelected.value() | rpl::map(!_1));
+ excludeWrap->finishAnimating();
+
+ const auto includeWrap = content->add(
+ object_ptr>(
+ content,
+ object_ptr(content))
+ )->setDuration(0);
+ const auto includeInner = includeWrap->entity();
+
+ Ui::AddSkip(includeInner);
+ Ui::AddSubsectionTitle(includeInner, tr::lng_chatbots_included_title());
+ const auto includeAdd = AddButtonWithIcon(
+ includeInner,
+ tr::lng_chatbots_include_button(),
+ st::settingsChatbotsAdd,
+ { &st::settingsIconAdd, IconType::Round, &st::windowBgActive });
+
+ includeWrap->toggleOn(state->onlySelected.value());
+ includeWrap->finishAnimating();
+
+ group->setChangedCallback([=](int value) {
+ state->onlySelected = (value == kSelectedOnly);
+ });
+
+ Ui::AddSkip(content, st::settingsChatbotsAccessSkip);
+ Ui::AddDividerText(
+ content,
+ tr::lng_chatbots_exclude_about(),
+ st::peerAppearanceDividerTextMargin);
+
+ Ui::AddSkip(content);
+ Ui::AddSubsectionTitle(content, tr::lng_chatbots_permissions_title());
+ content->add(object_ptr(
+ content,
+ tr::lng_chatbots_reply(),
+ st::settingsButtonNoIcon
+ ))->toggleOn(state->replyAllowed.value())->toggledChanges(
+ ) | rpl::start_with_next([=](bool value) {
+ state->replyAllowed = value;
+ }, content->lifetime());
+ Ui::AddSkip(content);
+
+ Ui::AddDividerText(
+ content,
+ tr::lng_chatbots_reply_about(),
+ st::settingsChatbotsBottomTextMargin,
+ RectPart::Top);
+
+ Ui::ResizeFitChild(this, content);
+}
+
+} // namespace
+
+Type ChatbotsId() {
+ return Chatbots::Id();
+}
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/business/settings_chatbots.h b/Telegram/SourceFiles/settings/business/settings_chatbots.h
new file mode 100644
index 000000000..06fab806c
--- /dev/null
+++ b/Telegram/SourceFiles/settings/business/settings_chatbots.h
@@ -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 ChatbotsId();
+
+} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp
index d2f8f9569..2d273895e 100644
--- a/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp
+++ b/Telegram/SourceFiles/settings/cloud_password/settings_cloud_password_manage.cpp
@@ -121,12 +121,12 @@ void Manage::setupContent() {
showOther(type);
};
- AddDividerTextWithLottie(
- content,
- showFinishes(),
- tr::lng_settings_cloud_password_manage_about1(
+ AddDividerTextWithLottie(content, {
+ .lottie = u"cloud_password/intro"_q,
+ .showFinished = showFinishes(),
+ .about = tr::lng_settings_cloud_password_manage_about1(
TextWithEntities::Simple),
- u"cloud_password/intro"_q);
+ });
Ui::AddSkip(content);
AddButtonWithIcon(
diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style
index eb6bcb360..f4eebf4ff 100644
--- a/Telegram/SourceFiles/settings/settings.style
+++ b/Telegram/SourceFiles/settings/settings.style
@@ -589,9 +589,19 @@ peerAppearanceButton: SettingsButton(settingsButtonLight) {
padding: margins(60px, 8px, 22px, 8px);
iconLeft: 20px;
}
-peerAppearanceCoverLabel: FlatLabel(boxDividerLabel) {
- align: align(top);
-}
peerAppearanceCoverLabelMargin: margins(22px, 0px, 22px, 17px);
peerAppearanceIconPadding: margins(0px, 15px, 0px, 5px);
peerAppearanceDividerTextMargin: margins(22px, 8px, 22px, 11px);
+
+settingsChatbotsUsername: InputField(defaultMultiSelectSearchField) {
+}
+settingsChatbotsAccess: Checkbox(defaultCheckbox) {
+ textPosition: point(18px, 2px);
+}
+settingsChatbotsUsernameMargins: margins(20px, 8px, 20px, 8px);
+settingsChatbotsAccessMargins: margins(22px, 5px, 22px, 9px);
+settingsChatbotsAccessSkip: 4px;
+settingsChatbotsBottomTextMargin: margins(22px, 8px, 22px, 3px);
+settingsChatbotsAdd: SettingsButton(settingsButton) {
+ iconLeft: 22px;
+}
diff --git a/Telegram/SourceFiles/settings/settings_business.cpp b/Telegram/SourceFiles/settings/settings_business.cpp
index 23a4d8914..fd56adb41 100644
--- a/Telegram/SourceFiles/settings/settings_business.cpp
+++ b/Telegram/SourceFiles/settings/settings_business.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "lang/lang_keys.h"
#include "main/main_session.h"
+#include "settings/business/settings_chatbots.h"
#include "settings/settings_common_session.h"
#include "settings/settings_premium.h"
#include "ui/effects/gradient.h"
@@ -287,6 +288,7 @@ public:
void setStepDataReference(std::any &data) override;
[[nodiscard]] rpl::producer<> sectionShowBack() override final;
+ [[nodiscard]] rpl::producer sectionShowOther() override;
private:
void setupContent();
@@ -299,9 +301,10 @@ private:
rpl::variable _backToggles;
rpl::variable _wrap;
Fn _setPaused;
-
std::shared_ptr _radioGroup;
+ rpl::event_stream _showOther;
+
rpl::event_stream<> _showBack;
rpl::event_stream<> _showFinished;
rpl::variable _buttonText;
@@ -330,6 +333,10 @@ rpl::producer<> Business::sectionShowBack() {
return _showBack.events();
}
+rpl::producer Business::sectionShowOther() {
+ return _showOther.events();
+}
+
void Business::setStepDataReference(std::any &data) {
using namespace Info::Settings;
const auto my = std::any_cast(&data);
@@ -347,6 +354,11 @@ void Business::setupContent() {
Ui::AddSkip(content, st::settingsFromFileTop);
AddBusinessSummary(content, _controller, [=](BusinessFeature feature) {
+ switch (feature) {
+ case BusinessFeature::Chatbots:
+ _showOther.fire(Settings::ChatbotsId());
+ break;
+ }
});
Ui::ResizeFitChild(this, content);
diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp
index f9de195fc..7e4ac4180 100644
--- a/Telegram/SourceFiles/settings/settings_common.cpp
+++ b/Telegram/SourceFiles/settings/settings_common.cpp
@@ -170,39 +170,43 @@ not_null