mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
Support url authorizations.
This commit is contained in:
parent
8660f976a9
commit
c92a798e1b
@ -1223,6 +1223,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_open_link" = "Open";
|
||||
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID to the web pages you open via this bot?";
|
||||
"lng_allow_bot" = "Allow";
|
||||
"lng_url_auth_open_confirm" = "Do you want to open {link}?";
|
||||
"lng_url_auth_login_option" = "Log in to {domain} as {user}";
|
||||
"lng_url_auth_allow_messages" = "Allow {bot} to send me messages";
|
||||
|
||||
"lng_bot_start" = "Start";
|
||||
"lng_bot_choose_group" = "Select a Group";
|
||||
|
@ -970,3 +970,7 @@ backgroundCheck: ServiceCheck {
|
||||
color: msgServiceFg;
|
||||
duration: 200;
|
||||
}
|
||||
|
||||
urlAuthCheckbox: Checkbox(defaultBoxCheckbox) {
|
||||
width: 240px;
|
||||
}
|
||||
|
204
Telegram/SourceFiles/boxes/url_auth_box.cpp
Normal file
204
Telegram/SourceFiles/boxes/url_auth_box.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
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 "boxes/url_auth_box.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "auth_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
void UrlAuthBox::Activate(
|
||||
not_null<const HistoryItem*> message,
|
||||
int row,
|
||||
int column) {
|
||||
const auto itemId = message->fullId();
|
||||
const auto button = HistoryMessageMarkupButton::Get(itemId, row, column);
|
||||
if (button->requestId || !IsServerMsgId(itemId.msg)) {
|
||||
return;
|
||||
}
|
||||
const auto session = &message->history()->session();
|
||||
const auto inputPeer = message->history()->peer->input;
|
||||
const auto buttonId = button->buttonId;
|
||||
const auto url = QString::fromUtf8(button->data);
|
||||
|
||||
button->requestId = session->api().request(MTPmessages_RequestUrlAuth(
|
||||
inputPeer,
|
||||
MTP_int(itemId.msg),
|
||||
MTP_int(buttonId)
|
||||
)).done([=](const MTPUrlAuthResult &result) {
|
||||
const auto button = HistoryMessageMarkupButton::Get(
|
||||
itemId,
|
||||
row,
|
||||
column);
|
||||
if (!button) return;
|
||||
|
||||
button->requestId = 0;
|
||||
result.match([&](const MTPDurlAuthResultAccepted &data) {
|
||||
UrlClickHandler::Open(qs(data.vurl));
|
||||
}, [&](const MTPDurlAuthResultDefault &data) {
|
||||
HiddenUrlClickHandler::Open(url);
|
||||
}, [&](const MTPDurlAuthResultRequest &data) {
|
||||
Request(data, session->data().message(itemId), row, column);
|
||||
});
|
||||
}).fail([=](const RPCError &error) {
|
||||
const auto button = HistoryMessageMarkupButton::Get(
|
||||
itemId,
|
||||
row,
|
||||
column);
|
||||
if (!button) return;
|
||||
|
||||
button->requestId = 0;
|
||||
HiddenUrlClickHandler::Open(url);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void UrlAuthBox::Request(
|
||||
const MTPDurlAuthResultRequest &request,
|
||||
not_null<const HistoryItem*> message,
|
||||
int row,
|
||||
int column) {
|
||||
const auto itemId = message->fullId();
|
||||
const auto button = HistoryMessageMarkupButton::Get(itemId, row, column);
|
||||
if (button->requestId || !IsServerMsgId(itemId.msg)) {
|
||||
return;
|
||||
}
|
||||
const auto session = &message->history()->session();
|
||||
const auto inputPeer = message->history()->peer->input;
|
||||
const auto buttonId = button->buttonId;
|
||||
const auto url = QString::fromUtf8(button->data);
|
||||
|
||||
const auto bot = request.is_request_write_access()
|
||||
? session->data().processUser(request.vbot).get()
|
||||
: nullptr;
|
||||
const auto box = std::make_shared<QPointer<BoxContent>>();
|
||||
const auto finishWithUrl = [=](const QString &url) {
|
||||
if (*box) {
|
||||
(*box)->closeBox();
|
||||
}
|
||||
UrlClickHandler::Open(url);
|
||||
};
|
||||
const auto callback = [=](Result result) {
|
||||
if (result == Result::None) {
|
||||
finishWithUrl(url);
|
||||
} else if (const auto msg = session->data().message(itemId)) {
|
||||
const auto allowWrite = (result == Result::AuthAndAllowWrite);
|
||||
using Flag = MTPmessages_AcceptUrlAuth::Flag;
|
||||
session->api().request(MTPmessages_AcceptUrlAuth(
|
||||
MTP_flags(allowWrite ? Flag::f_write_allowed : Flag(0)),
|
||||
inputPeer,
|
||||
MTP_int(itemId.msg),
|
||||
MTP_int(buttonId)
|
||||
)).done([=](const MTPUrlAuthResult &result) {
|
||||
const auto to = result.match(
|
||||
[&](const MTPDurlAuthResultAccepted &data) {
|
||||
return qs(data.vurl);
|
||||
}, [&](const MTPDurlAuthResultDefault &data) {
|
||||
return url;
|
||||
}, [&](const MTPDurlAuthResultRequest &data) {
|
||||
LOG(("API Error: "
|
||||
"got urlAuthResultRequest after acceptUrlAuth."));
|
||||
return url;
|
||||
});
|
||||
finishWithUrl(to);
|
||||
}).fail([=](const RPCError &error) {
|
||||
finishWithUrl(url);
|
||||
}).send();
|
||||
}
|
||||
};
|
||||
*box = Ui::show(
|
||||
Box<UrlAuthBox>(url, qs(request.vdomain), bot, callback),
|
||||
LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
UrlAuthBox::UrlAuthBox(
|
||||
QWidget*,
|
||||
const QString &url,
|
||||
const QString &domain,
|
||||
UserData *bot,
|
||||
Fn<void(Result)> callback)
|
||||
: _content(setupContent(url, domain, bot, std::move(callback))) {
|
||||
}
|
||||
|
||||
void UrlAuthBox::prepare() {
|
||||
setDimensionsToContent(st::boxWidth, _content);
|
||||
addButton(langFactory(lng_open_link), [=] { _callback(); });
|
||||
addButton(langFactory(lng_cancel), [=] { closeBox(); });
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> UrlAuthBox::setupContent(
|
||||
const QString &url,
|
||||
const QString &domain,
|
||||
UserData *bot,
|
||||
Fn<void(Result)> callback) {
|
||||
const auto result = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
result->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
result,
|
||||
lng_url_auth_open_confirm(lt_link, url),
|
||||
Ui::FlatLabel::InitType::Simple,
|
||||
st::boxLabel),
|
||||
st::boxPadding);
|
||||
const auto addCheckbox = [&](const QString &text) {
|
||||
const auto checkbox = result->add(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
result,
|
||||
QString(),
|
||||
false,
|
||||
st::urlAuthCheckbox),
|
||||
style::margins(
|
||||
st::boxPadding.left(),
|
||||
st::boxPadding.bottom(),
|
||||
st::boxPadding.right(),
|
||||
st::boxPadding.bottom()));
|
||||
checkbox->setAllowMultiline(true);
|
||||
checkbox->setText(text, true);
|
||||
return checkbox;
|
||||
};
|
||||
const auto auth = addCheckbox(
|
||||
lng_url_auth_login_option(
|
||||
lt_domain,
|
||||
textcmdStartSemibold() + domain + textcmdStopSemibold(),
|
||||
lt_user,
|
||||
(textcmdStartSemibold()
|
||||
+ App::peerName(Auth().user())
|
||||
+ textcmdStopSemibold())));
|
||||
const auto allow = bot
|
||||
? addCheckbox(lng_url_auth_allow_messages(
|
||||
lt_bot,
|
||||
textcmdStartSemibold() + bot->firstName + textcmdStopSemibold()))
|
||||
: nullptr;
|
||||
if (allow) {
|
||||
rpl::single(
|
||||
auth->checked()
|
||||
) | rpl::then(
|
||||
auth->checkedChanges()
|
||||
) | rpl::start_with_next([=](bool checked) {
|
||||
allow->setDisabled(!checked);
|
||||
}, auth->lifetime());
|
||||
}
|
||||
_callback = [=, callback = std::move(callback)]() {
|
||||
const auto authed = auth->checked();
|
||||
const auto allowed = (authed && allow && allow->checked());
|
||||
const auto onstack = callback;
|
||||
onstack(allowed
|
||||
? Result::AuthAndAllowWrite
|
||||
: authed
|
||||
? Result::Auth
|
||||
: Result::None);
|
||||
};
|
||||
return result;
|
||||
}
|
56
Telegram/SourceFiles/boxes/url_auth_box.h
Normal file
56
Telegram/SourceFiles/boxes/url_auth_box.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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 "boxes/abstract_box.h"
|
||||
|
||||
class HistoryItem;
|
||||
struct HistoryMessageMarkupButton;
|
||||
|
||||
class UrlAuthBox : public BoxContent {
|
||||
public:
|
||||
static void Activate(
|
||||
not_null<const HistoryItem*> message,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
static void Request(
|
||||
const MTPDurlAuthResultRequest &request,
|
||||
not_null<const HistoryItem*> message,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
enum class Result {
|
||||
None,
|
||||
Auth,
|
||||
AuthAndAllowWrite,
|
||||
};
|
||||
|
||||
public:
|
||||
UrlAuthBox(
|
||||
QWidget*,
|
||||
const QString &url,
|
||||
const QString &domain,
|
||||
UserData *bot,
|
||||
Fn<void(Result)> callback);
|
||||
|
||||
private:
|
||||
not_null<Ui::RpWidget*> setupContent(
|
||||
const QString &url,
|
||||
const QString &domain,
|
||||
UserData *bot,
|
||||
Fn<void(Result)> callback);
|
||||
|
||||
Fn<void()> _callback;
|
||||
not_null<Ui::RpWidget*> _content;
|
||||
|
||||
};
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "auth_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "boxes/url_auth_box.h"
|
||||
#include "window/layer_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/observer.h"
|
||||
@ -62,15 +63,7 @@ void activateBotCommand(
|
||||
not_null<const HistoryItem*> msg,
|
||||
int row,
|
||||
int column) {
|
||||
const HistoryMessageMarkupButton *button = nullptr;
|
||||
if (auto markup = msg->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (row < markup->rows.size()) {
|
||||
auto &buttonRow = markup->rows[row];
|
||||
if (column < buttonRow.size()) {
|
||||
button = &buttonRow[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto button = HistoryMessageMarkupButton::Get(msg->fullId(), row, column);
|
||||
if (!button) return;
|
||||
|
||||
using ButtonType = HistoryMessageMarkupButton::Type;
|
||||
@ -147,6 +140,10 @@ void activateBotCommand(
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::Auth:
|
||||
UrlAuthBox::Activate(msg, row, column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,17 +365,7 @@ QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
|
||||
// Note: it is possible that we will point to the different button
|
||||
// than the one was used when constructing the handler, but not a big deal.
|
||||
const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const {
|
||||
if (const auto item = Auth().data().message(_itemId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (_row < markup->rows.size()) {
|
||||
const auto &row = markup->rows[_row];
|
||||
if (_column < row.size()) {
|
||||
return &row[_column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return HistoryMessageMarkupButton::Get(_itemId, _row, _column);
|
||||
}
|
||||
|
||||
void ReplyMarkupClickHandler::onClickImpl() const {
|
||||
@ -730,66 +720,93 @@ void ReplyKeyboard::Style::paintButton(
|
||||
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) {
|
||||
if (v.isEmpty()) {
|
||||
rows.clear();
|
||||
HistoryMessageMarkupButton::HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
const QByteArray &data,
|
||||
int32 buttonId)
|
||||
: type(type)
|
||||
, text(text)
|
||||
, data(data)
|
||||
, buttonId(buttonId) {
|
||||
}
|
||||
|
||||
HistoryMessageMarkupButton *HistoryMessageMarkupButton::Get(
|
||||
FullMsgId itemId,
|
||||
int row,
|
||||
int column) {
|
||||
if (const auto item = Auth().data().message(itemId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (row < markup->rows.size()) {
|
||||
auto &buttons = markup->rows[row];
|
||||
if (column < buttons.size()) {
|
||||
return &buttons[column];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::createFromButtonRows(
|
||||
const QVector<MTPKeyboardButtonRow> &list) {
|
||||
rows.clear();
|
||||
if (list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
rows.reserve(v.size());
|
||||
for_const (auto &row, v) {
|
||||
switch (row.type()) {
|
||||
case mtpc_keyboardButtonRow: {
|
||||
auto &r = row.c_keyboardButtonRow();
|
||||
auto &b = r.vbuttons.v;
|
||||
if (!b.isEmpty()) {
|
||||
auto buttonRow = std::vector<Button>();
|
||||
buttonRow.reserve(b.size());
|
||||
for_const (auto &button, b) {
|
||||
switch (button.type()) {
|
||||
case mtpc_keyboardButton: {
|
||||
buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonCallback: {
|
||||
auto &buttonData = button.c_keyboardButtonCallback();
|
||||
buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonRequestGeoLocation: {
|
||||
buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonRequestPhone: {
|
||||
buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonUrl: {
|
||||
auto &buttonData = button.c_keyboardButtonUrl();
|
||||
buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonSwitchInline: {
|
||||
auto &buttonData = button.c_keyboardButtonSwitchInline();
|
||||
auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline;
|
||||
buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 });
|
||||
if (buttonType == Button::Type::SwitchInline) {
|
||||
// Optimization flag.
|
||||
// Fast check on all new messages if there is a switch button to auto-click it.
|
||||
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
|
||||
}
|
||||
} break;
|
||||
case mtpc_keyboardButtonGame: {
|
||||
auto &buttonData = button.c_keyboardButtonGame();
|
||||
buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonBuy: {
|
||||
auto &buttonData = button.c_keyboardButtonBuy();
|
||||
buttonRow.push_back({ Button::Type::Buy, qs(buttonData.vtext), QByteArray(), 0 });
|
||||
rows.reserve(list.size());
|
||||
for (const auto &row : list) {
|
||||
row.match([&](const MTPDkeyboardButtonRow &data) {
|
||||
auto row = std::vector<Button>();
|
||||
row.reserve(data.vbuttons.v.size());
|
||||
for (const auto &button : data.vbuttons.v) {
|
||||
using Type = Button::Type;
|
||||
button.match([&](const MTPDkeyboardButton &data) {
|
||||
row.emplace_back(Type::Default, qs(data.vtext));
|
||||
}, [&](const MTPDkeyboardButtonCallback &data) {
|
||||
row.emplace_back(
|
||||
Type::Callback,
|
||||
qs(data.vtext),
|
||||
qba(data.vdata));
|
||||
}, [&](const MTPDkeyboardButtonRequestGeoLocation &data) {
|
||||
row.emplace_back(Type::RequestLocation, qs(data.vtext));
|
||||
}, [&](const MTPDkeyboardButtonRequestPhone &data) {
|
||||
row.emplace_back(Type::RequestPhone, qs(data.vtext));
|
||||
}, [&](const MTPDkeyboardButtonUrl &data) {
|
||||
row.emplace_back(
|
||||
Type::Url,
|
||||
qs(data.vtext),
|
||||
qba(data.vurl));
|
||||
}, [&](const MTPDkeyboardButtonSwitchInline &data) {
|
||||
const auto type = data.is_same_peer()
|
||||
? Type::SwitchInlineSame
|
||||
: Type::SwitchInline;
|
||||
row.emplace_back(type, qs(data.vtext), qba(data.vquery));
|
||||
if (type == Type::SwitchInline) {
|
||||
// Optimization flag.
|
||||
// Fast check on all new messages if there is a switch button to auto-click it.
|
||||
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!buttonRow.empty()) {
|
||||
rows.push_back(std::move(buttonRow));
|
||||
}
|
||||
}, [&](const MTPDkeyboardButtonGame &data) {
|
||||
row.emplace_back(Type::Game, qs(data.vtext));
|
||||
}, [&](const MTPDkeyboardButtonBuy &data) {
|
||||
row.emplace_back(Type::Buy, qs(data.vtext));
|
||||
}, [&](const MTPDkeyboardButtonUrlAuth &data) {
|
||||
row.emplace_back(
|
||||
Type::Auth,
|
||||
qs(data.vtext),
|
||||
qba(data.vurl),
|
||||
data.vbutton_id.v);
|
||||
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
|
||||
LOG(("API Error: inputKeyboardButtonUrlAuth received."));
|
||||
// Should not get those for the users.
|
||||
});
|
||||
}
|
||||
} break;
|
||||
}
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -831,14 +848,19 @@ void HistoryMessageReplyMarkup::create(
|
||||
inlineKeyboard = nullptr;
|
||||
|
||||
rows.clear();
|
||||
for (const auto &row : markup.rows) {
|
||||
auto buttonRow = std::vector<Button>();
|
||||
buttonRow.reserve(row.size());
|
||||
for (const auto &button : row) {
|
||||
buttonRow.push_back({ button.type, button.text, button.data, 0 });
|
||||
rows.reserve(markup.rows.size());
|
||||
for (const auto &existing : markup.rows) {
|
||||
auto row = std::vector<Button>();
|
||||
row.reserve(existing.size());
|
||||
for (const auto &button : existing) {
|
||||
row.emplace_back(
|
||||
button.type,
|
||||
button.text,
|
||||
button.data,
|
||||
button.buttonId);
|
||||
}
|
||||
if (!buttonRow.empty()) {
|
||||
rows.push_back(std::move(buttonRow));
|
||||
if (!row.empty()) {
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,11 +164,25 @@ struct HistoryMessageMarkupButton {
|
||||
SwitchInlineSame,
|
||||
Game,
|
||||
Buy,
|
||||
Auth,
|
||||
};
|
||||
|
||||
HistoryMessageMarkupButton(
|
||||
Type type,
|
||||
const QString &text,
|
||||
const QByteArray &data = QByteArray(),
|
||||
int32 buttonId = 0);
|
||||
|
||||
static HistoryMessageMarkupButton *Get(
|
||||
FullMsgId itemId,
|
||||
int row,
|
||||
int column);
|
||||
|
||||
Type type;
|
||||
QString text;
|
||||
QByteArray data;
|
||||
mutable mtpRequestId requestId;
|
||||
int32 buttonId = 0;
|
||||
mutable mtpRequestId requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
|
@ -3327,28 +3327,22 @@ void HistoryWidget::botCallbackDone(
|
||||
BotCallbackInfo info,
|
||||
const MTPmessages_BotCallbackAnswer &answer,
|
||||
mtpRequestId req) {
|
||||
auto item = Auth().data().message(info.msgId);
|
||||
if (item) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (info.row < markup->rows.size()
|
||||
&& info.col < markup->rows[info.row].size()) {
|
||||
if (markup->rows[info.row][info.col].requestId == req) {
|
||||
markup->rows[info.row][info.col].requestId = 0;
|
||||
Auth().data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
const auto item = Auth().data().message(info.msgId);
|
||||
if (const auto button = HistoryMessageMarkupButton::Get(info.msgId, info.row, info.col)) {
|
||||
if (button->requestId == req) {
|
||||
button->requestId = 0;
|
||||
Auth().data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
if (answer.type() == mtpc_messages_botCallbackAnswer) {
|
||||
auto &answerData = answer.c_messages_botCallbackAnswer();
|
||||
if (answerData.has_message()) {
|
||||
if (answerData.is_alert()) {
|
||||
Ui::show(Box<InformBox>(qs(answerData.vmessage)));
|
||||
answer.match([&](const MTPDmessages_botCallbackAnswer &data) {
|
||||
if (data.has_message()) {
|
||||
if (data.is_alert()) {
|
||||
Ui::show(Box<InformBox>(qs(data.vmessage)));
|
||||
} else {
|
||||
Ui::Toast::Show(qs(answerData.vmessage));
|
||||
Ui::Toast::Show(qs(data.vmessage));
|
||||
}
|
||||
} else if (answerData.has_url()) {
|
||||
auto url = qs(answerData.vurl);
|
||||
} else if (data.has_url()) {
|
||||
auto url = qs(data.vurl);
|
||||
if (info.game) {
|
||||
url = AppendShareGameScoreUrl(url, info.msgId);
|
||||
BotGameUrlClickHandler(info.bot, url).onClick({});
|
||||
@ -3359,7 +3353,7 @@ void HistoryWidget::botCallbackDone(
|
||||
UrlClickHandler(url).onClick({});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool HistoryWidget::botCallbackFail(
|
||||
@ -3367,15 +3361,10 @@ bool HistoryWidget::botCallbackFail(
|
||||
const RPCError &error,
|
||||
mtpRequestId req) {
|
||||
// show error?
|
||||
if (const auto item = Auth().data().message(info.msgId)) {
|
||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (info.row < markup->rows.size()
|
||||
&& info.col < markup->rows[info.row].size()) {
|
||||
if (markup->rows[info.row][info.col].requestId == req) {
|
||||
markup->rows[info.row][info.col].requestId = 0;
|
||||
Auth().data().requestItemRepaint(item);
|
||||
}
|
||||
}
|
||||
if (const auto button = HistoryMessageMarkupButton::Get(info.msgId, info.row, info.col)) {
|
||||
if (button->requestId == req) {
|
||||
button->requestId = 0;
|
||||
Auth().data().requestItemRepaint(Auth().data().message(info.msgId));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -84,16 +84,16 @@ void KeyboardStyle::paintButtonIcon(
|
||||
const QRect &rect,
|
||||
int outerWidth,
|
||||
HistoryMessageMarkupButton::Type type) const {
|
||||
using Button = HistoryMessageMarkupButton;
|
||||
auto getIcon = [](Button::Type type) -> const style::icon* {
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
const auto getIcon = [](Type type) -> const style::icon* {
|
||||
switch (type) {
|
||||
case Button::Type::Url: return &st::msgBotKbUrlIcon;
|
||||
case Button::Type::SwitchInlineSame:
|
||||
case Button::Type::SwitchInline: return &st::msgBotKbSwitchPmIcon;
|
||||
case Type::Url: return &st::msgBotKbUrlIcon;
|
||||
case Type::SwitchInlineSame:
|
||||
case Type::SwitchInline: return &st::msgBotKbSwitchPmIcon;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
if (auto icon = getIcon(type)) {
|
||||
if (const auto icon = getIcon(type)) {
|
||||
icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
|
||||
}
|
||||
}
|
||||
@ -105,14 +105,15 @@ void KeyboardStyle::paintButtonLoading(Painter &p, const QRect &rect) const {
|
||||
|
||||
int KeyboardStyle::minButtonWidth(
|
||||
HistoryMessageMarkupButton::Type type) const {
|
||||
using Button = HistoryMessageMarkupButton;
|
||||
using Type = HistoryMessageMarkupButton::Type;
|
||||
int result = 2 * buttonPadding(), iconWidth = 0;
|
||||
switch (type) {
|
||||
case Button::Type::Url: iconWidth = st::msgBotKbUrlIcon.width(); break;
|
||||
case Button::Type::SwitchInlineSame:
|
||||
case Button::Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
|
||||
case Button::Type::Callback:
|
||||
case Button::Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
|
||||
case Type::Url: iconWidth = st::msgBotKbUrlIcon.width(); break;
|
||||
case Type::SwitchInlineSame:
|
||||
case Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
|
||||
case Type::Callback:
|
||||
case Type::Game:
|
||||
case Type::Auth: iconWidth = st::historySendingInvertedIcon.width(); break;
|
||||
}
|
||||
if (iconWidth > 0) {
|
||||
result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
|
||||
|
@ -20,6 +20,13 @@ TextParseOptions _checkboxOptions = {
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions _checkboxRichOptions = {
|
||||
TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
AbstractCheckView::AbstractCheckView(int duration, bool checked, Fn<void()> updateCallback)
|
||||
@ -401,7 +408,7 @@ Checkbox::Checkbox(
|
||||
: RippleButton(parent, st.ripple)
|
||||
, _st(st)
|
||||
, _check(std::move(check))
|
||||
, _text(_st.style, text, _checkboxOptions) {
|
||||
, _text(_st.style, text, _checkboxOptions, (_st.width > 0) ? _st.width : QFIXED_MAX) {
|
||||
_check->setUpdateCallback([=] { updateCheck(); });
|
||||
resizeToText();
|
||||
setCursor(style::cur_pointer);
|
||||
@ -423,8 +430,8 @@ QRect Checkbox::checkRect() const {
|
||||
}, size);
|
||||
}
|
||||
|
||||
void Checkbox::setText(const QString &text) {
|
||||
_text.setText(_st.style, text, _checkboxOptions);
|
||||
void Checkbox::setText(const QString &text, bool rich) {
|
||||
_text.setText(_st.style, text, rich ? _checkboxRichOptions : _checkboxOptions);
|
||||
resizeToText();
|
||||
update();
|
||||
}
|
||||
@ -436,6 +443,11 @@ void Checkbox::setCheckAlignment(style::align alignment) {
|
||||
}
|
||||
}
|
||||
|
||||
void Checkbox::setAllowMultiline(bool allow) {
|
||||
_allowMultiline = allow;
|
||||
update();
|
||||
}
|
||||
|
||||
bool Checkbox::checked() const {
|
||||
return _check->checked();
|
||||
}
|
||||
@ -519,19 +531,37 @@ void Checkbox::paintEvent(QPaintEvent *e) {
|
||||
+ _st.textPosition.x();
|
||||
auto textTop = _st.margin.top() + _st.textPosition.y();
|
||||
if (_checkAlignment & Qt::AlignLeft) {
|
||||
_text.drawLeftElided(
|
||||
p,
|
||||
textSkip,
|
||||
textTop,
|
||||
availableTextWidth,
|
||||
width());
|
||||
if (_allowMultiline) {
|
||||
_text.drawLeft(
|
||||
p,
|
||||
textSkip,
|
||||
textTop,
|
||||
availableTextWidth,
|
||||
width());
|
||||
} else {
|
||||
_text.drawLeftElided(
|
||||
p,
|
||||
textSkip,
|
||||
textTop,
|
||||
availableTextWidth,
|
||||
width());
|
||||
}
|
||||
} else if (_checkAlignment & Qt::AlignRight) {
|
||||
_text.drawRightElided(
|
||||
p,
|
||||
textSkip,
|
||||
textTop,
|
||||
availableTextWidth,
|
||||
width());
|
||||
if (_allowMultiline) {
|
||||
_text.drawRight(
|
||||
p,
|
||||
textSkip,
|
||||
textTop,
|
||||
availableTextWidth,
|
||||
width());
|
||||
} else {
|
||||
_text.drawRightElided(
|
||||
p,
|
||||
textSkip,
|
||||
textTop,
|
||||
availableTextWidth,
|
||||
width());
|
||||
}
|
||||
} else {
|
||||
_text.drawLeft(
|
||||
p,
|
||||
@ -582,13 +612,18 @@ void Checkbox::handlePress() {
|
||||
|
||||
int Checkbox::resizeGetHeight(int newWidth) {
|
||||
const auto result = _check->getSize().height();
|
||||
if (!(_checkAlignment & Qt::AlignHCenter)) {
|
||||
const auto centered = ((_checkAlignment & Qt::AlignHCenter) != 0);
|
||||
if (!centered && !_allowMultiline) {
|
||||
return result;
|
||||
}
|
||||
const auto textBottom = _st.margin.top()
|
||||
+ _st.textPosition.y()
|
||||
+ _text.countHeight(
|
||||
newWidth - _st.margin.left() - _st.margin.right());
|
||||
const auto leftSkip = _st.checkPosition.x()
|
||||
+ checkRect().width()
|
||||
+ _st.textPosition.x();
|
||||
const auto availableTextWidth = centered
|
||||
? (newWidth - _st.margin.left() - _st.margin.right())
|
||||
: qMax(width() - leftSkip, 1);
|
||||
const auto textBottom = _st.textPosition.y()
|
||||
+ _text.countHeight(availableTextWidth);
|
||||
return std::max(result, textBottom);
|
||||
}
|
||||
|
||||
|
@ -150,8 +150,9 @@ public:
|
||||
const style::Checkbox &st,
|
||||
std::unique_ptr<AbstractCheckView> check);
|
||||
|
||||
void setText(const QString &text);
|
||||
void setText(const QString &text, bool rich = false);
|
||||
void setCheckAlignment(style::align alignment);
|
||||
void setAllowMultiline(bool allow);
|
||||
|
||||
bool checked() const;
|
||||
rpl::producer<bool> checkedChanges() const;
|
||||
@ -198,6 +199,7 @@ private:
|
||||
|
||||
Text _text;
|
||||
style::align _checkAlignment = style::al_left;
|
||||
bool _allowMultiline = false;
|
||||
|
||||
};
|
||||
|
||||
|
@ -78,6 +78,8 @@
|
||||
<(src_loc)/boxes/sticker_set_box.h
|
||||
<(src_loc)/boxes/stickers_box.cpp
|
||||
<(src_loc)/boxes/stickers_box.h
|
||||
<(src_loc)/boxes/url_auth_box.cpp
|
||||
<(src_loc)/boxes/url_auth_box.h
|
||||
<(src_loc)/boxes/username_box.cpp
|
||||
<(src_loc)/boxes/username_box.h
|
||||
<(src_loc)/calls/calls_box_controller.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user