mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 21:27:07 +02:00
Support business bot state in chat.
This commit is contained in:
parent
3d97ea6f96
commit
cf1d0677d1
20 changed files with 588 additions and 52 deletions
|
@ -2288,6 +2288,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_chatbots_not_found" = "Chatbot not found.";
|
||||
"lng_chatbots_add" = "Add";
|
||||
"lng_chatbots_info_url" = "https://telegram.org/privacy";
|
||||
"lng_chatbot_status_can_reply" = "bot manages this chat";
|
||||
"lng_chatbot_status_paused" = "bot stopped";
|
||||
"lng_chatbot_status_views" = "bot has access to this chat";
|
||||
"lng_chatbot_button_pause" = "Stop";
|
||||
"lng_chatbot_button_resume" = "Start";
|
||||
"lng_chatbot_menu_manage" = "Manage bot";
|
||||
"lng_chatbot_menu_remove" = "Remove bot from this chat";
|
||||
"lng_chatbot_menu_revoke" = "Revoke access to this chat";
|
||||
|
||||
"lng_boost_channel_button" = "Boost Channel";
|
||||
"lng_boost_group_button" = "Boost Group";
|
||||
|
|
|
@ -351,9 +351,9 @@ Main::Session &EditFilterChatsListController::session() const {
|
|||
}
|
||||
|
||||
int EditFilterChatsListController::selectedTypesCount() const {
|
||||
Expects(_chatlist || _typesDelegate != nullptr);
|
||||
Expects(_chatlist || !_options || _typesDelegate != nullptr);
|
||||
|
||||
if (_chatlist) {
|
||||
if (_chatlist || !_options) {
|
||||
return 0;
|
||||
}
|
||||
auto result = 0;
|
||||
|
@ -396,7 +396,7 @@ bool EditFilterChatsListController::handleDeselectForeignRow(
|
|||
|
||||
void EditFilterChatsListController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(std::move(_title));
|
||||
if (!_chatlist) {
|
||||
if (!_chatlist && _options) {
|
||||
delegate()->peerListSetAboveWidget(prepareTypesList());
|
||||
}
|
||||
|
||||
|
@ -479,7 +479,8 @@ object_ptr<Ui::RpWidget> EditFilterChatsListController::prepareTypesList() {
|
|||
|
||||
auto EditFilterChatsListController::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
const auto business = _options & (Flag::NewChats | Flag::ExistingChats);
|
||||
const auto business = (_options & (Flag::NewChats | Flag::ExistingChats))
|
||||
|| (!_options && !_chatlist);
|
||||
if (business && (history->peer->isSelf() || !history->peer->isUser())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -857,6 +857,31 @@ historyEmojiStatusInfoLabel: FlatLabel(historyContactStatusLabel) {
|
|||
}
|
||||
historyContactStatusMinSkip: 16px;
|
||||
|
||||
historyBusinessBotPhoto: UserpicButton(defaultUserpicButton) {
|
||||
size: size(46px, 46px);
|
||||
photoSize: 46px;
|
||||
photoPosition: point(0px, 0px);
|
||||
}
|
||||
historyBusinessBotName: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
historyBusinessBotStatus: FlatLabel(historyContactStatusLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
historyBusinessBotToggle: defaultActiveButton;
|
||||
historyBusinessBotSettings: IconButton(defaultIconButton) {
|
||||
icon: icon{{ "menu/customize", menuIconFg }};
|
||||
iconOver: icon{{ "menu/customize", menuIconFgOver }};
|
||||
iconPosition: point(-1px, -1px);
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(4px, 9px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
height: 58px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
historyReplyCancelIcon: icon {{ "box_button_close", historyReplyCancelFg }};
|
||||
historyReplyCancelIconOver: icon {{ "box_button_close", historyReplyCancelFgOver }};
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ void Chatbots::save(
|
|||
? Flag::f_can_reply
|
||||
: Flag()),
|
||||
(settings.bot ? settings.bot : was.bot)->inputUser,
|
||||
ToMTP(settings.recipients)
|
||||
ForBotsToMTP(settings.recipients)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
if (done) {
|
||||
|
@ -103,4 +103,72 @@ void Chatbots::save(
|
|||
_settings = settings;
|
||||
}
|
||||
|
||||
void Chatbots::togglePaused(not_null<PeerData*> peer, bool paused) {
|
||||
const auto type = paused
|
||||
? SentRequestType::Pause
|
||||
: SentRequestType::Unpause;
|
||||
const auto api = &_owner->session().api();
|
||||
const auto i = _sentRequests.find(peer);
|
||||
if (i != end(_sentRequests)) {
|
||||
const auto already = i->second.type;
|
||||
if (already == SentRequestType::Remove || already == type) {
|
||||
return;
|
||||
}
|
||||
api->request(i->second.requestId).cancel();
|
||||
_sentRequests.erase(i);
|
||||
}
|
||||
const auto id = api->request(MTPaccount_ToggleConnectedBotPaused(
|
||||
peer->input,
|
||||
MTP_bool(paused)
|
||||
)).done([=] {
|
||||
if (_sentRequests[peer].type != type) {
|
||||
return;
|
||||
} else if (const auto settings = peer->barSettings()) {
|
||||
peer->setBarSettings(paused
|
||||
? (*settings | PeerBarSetting::BusinessBotPaused)
|
||||
: (*settings & ~PeerBarSetting::BusinessBotPaused));
|
||||
} else {
|
||||
api->requestPeerSettings(peer);
|
||||
}
|
||||
_sentRequests.remove(peer);
|
||||
}).fail([=] {
|
||||
if (_sentRequests[peer].type != type) {
|
||||
return;
|
||||
}
|
||||
api->requestPeerSettings(peer);
|
||||
_sentRequests.remove(peer);
|
||||
}).send();
|
||||
_sentRequests[peer] = SentRequest{ type, id };
|
||||
}
|
||||
|
||||
void Chatbots::removeFrom(not_null<PeerData*> peer) {
|
||||
const auto type = SentRequestType::Remove;
|
||||
const auto api = &_owner->session().api();
|
||||
const auto i = _sentRequests.find(peer);
|
||||
if (i != end(_sentRequests)) {
|
||||
const auto already = i->second.type;
|
||||
if (already == type) {
|
||||
return;
|
||||
}
|
||||
api->request(i->second.requestId).cancel();
|
||||
_sentRequests.erase(i);
|
||||
}
|
||||
const auto id = api->request(MTPaccount_DisablePeerConnectedBot(
|
||||
peer->input
|
||||
)).done([=] {
|
||||
if (_sentRequests[peer].type != type) {
|
||||
return;
|
||||
} else if (const auto settings = peer->barSettings()) {
|
||||
peer->clearBusinessBot();
|
||||
} else {
|
||||
api->requestPeerSettings(peer);
|
||||
}
|
||||
_sentRequests.remove(peer);
|
||||
}).fail([=] {
|
||||
api->requestPeerSettings(peer);
|
||||
_sentRequests.remove(peer);
|
||||
}).send();
|
||||
_sentRequests[peer] = SentRequest{ type, id };
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -41,13 +41,28 @@ public:
|
|||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
|
||||
void togglePaused(not_null<PeerData*> peer, bool paused);
|
||||
void removeFrom(not_null<PeerData*> peer);
|
||||
|
||||
private:
|
||||
enum class SentRequestType {
|
||||
Pause,
|
||||
Unpause,
|
||||
Remove,
|
||||
};
|
||||
struct SentRequest {
|
||||
SentRequestType type = SentRequestType::Pause;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
rpl::variable<ChatbotsSettings> _settings;
|
||||
mtpRequestId _requestId = 0;
|
||||
bool _loaded = false;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, SentRequest> _sentRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -51,16 +51,13 @@ constexpr auto kInNextDayMax = WorkingInterval::kInNextDayMax;
|
|||
return intervals;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPInputBusinessRecipients ToMTP(
|
||||
const BusinessRecipients &data) {
|
||||
using Flag = MTPDinputBusinessRecipients::Flag;
|
||||
using Type = BusinessChatType;
|
||||
template <typename Flag>
|
||||
auto RecipientsFlags(const BusinessRecipients &data) {
|
||||
const auto &chats = data.allButExcluded
|
||||
? data.excluded
|
||||
: data.included;
|
||||
const auto flags = Flag()
|
||||
using Type = BusinessChatType;
|
||||
return Flag()
|
||||
| ((chats.types & Type::NewChats) ? Flag::f_new_chats : Flag())
|
||||
| ((chats.types & Type::ExistingChats)
|
||||
? Flag::f_existing_chats
|
||||
|
@ -69,12 +66,30 @@ MTPInputBusinessRecipients ToMTP(
|
|||
| ((chats.types & Type::NonContacts) ? Flag::f_non_contacts : Flag())
|
||||
| (chats.list.empty() ? Flag() : Flag::f_users)
|
||||
| (data.allButExcluded ? Flag::f_exclude_selected : Flag());
|
||||
const auto &users = data.allButExcluded
|
||||
? data.excluded
|
||||
: data.included;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPInputBusinessRecipients ForMessagesToMTP(const BusinessRecipients &data) {
|
||||
using Flag = MTPDinputBusinessRecipients::Flag;
|
||||
const auto &chats = data.allButExcluded ? data.excluded : data.included;
|
||||
return MTP_inputBusinessRecipients(
|
||||
MTP_flags(flags),
|
||||
MTP_vector_from_range(users.list
|
||||
MTP_flags(RecipientsFlags<Flag>(data)),
|
||||
MTP_vector_from_range(chats.list
|
||||
| ranges::views::transform(&UserData::inputUser)));
|
||||
}
|
||||
|
||||
MTPInputBusinessBotRecipients ForBotsToMTP(const BusinessRecipients &data) {
|
||||
using Flag = MTPDinputBusinessBotRecipients::Flag;
|
||||
const auto &chats = data.allButExcluded ? data.excluded : data.included;
|
||||
return MTP_inputBusinessBotRecipients(
|
||||
MTP_flags(RecipientsFlags<Flag>(data)
|
||||
| ((data.allButExcluded || data.excluded.empty())
|
||||
? Flag()
|
||||
: Flag::f_exclude_users)),
|
||||
MTP_vector_from_range(chats.list
|
||||
| ranges::views::transform(&UserData::inputUser)),
|
||||
MTP_vector_from_range(data.excluded.list
|
||||
| ranges::views::transform(&UserData::inputUser)));
|
||||
}
|
||||
|
||||
|
@ -103,7 +118,40 @@ BusinessRecipients FromMTP(
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] BusinessDetails FromMTP(
|
||||
BusinessRecipients FromMTP(
|
||||
not_null<Session*> owner,
|
||||
const MTPBusinessBotRecipients &recipients) {
|
||||
using Type = BusinessChatType;
|
||||
|
||||
const auto &data = recipients.data();
|
||||
auto result = BusinessRecipients{
|
||||
.allButExcluded = data.is_exclude_selected(),
|
||||
};
|
||||
auto &chats = result.allButExcluded
|
||||
? result.excluded
|
||||
: result.included;
|
||||
chats.types = Type()
|
||||
| (data.is_new_chats() ? Type::NewChats : Type())
|
||||
| (data.is_existing_chats() ? Type::ExistingChats : Type())
|
||||
| (data.is_contacts() ? Type::Contacts : Type())
|
||||
| (data.is_non_contacts() ? Type::NonContacts : Type());
|
||||
if (const auto users = data.vusers()) {
|
||||
for (const auto &userId : users->v) {
|
||||
chats.list.push_back(owner->user(UserId(userId.v)));
|
||||
}
|
||||
}
|
||||
if (!result.allButExcluded) {
|
||||
if (const auto excluded = data.vexclude_users()) {
|
||||
for (const auto &userId : excluded->v) {
|
||||
result.excluded.list.push_back(
|
||||
owner->user(UserId(userId.v)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BusinessDetails FromMTP(
|
||||
const tl::conditional<MTPBusinessWorkHours> &hours,
|
||||
const tl::conditional<MTPBusinessLocation> &location) {
|
||||
auto result = BusinessDetails();
|
||||
|
|
|
@ -49,11 +49,21 @@ struct BusinessRecipients {
|
|||
const BusinessRecipients &b) = default;
|
||||
};
|
||||
|
||||
[[nodiscard]] MTPInputBusinessRecipients ToMTP(
|
||||
enum class BusinessRecipientsType : uchar {
|
||||
Messages,
|
||||
Bots,
|
||||
};
|
||||
|
||||
[[nodiscard]] MTPInputBusinessRecipients ForMessagesToMTP(
|
||||
const BusinessRecipients &data);
|
||||
[[nodiscard]] MTPInputBusinessBotRecipients ForBotsToMTP(
|
||||
const BusinessRecipients &data);
|
||||
[[nodiscard]] BusinessRecipients FromMTP(
|
||||
not_null<Session*> owner,
|
||||
const MTPBusinessRecipients &recipients);
|
||||
[[nodiscard]] BusinessRecipients FromMTP(
|
||||
not_null<Session*> owner,
|
||||
const MTPBusinessBotRecipients &recipients);
|
||||
|
||||
struct Timezone {
|
||||
QString id;
|
||||
|
|
|
@ -49,14 +49,14 @@ namespace {
|
|||
MTP_flags(data.offlineOnly ? Flag::f_offline_only : Flag()),
|
||||
MTP_int(data.shortcutId),
|
||||
ToMTP(data.schedule),
|
||||
ToMTP(data.recipients));
|
||||
ForMessagesToMTP(data.recipients));
|
||||
}
|
||||
|
||||
[[nodiscard]] MTPInputBusinessGreetingMessage ToMTP(
|
||||
const GreetingSettings &data) {
|
||||
return MTP_inputBusinessGreetingMessage(
|
||||
MTP_int(data.shortcutId),
|
||||
ToMTP(data.recipients),
|
||||
ForMessagesToMTP(data.recipients),
|
||||
MTP_int(data.noActivityDays));
|
||||
}
|
||||
|
||||
|
|
|
@ -608,6 +608,23 @@ void PeerData::checkFolder(FolderId folderId) {
|
|||
}
|
||||
}
|
||||
|
||||
void PeerData::clearBusinessBot() {
|
||||
if (const auto details = _barDetails.get()) {
|
||||
if (details->requestChatDate) {
|
||||
details->businessBot = nullptr;
|
||||
details->businessBotManageUrl = QString();
|
||||
} else {
|
||||
_barDetails = nullptr;
|
||||
}
|
||||
}
|
||||
if (const auto settings = barSettings()) {
|
||||
setBarSettings(*settings
|
||||
& ~PeerBarSetting::BusinessBotPaused
|
||||
& ~PeerBarSetting::BusinessBotCanReply
|
||||
& ~PeerBarSetting::HasBusinessBot);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::setTranslationDisabled(bool disabled) {
|
||||
const auto flag = disabled
|
||||
? TranslationFlag::Disabled
|
||||
|
|
|
@ -373,6 +373,7 @@ public:
|
|||
[[nodiscard]] TimeId requestChatDate() const;
|
||||
[[nodiscard]] UserData *businessBot() const;
|
||||
[[nodiscard]] QString businessBotManageUrl() const;
|
||||
void clearBusinessBot();
|
||||
|
||||
enum class TranslationFlag : uchar {
|
||||
Unknown,
|
||||
|
|
|
@ -1578,6 +1578,9 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
|
|||
void HistoryWidget::orderWidgets() {
|
||||
_voiceRecordBar->raise();
|
||||
_send->raise();
|
||||
if (_businessBotStatus) {
|
||||
_businessBotStatus->bar().raise();
|
||||
}
|
||||
if (_contactStatus) {
|
||||
_contactStatus->bar().raise();
|
||||
}
|
||||
|
@ -2282,17 +2285,30 @@ void HistoryWidget::showHistory(
|
|||
_showAtMsgHighlightPartOffsetHint = highlightPartOffsetHint;
|
||||
_historyInited = false;
|
||||
_contactStatus = nullptr;
|
||||
_businessBotStatus = nullptr;
|
||||
|
||||
if (peerId) {
|
||||
using namespace HistoryView;
|
||||
_peer = session().data().peer(peerId);
|
||||
_contactStatus = std::make_unique<HistoryView::ContactStatus>(
|
||||
_contactStatus = std::make_unique<ContactStatus>(
|
||||
controller(),
|
||||
this,
|
||||
_peer,
|
||||
false);
|
||||
_contactStatus->bar().heightValue() | rpl::start_with_next([=] {
|
||||
_contactStatus->bar().heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateControlsGeometry();
|
||||
}, _contactStatus->bar().lifetime());
|
||||
if (const auto user = _peer->asUser()) {
|
||||
_businessBotStatus = std::make_unique<BusinessBotStatus>(
|
||||
controller(),
|
||||
this,
|
||||
user);
|
||||
_businessBotStatus->bar().heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateControlsGeometry();
|
||||
}, _businessBotStatus->bar().lifetime());
|
||||
}
|
||||
orderWidgets();
|
||||
controller()->tabbedSelector()->setCurrentPeer(_peer);
|
||||
}
|
||||
|
@ -2879,6 +2895,9 @@ void HistoryWidget::updateControlsVisibility() {
|
|||
if (_contactStatus) {
|
||||
_contactStatus->show();
|
||||
}
|
||||
if (_businessBotStatus) {
|
||||
_businessBotStatus->show();
|
||||
}
|
||||
if (isChoosingTheme()
|
||||
|| (!editingMessage()
|
||||
&& (isSearching()
|
||||
|
@ -4059,6 +4078,9 @@ void HistoryWidget::hideChildWidgets() {
|
|||
if (_contactStatus) {
|
||||
_contactStatus->hide();
|
||||
}
|
||||
if (_businessBotStatus) {
|
||||
_businessBotStatus->hide();
|
||||
}
|
||||
hideChildren();
|
||||
}
|
||||
|
||||
|
@ -5840,7 +5862,11 @@ void HistoryWidget::updateControlsGeometry() {
|
|||
if (_contactStatus) {
|
||||
_contactStatus->bar().move(0, contactStatusTop);
|
||||
}
|
||||
const auto scrollAreaTop = contactStatusTop + (_contactStatus ? _contactStatus->bar().height() : 0);
|
||||
const auto businessBotTop = contactStatusTop + (_contactStatus ? _contactStatus->bar().height() : 0);
|
||||
if (_businessBotStatus) {
|
||||
_businessBotStatus->bar().move(0, businessBotTop);
|
||||
}
|
||||
const auto scrollAreaTop = businessBotTop + (_businessBotStatus ? _businessBotStatus->bar().height() : 0);
|
||||
if (_scroll->y() != scrollAreaTop) {
|
||||
_scroll->moveToLeft(0, scrollAreaTop);
|
||||
_fieldAutocomplete->setBoundings(_scroll->geometry());
|
||||
|
@ -6076,6 +6102,9 @@ void HistoryWidget::updateHistoryGeometry(
|
|||
if (_contactStatus) {
|
||||
newScrollHeight -= _contactStatus->bar().height();
|
||||
}
|
||||
if (_businessBotStatus) {
|
||||
newScrollHeight -= _businessBotStatus->bar().height();
|
||||
}
|
||||
if (isChoosingTheme()) {
|
||||
newScrollHeight -= _chooseTheme->height();
|
||||
} else if (!editingMessage()
|
||||
|
@ -6457,6 +6486,7 @@ int HistoryWidget::computeMaxFieldHeight() const {
|
|||
const auto available = height()
|
||||
- _topBar->height()
|
||||
- (_contactStatus ? _contactStatus->bar().height() : 0)
|
||||
- (_businessBotStatus ? _businessBotStatus->bar().height() : 0)
|
||||
- (_pinnedBar ? _pinnedBar->height() : 0)
|
||||
- (_groupCallBar ? _groupCallBar->height() : 0)
|
||||
- (_requestsBar ? _requestsBar->height() : 0)
|
||||
|
|
|
@ -89,6 +89,7 @@ namespace HistoryView {
|
|||
class StickerToast;
|
||||
class TopBarWidget;
|
||||
class ContactStatus;
|
||||
class BusinessBotStatus;
|
||||
class Element;
|
||||
class PinnedTracker;
|
||||
class TranslateBar;
|
||||
|
@ -744,6 +745,7 @@ private:
|
|||
bool _isInlineBot = false;
|
||||
|
||||
std::unique_ptr<HistoryView::ContactStatus> _contactStatus;
|
||||
std::unique_ptr<HistoryView::BusinessBotStatus> _businessBotStatus;
|
||||
|
||||
const std::shared_ptr<Ui::SendButton> _send;
|
||||
object_ptr<Ui::FlatButton> _unblock;
|
||||
|
|
|
@ -8,9 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_contact_status.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/toast/toast.h"
|
||||
|
@ -18,7 +21,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/business/data_business_chatbots.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -839,6 +844,241 @@ void ContactStatus::hide() {
|
|||
_bar.hide();
|
||||
}
|
||||
|
||||
class BusinessBotStatus::Bar final : public Ui::RpWidget {
|
||||
public:
|
||||
Bar(QWidget *parent);
|
||||
|
||||
void showState(State state);
|
||||
|
||||
[[nodiscard]] rpl::producer<> pauseClicks() const;
|
||||
[[nodiscard]] rpl::producer<> resumeClicks() const;
|
||||
[[nodiscard]] rpl::producer<> removeClicks() const;
|
||||
[[nodiscard]] rpl::producer<> manageClicks() const;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
|
||||
void showMenu();
|
||||
|
||||
object_ptr<Ui::UserpicButton> _userpic = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _name;
|
||||
object_ptr<Ui::FlatLabel> _status;
|
||||
object_ptr<Ui::RoundButton> _togglePaused;
|
||||
object_ptr<Ui::IconButton> _settings;
|
||||
rpl::event_stream<> _removeClicks;
|
||||
rpl::event_stream<> _manageClicks;
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
bool _paused = false;
|
||||
|
||||
};
|
||||
|
||||
BusinessBotStatus::Bar::Bar(QWidget *parent)
|
||||
: RpWidget(parent)
|
||||
, _name(this, st::historyBusinessBotName)
|
||||
, _status(this, st::historyBusinessBotStatus)
|
||||
, _togglePaused(
|
||||
this,
|
||||
rpl::single(QString()),
|
||||
st::historyBusinessBotToggle)
|
||||
, _settings(this, st::historyBusinessBotSettings) {
|
||||
_name->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_status->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_settings->setClickedCallback([=] {
|
||||
showMenu();
|
||||
});
|
||||
}
|
||||
|
||||
void BusinessBotStatus::Bar::showState(State state) {
|
||||
Expects(state.bot != nullptr);
|
||||
|
||||
_userpic = object_ptr<Ui::UserpicButton>(
|
||||
this,
|
||||
state.bot,
|
||||
st::historyBusinessBotPhoto);
|
||||
_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_userpic->show();
|
||||
_name->setText(state.bot->name());
|
||||
_status->setText(!state.canReply
|
||||
? tr::lng_chatbot_status_views(tr::now)
|
||||
: state.paused
|
||||
? tr::lng_chatbot_status_paused(tr::now)
|
||||
: tr::lng_chatbot_status_can_reply(tr::now));
|
||||
_togglePaused->setText(state.paused
|
||||
? tr::lng_chatbot_button_resume()
|
||||
: tr::lng_chatbot_button_pause());
|
||||
_togglePaused->setVisible(state.canReply);
|
||||
_paused = state.paused;
|
||||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
rpl::producer<> BusinessBotStatus::Bar::pauseClicks() const {
|
||||
return _togglePaused->clicks() | rpl::filter([=] {
|
||||
return !_paused;
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
rpl::producer<> BusinessBotStatus::Bar::resumeClicks() const {
|
||||
return _togglePaused->clicks() | rpl::filter([=] {
|
||||
return _paused;
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
rpl::producer<> BusinessBotStatus::Bar::removeClicks() const {
|
||||
return _removeClicks.events();
|
||||
}
|
||||
|
||||
rpl::producer<> BusinessBotStatus::Bar::manageClicks() const {
|
||||
return _manageClicks.events();
|
||||
}
|
||||
|
||||
void BusinessBotStatus::Bar::showMenu() {
|
||||
if (_menu) {
|
||||
return;
|
||||
}
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuExpandedSeparator);
|
||||
_menu->setDestroyedCallback([
|
||||
weak = Ui::MakeWeak(this),
|
||||
weakButton = Ui::MakeWeak(_settings.data()),
|
||||
menu = _menu.get()] {
|
||||
if (weak && weak->_menu == menu) {
|
||||
if (weakButton) {
|
||||
weakButton->setForceRippled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
_settings->setForceRippled(true);
|
||||
|
||||
const auto addAction = Ui::Menu::CreateAddActionCallback(_menu);
|
||||
|
||||
addAction(tr::lng_chatbot_menu_manage(tr::now), crl::guard(this, [=] {
|
||||
_manageClicks.fire({});
|
||||
}), &st::menuIconSettings);
|
||||
addAction({
|
||||
.text = (_togglePaused->isHidden()
|
||||
? tr::lng_chatbot_menu_revoke(tr::now)
|
||||
: tr::lng_chatbot_menu_remove(tr::now)),
|
||||
.handler = crl::guard(this, [=] { _removeClicks.fire({}); }),
|
||||
.icon = &st::menuIconDisableAttention,
|
||||
.isAttention = true,
|
||||
});
|
||||
|
||||
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
||||
_menu->popup(mapToGlobal(QPoint(
|
||||
width() + st::topBarMenuPosition.x(),
|
||||
st::topBarMenuPosition.y())));
|
||||
}
|
||||
|
||||
void BusinessBotStatus::Bar::paintEvent(QPaintEvent *e) {
|
||||
QPainter p(this);
|
||||
p.fillRect(e->rect(), st::historyContactStatusButton.bgColor);
|
||||
}
|
||||
|
||||
int BusinessBotStatus::Bar::resizeGetHeight(int newWidth) {
|
||||
const auto &st = st::defaultPeerList.item;
|
||||
_settings->moveToRight(0, 0, newWidth);
|
||||
if (_userpic) {
|
||||
_userpic->moveToLeft(st.photoPosition.x(), st.photoPosition.y());
|
||||
}
|
||||
auto available = newWidth - _settings->width() - st.namePosition.x();
|
||||
if (!_togglePaused->isHidden()) {
|
||||
_togglePaused->moveToRight(_settings->width(), 0);
|
||||
available -= _togglePaused->width();
|
||||
}
|
||||
_name->resizeToWidth(available);
|
||||
_name->moveToLeft(st.namePosition.x(), st.namePosition.y());
|
||||
_status->resizeToWidth(available);
|
||||
_status->moveToLeft(st.statusPosition.x(), st.statusPosition.y());
|
||||
return st.height;
|
||||
}
|
||||
|
||||
BusinessBotStatus::BusinessBotStatus(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<PeerData*> peer)
|
||||
: _controller(window)
|
||||
, _inner(Ui::CreateChild<Bar>(parent.get()))
|
||||
, _bar(parent, object_ptr<Bar>::fromRaw(_inner)) {
|
||||
setupState(peer);
|
||||
setupHandlers(peer);
|
||||
}
|
||||
|
||||
auto BusinessBotStatus::PeerState(not_null<PeerData*> peer)
|
||||
-> rpl::producer<State> {
|
||||
using SettingsChange = PeerData::BarSettings::Change;
|
||||
return peer->barSettingsValue(
|
||||
) | rpl::map([=](SettingsChange settings) -> State {
|
||||
using Flag = PeerBarSetting;
|
||||
return {
|
||||
.bot = peer->businessBot(),
|
||||
.manageUrl = peer->businessBotManageUrl(),
|
||||
.canReply = ((settings.value & Flag::BusinessBotCanReply) != 0),
|
||||
.paused = ((settings.value & Flag::BusinessBotPaused) != 0),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
void BusinessBotStatus::setupState(not_null<PeerData*> peer) {
|
||||
if (!BarCurrentlyHidden(peer)) {
|
||||
peer->session().api().requestPeerSettings(peer);
|
||||
}
|
||||
PeerState(
|
||||
peer
|
||||
) | rpl::start_with_next([=](State state) {
|
||||
_state = state;
|
||||
if (!state.bot) {
|
||||
_bar.toggleContent(false);
|
||||
} else {
|
||||
_inner->showState(state);
|
||||
_bar.toggleContent(true);
|
||||
}
|
||||
}, _bar.lifetime());
|
||||
}
|
||||
|
||||
void BusinessBotStatus::setupHandlers(not_null<PeerData*> peer) {
|
||||
_inner->pauseClicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
peer->owner().chatbots().togglePaused(peer, true);
|
||||
}, _bar.lifetime());
|
||||
|
||||
_inner->resumeClicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
peer->owner().chatbots().togglePaused(peer, false);
|
||||
}, _bar.lifetime());
|
||||
|
||||
_inner->removeClicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
peer->owner().chatbots().removeFrom(peer);
|
||||
}, _bar.lifetime());
|
||||
|
||||
_inner->manageClicks(
|
||||
) | rpl::start_with_next([=] {
|
||||
UrlClickHandler::Open(
|
||||
_state.manageUrl,
|
||||
QVariant::fromValue(ClickHandlerContext{
|
||||
.sessionWindow = base::make_weak(_controller),
|
||||
.botStartAutoSubmit = true,
|
||||
}));
|
||||
}, _bar.lifetime());
|
||||
}
|
||||
|
||||
void BusinessBotStatus::show() {
|
||||
if (!_shown) {
|
||||
_shown = true;
|
||||
if (_state.bot) {
|
||||
_inner->showState(_state);
|
||||
_bar.toggleContent(true);
|
||||
}
|
||||
}
|
||||
_bar.show();
|
||||
}
|
||||
|
||||
void BusinessBotStatus::hide() {
|
||||
_bar.hide();
|
||||
}
|
||||
|
||||
TopicReopenBar::TopicReopenBar(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Data::ForumTopic*> topic)
|
||||
|
|
|
@ -124,6 +124,43 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class BusinessBotStatus final {
|
||||
public:
|
||||
BusinessBotStatus(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void show();
|
||||
void hide();
|
||||
|
||||
[[nodiscard]] SlidingBar &bar() {
|
||||
return _bar;
|
||||
}
|
||||
|
||||
private:
|
||||
class Bar;
|
||||
|
||||
struct State {
|
||||
UserData *bot = nullptr;
|
||||
QString manageUrl;
|
||||
bool canReply = false;
|
||||
bool paused = false;
|
||||
};
|
||||
|
||||
void setupState(not_null<PeerData*> peer);
|
||||
void setupHandlers(not_null<PeerData*> peer);
|
||||
|
||||
static rpl::producer<State> PeerState(not_null<PeerData*> peer);
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
State _state;
|
||||
QPointer<Bar> _inner;
|
||||
SlidingBar _bar;
|
||||
bool _shown = false;
|
||||
|
||||
};
|
||||
|
||||
class TopicReopenBar final {
|
||||
public:
|
||||
TopicReopenBar(
|
||||
|
|
|
@ -1704,7 +1704,7 @@ inputQuickReplyShortcutId#1190cf1 shortcut_id:int = InputQuickReplyShortcut;
|
|||
messages.quickReplies#c68d6695 quick_replies:Vector<QuickReply> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.QuickReplies;
|
||||
messages.quickRepliesNotModified#5f91eb5b = messages.QuickReplies;
|
||||
|
||||
connectedBot#e7e999e7 flags:# can_reply:flags.0?true bot_id:long recipients:BusinessRecipients = ConnectedBot;
|
||||
connectedBot#bd068601 flags:# can_reply:flags.0?true bot_id:long recipients:BusinessBotRecipients = ConnectedBot;
|
||||
|
||||
account.connectedBots#17d7f87b connected_bots:Vector<ConnectedBot> users:Vector<User> = account.ConnectedBots;
|
||||
|
||||
|
@ -1723,6 +1723,10 @@ inputCollectiblePhone#a2e214a4 phone:string = InputCollectible;
|
|||
|
||||
fragment.collectibleInfo#6ebdff91 purchase_date:int currency:string amount:long crypto_currency:string crypto_amount:long url:string = fragment.CollectibleInfo;
|
||||
|
||||
inputBusinessBotRecipients#c4e5921e flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector<InputUser> exclude_users:flags.6?Vector<InputUser> = InputBusinessBotRecipients;
|
||||
|
||||
businessBotRecipients#b88cf373 flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector<long> exclude_users:flags.6?Vector<long> = BusinessBotRecipients;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
|
@ -1853,7 +1857,7 @@ account.updateBusinessWorkHours#4b00e066 flags:# business_work_hours:flags.0?Bus
|
|||
account.updateBusinessLocation#9e6b131a flags:# geo_point:flags.1?InputGeoPoint address:flags.0?string = Bool;
|
||||
account.updateBusinessGreetingMessage#66cdafc4 flags:# message:flags.0?InputBusinessGreetingMessage = Bool;
|
||||
account.updateBusinessAwayMessage#a26a7fa5 flags:# message:flags.0?InputBusinessAwayMessage = Bool;
|
||||
account.updateConnectedBot#9c2d527d flags:# can_reply:flags.0?true deleted:flags.1?true bot:InputUser recipients:InputBusinessRecipients = Updates;
|
||||
account.updateConnectedBot#43d8521d flags:# can_reply:flags.0?true deleted:flags.1?true bot:InputUser recipients:InputBusinessBotRecipients = Updates;
|
||||
account.getConnectedBots#4ea4c80f = account.ConnectedBots;
|
||||
account.getBotBusinessConnection#76a86270 connection_id:string = Updates;
|
||||
account.updateBusinessIntro#a614d034 flags:# intro:flags.0?InputBusinessIntro = Bool;
|
||||
|
|
|
@ -336,6 +336,7 @@ void AwayMessage::setupContent(
|
|||
.controller = controller,
|
||||
.title = tr::lng_away_recipients(),
|
||||
.data = &_recipients,
|
||||
.type = Data::BusinessRecipientsType::Messages,
|
||||
});
|
||||
|
||||
Ui::AddSkip(inner, st::settingsChatbotsAccessSkip);
|
||||
|
|
|
@ -448,6 +448,7 @@ void Chatbots::setupContent(
|
|||
.controller = controller,
|
||||
.title = tr::lng_chatbots_access_title(),
|
||||
.data = &_recipients,
|
||||
.type = Data::BusinessRecipientsType::Bots,
|
||||
});
|
||||
|
||||
Ui::AddSkip(content, st::settingsChatbotsAccessSkip);
|
||||
|
|
|
@ -229,6 +229,7 @@ void Greeting::setupContent(
|
|||
.controller = controller,
|
||||
.title = tr::lng_greeting_recipients(),
|
||||
.data = &_recipients,
|
||||
.type = Data::BusinessRecipientsType::Messages,
|
||||
});
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
|
|
|
@ -69,7 +69,7 @@ void EditBusinessChats(
|
|||
(descriptor.include
|
||||
? tr::lng_filters_include_title()
|
||||
: tr::lng_filters_exclude_title()),
|
||||
options,
|
||||
(descriptor.usersOnly ? Flag() : options),
|
||||
TypesToFlags(descriptor.current.types) & options,
|
||||
base::flat_set<not_null<History*>>(begin(peers), end(peers)),
|
||||
100,
|
||||
|
@ -162,6 +162,8 @@ void AddBusinessRecipientsSelector(
|
|||
auto &lifetime = container->lifetime();
|
||||
const auto controller = descriptor.controller;
|
||||
const auto data = descriptor.data;
|
||||
const auto includeWithExcluded = (descriptor.type
|
||||
== Data::BusinessRecipientsType::Bots);
|
||||
const auto change = [=](Fn<void(Data::BusinessRecipients&)> modify) {
|
||||
auto now = data->current();
|
||||
modify(now);
|
||||
|
@ -191,11 +193,17 @@ void AddBusinessRecipientsSelector(
|
|||
Ui::AddSkip(container, st::settingsChatbotsAccessSkip);
|
||||
Ui::AddDivider(container);
|
||||
|
||||
const auto includeWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container))
|
||||
)->setDuration(0);
|
||||
const auto excludeWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container))
|
||||
)->setDuration(0);
|
||||
|
||||
const auto excludeInner = excludeWrap->entity();
|
||||
|
||||
Ui::AddSkip(excludeInner);
|
||||
|
@ -205,18 +213,34 @@ void AddBusinessRecipientsSelector(
|
|||
tr::lng_chatbots_exclude_button(),
|
||||
st::settingsChatbotsAdd,
|
||||
{ &st::settingsIconRemove, IconType::Round, &st::windowBgActive });
|
||||
excludeAdd->setClickedCallback([=] {
|
||||
const auto addExcluded = [=] {
|
||||
const auto save = [=](Data::BusinessChats value) {
|
||||
change([&](Data::BusinessRecipients &data) {
|
||||
if (includeWithExcluded) {
|
||||
if (!data.allButExcluded) {
|
||||
value.types = {};
|
||||
}
|
||||
for (const auto &user : value.list) {
|
||||
data.included.list.erase(
|
||||
ranges::remove(data.included.list, user),
|
||||
end(data.included.list));
|
||||
}
|
||||
}
|
||||
if (!value.empty()) {
|
||||
data.included = {};
|
||||
}
|
||||
data.excluded = std::move(value);
|
||||
});
|
||||
};
|
||||
EditBusinessChats(controller, {
|
||||
.current = data->current().excluded,
|
||||
.save = crl::guard(excludeAdd, save),
|
||||
.usersOnly = (includeWithExcluded
|
||||
&& !data->current().allButExcluded),
|
||||
.include = false,
|
||||
});
|
||||
});
|
||||
};
|
||||
excludeAdd->setClickedCallback(addExcluded);
|
||||
|
||||
const auto excluded = lifetime.make_state<
|
||||
rpl::variable<Data::BusinessChats>
|
||||
|
@ -227,24 +251,19 @@ void AddBusinessRecipientsSelector(
|
|||
}, lifetime);
|
||||
excluded->changes(
|
||||
) | rpl::start_with_next([=](Data::BusinessChats &&value) {
|
||||
auto now = data->current();
|
||||
now.excluded = std::move(value);
|
||||
*data = std::move(now);
|
||||
change([&](Data::BusinessRecipients &data) {
|
||||
data.excluded = std::move(value);
|
||||
});
|
||||
}, lifetime);
|
||||
|
||||
SetupBusinessChatsPreview(excludeInner, excluded);
|
||||
|
||||
excludeWrap->toggleOn(data->value(
|
||||
) | rpl::map([](const Data::BusinessRecipients &value) {
|
||||
return value.allButExcluded;
|
||||
) | rpl::map([=](const Data::BusinessRecipients &value) {
|
||||
return value.allButExcluded || includeWithExcluded;
|
||||
}));
|
||||
excludeWrap->finishAnimating();
|
||||
|
||||
const auto includeWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container))
|
||||
)->setDuration(0);
|
||||
const auto includeInner = includeWrap->entity();
|
||||
|
||||
Ui::AddSkip(includeInner);
|
||||
|
@ -254,18 +273,32 @@ void AddBusinessRecipientsSelector(
|
|||
tr::lng_chatbots_include_button(),
|
||||
st::settingsChatbotsAdd,
|
||||
{ &st::settingsIconAdd, IconType::Round, &st::windowBgActive });
|
||||
includeAdd->setClickedCallback([=] {
|
||||
const auto addIncluded = [=] {
|
||||
const auto save = [=](Data::BusinessChats value) {
|
||||
change([&](Data::BusinessRecipients &data) {
|
||||
if (includeWithExcluded) {
|
||||
for (const auto &user : value.list) {
|
||||
data.excluded.list.erase(
|
||||
ranges::remove(data.excluded.list, user),
|
||||
end(data.excluded.list));
|
||||
}
|
||||
}
|
||||
if (!value.empty()) {
|
||||
data.excluded.types = {};
|
||||
}
|
||||
data.included = std::move(value);
|
||||
});
|
||||
if (!data->current().included.empty()) {
|
||||
group->setValue(kSelectedOnly);
|
||||
}
|
||||
};
|
||||
EditBusinessChats(controller, {
|
||||
.current = data->current().included ,
|
||||
.current = data->current().included,
|
||||
.save = crl::guard(includeAdd, save),
|
||||
.include = true,
|
||||
});
|
||||
});
|
||||
};
|
||||
includeAdd->setClickedCallback(addIncluded);
|
||||
|
||||
const auto included = lifetime.make_state<
|
||||
rpl::variable<Data::BusinessChats>
|
||||
|
@ -298,16 +331,7 @@ void AddBusinessRecipientsSelector(
|
|||
group->setChangedCallback([=](int value) {
|
||||
if (value == kSelectedOnly && data->current().included.empty()) {
|
||||
group->setValue(kAllExcept);
|
||||
const auto save = [=](Data::BusinessChats value) {
|
||||
change([&](Data::BusinessRecipients &data) {
|
||||
data.included = std::move(value);
|
||||
});
|
||||
group->setValue(kSelectedOnly);
|
||||
};
|
||||
EditBusinessChats(controller, {
|
||||
.save = crl::guard(includeAdd, save),
|
||||
.include = true,
|
||||
});
|
||||
addIncluded();
|
||||
return;
|
||||
}
|
||||
change([&](Data::BusinessRecipients &data) {
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/required.h"
|
||||
#include "data/business/data_business_common.h"
|
||||
#include "settings/settings_common_session.h"
|
||||
|
||||
|
@ -52,6 +53,7 @@ private:
|
|||
struct BusinessChatsDescriptor {
|
||||
Data::BusinessChats current;
|
||||
Fn<void(const Data::BusinessChats&)> save;
|
||||
bool usersOnly = false;
|
||||
bool include = false;
|
||||
};
|
||||
void EditBusinessChats(
|
||||
|
@ -66,6 +68,7 @@ struct BusinessRecipientsSelectorDescriptor {
|
|||
not_null<Window::SessionController*> controller;
|
||||
rpl::producer<QString> title;
|
||||
not_null<rpl::variable<Data::BusinessRecipients>*> data;
|
||||
base::required<Data::BusinessRecipientsType> type;
|
||||
};
|
||||
void AddBusinessRecipientsSelector(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
|
Loading…
Add table
Reference in a new issue