mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-13 23:13:32 +00:00
Support global search in chat automation.
This commit is contained in:
@@ -3444,6 +3444,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_chatbots_not_found" = "Chatbot not found.";
|
||||
"lng_chatbots_not_supported" = "This bot doesn't support Telegram Business yet.";
|
||||
"lng_chatbots_add" = "Add";
|
||||
"lng_chatbots_leave_without_added_title" = "No Bot Added";
|
||||
"lng_chatbots_leave_without_added_text" = "You haven't added a bot to manage your account. Leave anyway?";
|
||||
"lng_chatbots_added_success" = "{bot} now manages your account.";
|
||||
|
||||
"lng_chat_automation_title" = "Chat Automation";
|
||||
"lng_chat_automation_about" = "Add a bot to answer messages on your behalf.";
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_permissions_box.h"
|
||||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "config.h"
|
||||
#include "core/application.h"
|
||||
#include "data/business/data_business_chatbots.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -21,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
@@ -29,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
@@ -36,6 +39,7 @@ namespace Settings {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDebounceTimeout = crl::time(400);
|
||||
constexpr auto kMaxSearchBots = 3;
|
||||
|
||||
enum class LookupState {
|
||||
Empty,
|
||||
@@ -44,9 +48,10 @@ enum class LookupState {
|
||||
Ready,
|
||||
};
|
||||
|
||||
struct BotState {
|
||||
UserData *bot = nullptr;
|
||||
struct BotSearchState {
|
||||
std::vector<UserData*> bots;
|
||||
LookupState state = LookupState::Empty;
|
||||
bool exactUnsupported = false;
|
||||
};
|
||||
|
||||
enum class PreviewActionKind {
|
||||
@@ -66,11 +71,14 @@ public:
|
||||
~Chatbots();
|
||||
|
||||
[[nodiscard]] bool closeByOutsideClick() const override;
|
||||
void checkBeforeClose(Fn<void()> close) override;
|
||||
[[nodiscard]] rpl::producer<QString> title() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
const Ui::RoundRect *bottomSkipRounding() const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool shouldConfirmLeaveWithoutAddedBot() const;
|
||||
void setupContent();
|
||||
void refreshDetails();
|
||||
void save();
|
||||
@@ -79,13 +87,15 @@ private:
|
||||
|
||||
Ui::VerticalLayout *_detailsWrap = nullptr;
|
||||
Ui::VerticalLayout *_permissionsWrap = nullptr;
|
||||
Ui::SlideWrap<Ui::InputField> *_usernameWrap = nullptr;
|
||||
|
||||
rpl::variable<Data::BusinessRecipients> _recipients;
|
||||
rpl::variable<UserData*> _committedBot;
|
||||
Data::BusinessRecipients _committedRecipients;
|
||||
Data::ChatbotsPermissions _committedPermissions = Defaults();
|
||||
rpl::variable<QString> _usernameValue;
|
||||
rpl::variable<BotState> _botValue;
|
||||
rpl::variable<BotSearchState> _lookupState;
|
||||
rpl::variable<bool> _chooserVisible = false;
|
||||
rpl::variable<Data::ChatbotsPermissions> _permissions = Defaults();
|
||||
Fn<Data::ChatbotsPermissions()> _resolvePermissions;
|
||||
|
||||
@@ -94,10 +104,10 @@ private:
|
||||
class PreviewController final : public PeerListController {
|
||||
public:
|
||||
PreviewController(
|
||||
not_null<PeerData*> peer,
|
||||
PreviewActionKind kind,
|
||||
std::vector<UserData*> bots,
|
||||
UserData *committedBot,
|
||||
Fn<void()> resetBot,
|
||||
Fn<void()> addBot);
|
||||
Fn<void(UserData*)> addBot);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
@@ -106,10 +116,10 @@ public:
|
||||
Main::Session &session() const override;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
const PreviewActionKind _kind;
|
||||
const std::vector<UserData*> _bots;
|
||||
UserData *const _committedBot = nullptr;
|
||||
const Fn<void()> _resetBot;
|
||||
const Fn<void()> _addBot;
|
||||
const Fn<void(UserData*)> _addBot;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
@@ -153,6 +163,10 @@ PreviewRow::PreviewRow(
|
||||
, _addTextWidth((_kind == PreviewActionKind::Add)
|
||||
? st::settingsChatbotsAddButton.style.font->width(_addText)
|
||||
: 0) {
|
||||
const auto username = peer->username();
|
||||
if (!username.isEmpty()) {
|
||||
setCustomStatus('@' + username);
|
||||
}
|
||||
}
|
||||
|
||||
QSize PreviewRow::addPillSize() const {
|
||||
@@ -246,19 +260,24 @@ void PreviewRow::rightActionStopLastRipple() {
|
||||
}
|
||||
|
||||
PreviewController::PreviewController(
|
||||
not_null<PeerData*> peer,
|
||||
PreviewActionKind kind,
|
||||
std::vector<UserData*> bots,
|
||||
UserData *committedBot,
|
||||
Fn<void()> resetBot,
|
||||
Fn<void()> addBot)
|
||||
: _peer(peer)
|
||||
, _kind(kind)
|
||||
Fn<void(UserData*)> addBot)
|
||||
: _bots(std::move(bots))
|
||||
, _committedBot(committedBot)
|
||||
, _resetBot(std::move(resetBot))
|
||||
, _addBot(std::move(addBot)) {
|
||||
}
|
||||
|
||||
void PreviewController::prepare() {
|
||||
delegate()->peerListAppendRow(
|
||||
std::make_unique<PreviewRow>(_peer, _kind));
|
||||
for (const auto bot : _bots) {
|
||||
const auto kind = (bot == _committedBot)
|
||||
? PreviewActionKind::Remove
|
||||
: PreviewActionKind::Add;
|
||||
delegate()->peerListAppendRow(
|
||||
std::make_unique<PreviewRow>(bot, kind));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
@@ -270,15 +289,19 @@ void PreviewController::rowClicked(not_null<PeerListRow*> row) {
|
||||
|
||||
void PreviewController::rowRightActionClicked(
|
||||
not_null<PeerListRow*> row) {
|
||||
if (_kind == PreviewActionKind::Add) {
|
||||
_addBot();
|
||||
} else {
|
||||
const auto user = row->peer()->asUser();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
if (user == _committedBot) {
|
||||
_resetBot();
|
||||
} else {
|
||||
_addBot(user);
|
||||
}
|
||||
}
|
||||
|
||||
Main::Session &PreviewController::session() const {
|
||||
return _peer->session();
|
||||
return _bots.front()->session();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> DebouncedValue(
|
||||
@@ -311,86 +334,191 @@ Main::Session &PreviewController::session() const {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] QString ExtractUsername(QString text) {
|
||||
struct ParsedUsernameQuery {
|
||||
QString value;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] ParsedUsernameQuery ParseUsernameQuery(QString text) {
|
||||
text = text.trimmed();
|
||||
if (text.startsWith(QChar('@'))) {
|
||||
return text.mid(1);
|
||||
text = text.mid(1);
|
||||
} else {
|
||||
static const auto extractExpression = QRegularExpression(
|
||||
"^(https://)?([a-zA-Z0-9\\.]+/)?([a-zA-Z0-9_\\.]+)");
|
||||
const auto match = extractExpression.match(text);
|
||||
text = match.hasMatch() ? match.captured(3) : text;
|
||||
}
|
||||
static const auto expression = QRegularExpression(
|
||||
"^(https://)?([a-zA-Z0-9\\.]+/)?([a-zA-Z0-9_\\.]+)");
|
||||
const auto match = expression.match(text);
|
||||
return match.hasMatch() ? match.captured(3) : text;
|
||||
static const auto validateExpression = QRegularExpression(
|
||||
"^[a-zA-Z0-9_\\.]+$");
|
||||
return {
|
||||
.value = text,
|
||||
.valid = !text.isEmpty()
|
||||
&& validateExpression.match(text).hasMatch(),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<BotState> LookupBot(
|
||||
[[nodiscard]] bool IsUsableBusinessBot(UserData *user) {
|
||||
return user
|
||||
&& user->isBot()
|
||||
&& user->botInfo->supportsBusiness;
|
||||
}
|
||||
|
||||
[[nodiscard]] UserData *FirstBot(const BotSearchState &state) {
|
||||
return state.bots.empty() ? nullptr : state.bots.front();
|
||||
}
|
||||
|
||||
[[nodiscard]] BotSearchState SingleBotState(UserData *bot) {
|
||||
auto result = BotSearchState{
|
||||
.state = bot ? LookupState::Ready : LookupState::Empty,
|
||||
};
|
||||
if (bot) {
|
||||
result.bots.push_back(bot);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<BotSearchState, UserData*> PreviewState(
|
||||
BotSearchState lookupState,
|
||||
UserData *committed,
|
||||
bool chooserVisible,
|
||||
const QString &usernameText) {
|
||||
if (committed && !chooserVisible) {
|
||||
return { SingleBotState(committed), committed };
|
||||
}
|
||||
if (chooserVisible && ParseUsernameQuery(usernameText).valid) {
|
||||
return { std::move(lookupState), nullptr };
|
||||
}
|
||||
return { BotSearchState(), nullptr };
|
||||
}
|
||||
|
||||
void AppendUniqueBot(
|
||||
std::vector<UserData*> &bots,
|
||||
UserData *user) {
|
||||
if (!IsUsableBusinessBot(user)
|
||||
|| ranges::contains(bots, user)
|
||||
|| bots.size() >= kMaxSearchBots) {
|
||||
return;
|
||||
}
|
||||
bots.push_back(user);
|
||||
}
|
||||
|
||||
[[nodiscard]] UserData *UserFromPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPeer &peer) {
|
||||
return session->data().peer(peerFromMTP(peer))->asUser();
|
||||
}
|
||||
|
||||
struct BotSearchRequest {
|
||||
UserData *exact = nullptr;
|
||||
std::vector<UserData*> my;
|
||||
std::vector<UserData*> global;
|
||||
mtpRequestId exactRequestId = 0;
|
||||
mtpRequestId searchRequestId = 0;
|
||||
bool exactUnsupported = false;
|
||||
bool exactDone = false;
|
||||
bool searchDone = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] BotSearchState MakeReadySearchState(
|
||||
const BotSearchRequest &request) {
|
||||
auto bots = std::vector<UserData*>();
|
||||
AppendUniqueBot(bots, request.exact);
|
||||
for (const auto user : request.my) {
|
||||
AppendUniqueBot(bots, user);
|
||||
}
|
||||
for (const auto user : request.global) {
|
||||
AppendUniqueBot(bots, user);
|
||||
}
|
||||
const auto unsupported = bots.empty()
|
||||
&& request.exactUnsupported;
|
||||
return {
|
||||
.bots = std::move(bots),
|
||||
.state = unsupported ? LookupState::Unsupported : LookupState::Ready,
|
||||
.exactUnsupported = request.exactUnsupported,
|
||||
};
|
||||
}
|
||||
|
||||
void AppendUsersFromPeerList(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPVector<MTPPeer> &list,
|
||||
std::vector<UserData*> &users) {
|
||||
users.reserve(users.size() + list.v.size());
|
||||
for (const auto &mtpPeer : list.v) {
|
||||
if (const auto user = UserFromPeer(session, mtpPeer)) {
|
||||
users.push_back(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<BotSearchState> LookupBots(
|
||||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> usernameChanges) {
|
||||
using Cache = base::flat_map<QString, UserData*>;
|
||||
const auto cache = std::make_shared<Cache>();
|
||||
return std::move(
|
||||
usernameChanges
|
||||
) | rpl::map([=](const QString &username) -> rpl::producer<BotState> {
|
||||
const auto extracted = ExtractUsername(username);
|
||||
const auto owner = &session->data();
|
||||
static const auto expression = QRegularExpression(
|
||||
"^[a-zA-Z0-9_\\.]+$");
|
||||
if (!expression.match(extracted).hasMatch()) {
|
||||
return rpl::single(BotState());
|
||||
} else if (const auto peer = owner->peerByUsername(extracted)) {
|
||||
if (const auto user = peer->asUser(); user && user->isBot()) {
|
||||
if (user->botInfo->supportsBusiness) {
|
||||
return rpl::single(BotState{
|
||||
.bot = user,
|
||||
.state = LookupState::Ready,
|
||||
});
|
||||
}
|
||||
return rpl::single(BotState{
|
||||
.state = LookupState::Unsupported,
|
||||
});
|
||||
}
|
||||
return rpl::single(BotState{
|
||||
.state = LookupState::Ready,
|
||||
});
|
||||
} else if (const auto i = cache->find(extracted); i != end(*cache)) {
|
||||
return rpl::single(BotState{
|
||||
.bot = i->second,
|
||||
.state = LookupState::Ready,
|
||||
});
|
||||
) | rpl::map([=](
|
||||
const QString &username) -> rpl::producer<BotSearchState> {
|
||||
const auto query = ParseUsernameQuery(username);
|
||||
if (!query.valid) {
|
||||
return rpl::single(BotSearchState());
|
||||
}
|
||||
const auto extracted = query.value;
|
||||
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
|
||||
const auto requestId = result.make_state<mtpRequestId>();
|
||||
*requestId = session->api().request(MTPcontacts_ResolveUsername(
|
||||
const auto data = result.make_state<BotSearchRequest>();
|
||||
const auto finish = [=] {
|
||||
if (!data->exactDone || !data->searchDone) {
|
||||
return;
|
||||
}
|
||||
consumer.put_next(MakeReadySearchState(*data));
|
||||
};
|
||||
|
||||
consumer.put_next(BotSearchState{
|
||||
.state = LookupState::Loading,
|
||||
});
|
||||
|
||||
data->exactRequestId = session->api().request(MTPcontacts_ResolveUsername(
|
||||
MTP_flags(0),
|
||||
MTP_string(extracted),
|
||||
MTP_string()
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
const auto &data = result.data();
|
||||
session->data().processUsers(data.vusers());
|
||||
session->data().processChats(data.vchats());
|
||||
const auto peerId = peerFromMTP(data.vpeer());
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->isBot()) {
|
||||
cache->emplace(extracted, user);
|
||||
consumer.put_next(BotState{
|
||||
.bot = user,
|
||||
.state = LookupState::Ready,
|
||||
});
|
||||
return;
|
||||
const auto &resolved = result.data();
|
||||
session->data().processUsers(resolved.vusers());
|
||||
session->data().processChats(resolved.vchats());
|
||||
if (const auto user = UserFromPeer(session, resolved.vpeer())) {
|
||||
if (IsUsableBusinessBot(user)) {
|
||||
data->exact = user;
|
||||
} else if (user->isBot()) {
|
||||
data->exactUnsupported = true;
|
||||
}
|
||||
}
|
||||
cache->emplace(extracted, nullptr);
|
||||
consumer.put_next(BotState{ .state = LookupState::Ready });
|
||||
data->exactDone = true;
|
||||
finish();
|
||||
}).fail([=] {
|
||||
cache->emplace(extracted, nullptr);
|
||||
consumer.put_next(BotState{ .state = LookupState::Ready });
|
||||
data->exactDone = true;
|
||||
finish();
|
||||
}).send();
|
||||
data->searchRequestId = session->api().request(MTPcontacts_Search(
|
||||
MTP_string(extracted),
|
||||
MTP_int(SearchPeopleLimit)
|
||||
)).done([=](const MTPcontacts_Found &result) {
|
||||
const auto &found = result.data();
|
||||
session->data().processUsers(found.vusers());
|
||||
session->data().processChats(found.vchats());
|
||||
AppendUsersFromPeerList(session, found.vmy_results(), data->my);
|
||||
AppendUsersFromPeerList(session, found.vresults(), data->global);
|
||||
data->searchDone = true;
|
||||
finish();
|
||||
}).fail([=] {
|
||||
data->searchDone = true;
|
||||
finish();
|
||||
}).send();
|
||||
|
||||
result.add([=] {
|
||||
session->api().request(*requestId).cancel();
|
||||
session->api().request(data->exactRequestId).cancel();
|
||||
session->api().request(data->searchRequestId).cancel();
|
||||
});
|
||||
return result;
|
||||
};
|
||||
@@ -399,9 +527,9 @@ Main::Session &PreviewController::session() const {
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeBotPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
rpl::producer<std::pair<BotState, PreviewActionKind>> stateAndKind,
|
||||
rpl::producer<std::pair<BotSearchState, UserData*>> stateAndBot,
|
||||
Fn<void()> resetBot,
|
||||
Fn<void()> addBot) {
|
||||
Fn<void(UserData*)> addBot) {
|
||||
auto result = object_ptr<Ui::SlideWrap<>>(
|
||||
parent.get(),
|
||||
object_ptr<Ui::RpWidget>(parent.get()));
|
||||
@@ -410,22 +538,22 @@ Main::Session &PreviewController::session() const {
|
||||
raw->hide(anim::type::instant);
|
||||
|
||||
const auto child = inner->lifetime().make_state<Ui::RpWidget*>(nullptr);
|
||||
std::move(stateAndKind) | rpl::filter([=](const auto &pair) {
|
||||
return pair.first.state != LookupState::Loading;
|
||||
}) | rpl::on_next([=](std::pair<BotState, PreviewActionKind> pair) {
|
||||
std::move(stateAndBot) | rpl::on_next([=](
|
||||
std::pair<BotSearchState, UserData*> pair) {
|
||||
const auto &state = pair.first;
|
||||
const auto kind = pair.second;
|
||||
raw->toggle(
|
||||
(state.state == LookupState::Ready
|
||||
|| state.state == LookupState::Unsupported),
|
||||
anim::type::normal);
|
||||
if (state.bot) {
|
||||
const auto committed = pair.second;
|
||||
const auto hasBots = !state.bots.empty();
|
||||
const auto hasLabel = !hasBots
|
||||
&& (state.state == LookupState::Ready
|
||||
|| state.state == LookupState::Unsupported);
|
||||
raw->toggle(hasBots || hasLabel, anim::type::normal);
|
||||
if (hasBots) {
|
||||
const auto delegate = parent->lifetime().make_state<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = parent->lifetime().make_state<
|
||||
PreviewController
|
||||
>(state.bot, kind, resetBot, addBot);
|
||||
>(state.bots, committed, resetBot, addBot);
|
||||
controller->setStyleOverrides(&st::peerListSingleRow);
|
||||
const auto content = Ui::CreateChild<PeerListContent>(
|
||||
inner,
|
||||
@@ -434,8 +562,7 @@ Main::Session &PreviewController::session() const {
|
||||
controller->setDelegate(delegate);
|
||||
delete base::take(*child);
|
||||
*child = content;
|
||||
} else if (state.state == LookupState::Ready
|
||||
|| state.state == LookupState::Unsupported) {
|
||||
} else if (hasLabel) {
|
||||
const auto content = Ui::CreateChild<Ui::RpWidget>(inner);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
content,
|
||||
@@ -457,6 +584,7 @@ Main::Session &PreviewController::session() const {
|
||||
delete base::take(*child);
|
||||
*child = content;
|
||||
} else {
|
||||
delete base::take(*child);
|
||||
return;
|
||||
}
|
||||
(*child)->show();
|
||||
@@ -467,7 +595,7 @@ Main::Session &PreviewController::session() const {
|
||||
|
||||
(*child)->heightValue() | rpl::on_next([=](int height) {
|
||||
inner->resize(inner->width(), height + st::contactSkip);
|
||||
}, inner->lifetime());
|
||||
}, (*child)->lifetime());
|
||||
}, inner->lifetime());
|
||||
|
||||
raw->finishAnimating();
|
||||
@@ -488,14 +616,49 @@ Chatbots::~Chatbots() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Chatbots::shouldConfirmLeaveWithoutAddedBot() const {
|
||||
if (!_chooserVisible.current() || _committedBot.current()) {
|
||||
return false;
|
||||
}
|
||||
const auto parsed = ParseUsernameQuery(_usernameValue.current());
|
||||
const auto ¤t = _lookupState.current();
|
||||
return parsed.valid
|
||||
&& !current.bots.empty();
|
||||
}
|
||||
|
||||
bool Chatbots::closeByOutsideClick() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Chatbots::checkBeforeClose(Fn<void()> close) {
|
||||
if (!shouldConfirmLeaveWithoutAddedBot()) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
controller()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_chatbots_leave_without_added_text(),
|
||||
.confirmed = crl::guard(this, [=](Fn<void()> closeBox) {
|
||||
closeBox();
|
||||
close();
|
||||
}),
|
||||
.confirmText = tr::lng_box_leave(),
|
||||
.cancelText = tr::lng_cancel(),
|
||||
.title = tr::lng_chatbots_leave_without_added_title(),
|
||||
}));
|
||||
}
|
||||
|
||||
rpl::producer<QString> Chatbots::title() {
|
||||
return tr::lng_chat_automation_title();
|
||||
}
|
||||
|
||||
void Chatbots::setInnerFocus() {
|
||||
if (_chooserVisible.current()) {
|
||||
_usernameWrap->entity()->setFocus();
|
||||
} else {
|
||||
AbstractSection::setInnerFocus();
|
||||
}
|
||||
}
|
||||
|
||||
const Ui::RoundRect *Chatbots::bottomSkipRounding() const {
|
||||
return _permissionsWrap->count() ? nullptr : &_bottomSkipRounding;
|
||||
}
|
||||
@@ -521,58 +684,81 @@ void Chatbots::setupContent() {
|
||||
.aboutMargins = st::peerAppearanceCoverLabelMargin,
|
||||
});
|
||||
|
||||
const auto username = content->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
const auto usernameWrap = content->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::InputField>>(
|
||||
content,
|
||||
st::settingsChatbotsUsername,
|
||||
tr::lng_chatbots_placeholder(),
|
||||
(current.bot
|
||||
? current.bot->session().createInternalLink(
|
||||
current.bot->username())
|
||||
: QString())),
|
||||
st::settingsChatbotsUsernameMargins);
|
||||
object_ptr<Ui::InputField>(
|
||||
content,
|
||||
st::settingsChatbotsUsername,
|
||||
tr::lng_chatbots_placeholder(),
|
||||
QString()),
|
||||
st::settingsChatbotsUsernameMargins),
|
||||
QMargins(0, st::settingsChatbotsUsernameMargins.bottom(), 0, 0));
|
||||
usernameWrap->setDuration(0);
|
||||
_usernameWrap = usernameWrap;
|
||||
_chooserVisible = !current.bot;
|
||||
_usernameWrap->toggle(_chooserVisible.current(), anim::type::instant);
|
||||
const auto username = usernameWrap->entity();
|
||||
|
||||
_usernameValue = DebouncedValue(username);
|
||||
_botValue = rpl::single(BotState{
|
||||
current.bot,
|
||||
current.bot ? LookupState::Ready : LookupState::Empty
|
||||
}) | rpl::then(
|
||||
LookupBot(&controller()->session(), _usernameValue.changes())
|
||||
_usernameValue = rpl::single(
|
||||
username->getLastText()
|
||||
) | rpl::then(
|
||||
username->changes() | rpl::map([=] {
|
||||
return username->getLastText();
|
||||
}));
|
||||
_lookupState = LookupBots(&controller()->session(), DebouncedValue(username)
|
||||
);
|
||||
|
||||
const auto resetBot = [=] {
|
||||
username->setText(QString());
|
||||
username->setFocus();
|
||||
_committedBot = nullptr;
|
||||
_recipients = Data::BusinessRecipients::MakeValid({});
|
||||
_permissions = Defaults();
|
||||
_committedRecipients = _recipients.current();
|
||||
_committedPermissions = _permissions.current();
|
||||
_chooserVisible = true;
|
||||
_usernameWrap->toggle(true, anim::type::instant);
|
||||
username->setFocus();
|
||||
};
|
||||
const auto addBot = [=] {
|
||||
const auto resolved = _botValue.current().bot;
|
||||
const auto addBot = [=](UserData *resolved) {
|
||||
if (!resolved) {
|
||||
return;
|
||||
}
|
||||
_committedBot = resolved;
|
||||
_committedRecipients = _recipients.current();
|
||||
_committedPermissions = _resolvePermissions();
|
||||
_chooserVisible = false;
|
||||
_usernameWrap->toggle(false, anim::type::instant);
|
||||
controller()->showToast(Ui::Toast::Config{
|
||||
.text = { tr::lng_chatbots_added_success(
|
||||
tr::now,
|
||||
lt_bot,
|
||||
resolved->name()) },
|
||||
.icon = &st::toastCheckIcon,
|
||||
});
|
||||
};
|
||||
auto stateAndKind = rpl::combine(
|
||||
_botValue.value(),
|
||||
_committedBot.value()
|
||||
) | rpl::map([](BotState state, UserData *committed) {
|
||||
const auto kind = (state.bot && state.bot == committed)
|
||||
? PreviewActionKind::Remove
|
||||
: PreviewActionKind::Add;
|
||||
return std::make_pair(state, kind);
|
||||
auto stateAndBot = rpl::combine(
|
||||
_lookupState.value(),
|
||||
_committedBot.value(),
|
||||
_chooserVisible.value(),
|
||||
_usernameValue.value()
|
||||
) | rpl::map([](
|
||||
BotSearchState lookupState,
|
||||
UserData *committed,
|
||||
bool chooserVisible,
|
||||
const QString &usernameText) {
|
||||
return PreviewState(
|
||||
std::move(lookupState),
|
||||
committed,
|
||||
chooserVisible,
|
||||
usernameText);
|
||||
});
|
||||
|
||||
content->add(object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
|
||||
content,
|
||||
MakeBotPreview(
|
||||
content,
|
||||
std::move(stateAndKind),
|
||||
std::move(stateAndBot),
|
||||
resetBot,
|
||||
addBot)));
|
||||
|
||||
@@ -603,8 +789,10 @@ void Chatbots::setupContent() {
|
||||
|
||||
refreshDetails();
|
||||
rpl::merge(
|
||||
_chooserVisible.changes() | rpl::to_empty,
|
||||
_committedBot.changes() | rpl::to_empty,
|
||||
_botValue.changes() | rpl::to_empty
|
||||
_lookupState.changes() | rpl::to_empty,
|
||||
_usernameValue.changes() | rpl::to_empty
|
||||
) | rpl::on_next([=] {
|
||||
refreshDetails();
|
||||
}, lifetime());
|
||||
@@ -620,9 +808,13 @@ void Chatbots::refreshDetails() {
|
||||
delete _permissionsWrap->widgetAt(0);
|
||||
}
|
||||
|
||||
const auto bot = _botValue.current().bot
|
||||
? _botValue.current().bot
|
||||
: _committedBot.current();
|
||||
auto bot = _committedBot.current();
|
||||
if (!bot && _chooserVisible.current()) {
|
||||
const auto parsed = ParseUsernameQuery(_usernameValue.current());
|
||||
if (parsed.valid) {
|
||||
bot = FirstBot(_lookupState.current());
|
||||
}
|
||||
}
|
||||
if (!bot) {
|
||||
_permissionsWrap->resizeToWidth(width());
|
||||
return;
|
||||
|
||||
@@ -666,7 +666,7 @@ settingsChatbotsAccess: Checkbox(defaultCheckbox) {
|
||||
}
|
||||
settingsLocationAddress: InputField(defaultMultiSelectSearchField) {
|
||||
}
|
||||
settingsChatbotsUsernameMargins: margins(20px, 8px, 20px, 8px);
|
||||
settingsChatbotsUsernameMargins: margins(20px, 0px, 20px, 8px);
|
||||
settingsChatbotsAccessMargins: margins(22px, 5px, 22px, 9px);
|
||||
settingsChatbotsAccessSkip: 4px;
|
||||
settingsChatbotsBottomTextMargin: margins(22px, 8px, 22px, 3px);
|
||||
|
||||
Reference in New Issue
Block a user