diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 36b8f38c0..14ad8aa7e 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -602,7 +602,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_in_reply_to" = "In reply to"; "lng_bot_share_location_unavailable" = "Sorry, the location sharing is currently unavailable in Telegram Desktop."; -"lng_bot_share_phone" = "Share phone?"; +"lng_bot_share_phone" = "Share Phone Number?"; "lng_bot_share_phone_confirm" = "Share"; "lng_attach_failed" = "Failed"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 196059ae5..02426507f 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1087,6 +1087,12 @@ msgInReplyBarSelColor: #2fa9e2; msgBotKbDuration: 200; msgBotKbFont: semiboldFont; +msgBotKbOverOpacity: 0.1; +msgBotKbIconPadding: 2px; +msgBotKbUrlIcon: sprite(188px, 338px, 10px, 10px); +msgBotKbCallbackIcon: msgBotKbUrlIcon; +msgBotKbRequestPhoneIcon: msgBotKbUrlIcon; +msgBotKbRequestLocationIcon: msgBotKbUrlIcon; msgBotKbButton: botKeyboardButton { margin: 5px; padding: 10px; diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index 86e0fd4ec..d919c9f3a 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index 3c2c54606..0ceeb20dd 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 53e02bb5e..6e14cfeb3 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -166,6 +166,16 @@ void ConfirmBox::resizeEvent(QResizeEvent *e) { _cancel.moveToRight(st::boxButtonPadding.right() + _confirm.width() + st::boxButtonPadding.left(), _confirm.y()); } +SharePhoneConfirmBox::SharePhoneConfirmBox(PeerData *recipient) +: ConfirmBox(lang(lng_bot_share_phone), lang(lng_bot_share_phone_confirm)) +, _recipient(recipient) { + connect(this, SIGNAL(confirmed()), this, SLOT(onConfirm())); +} + +void SharePhoneConfirmBox::onConfirm() { + emit confirmed(_recipient); +} + ConfirmLinkBox::ConfirmLinkBox(const QString &url) : ConfirmBox(lang(lng_open_this_link) + qsl("\n\n") + url, lang(lng_open_link)) , _url(url) { connect(this, SIGNAL(confirmed()), this, SLOT(onOpenLink())); diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 0931b238f..097e6f436 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -83,6 +83,23 @@ public: } }; +class SharePhoneConfirmBox : public ConfirmBox { + Q_OBJECT + +public: + SharePhoneConfirmBox(PeerData *recipient); + +signals: + void confirmed(PeerData *recipient); + +private slots: + void onConfirm(); + +private: + PeerData *_recipient; + +}; + class ConfirmLinkBox : public ConfirmBox { Q_OBJECT diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 3200ad4fa..ebd738870 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -68,8 +68,8 @@ namespace App { } break; case HistoryMessageReplyMarkup::Button::RequestPhone: { - ConfirmBox *box = new ConfirmBox(lang(lng_bot_share_phone), lang(lng_bot_share_phone_confirm)); - box->connect(box, SIGNAL(confirmed()), App::main(), SLOT(onShareBotLocation())); + SharePhoneConfirmBox *box = new SharePhoneConfirmBox(peer); + box->connect(box, SIGNAL(confirmed(PeerData*)), App::main(), SLOT(onSharePhoneWithBot(PeerData*))); Ui::showLayer(box); } break; } diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 207e3a693..d7b737112 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -38,7 +38,6 @@ namespace App { void showSettings(); void activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button); - }; namespace Ui { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 78f72741b..a0d70ca21 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2706,16 +2706,34 @@ void HistoryBlock::removeItem(HistoryItem *item) { } void ReplyMarkupClickHandler::onClickImpl() const { + const HistoryItem *item = nullptr; + const HistoryMessageReplyMarkup::Button *button = nullptr; + if (getItemAndButton(&item, &button)) { + App::activateBotCommand(item->history()->peer, *button, _msgId.msg); + } +} + +// We need to make sure the item still exists, so we get it by id. +// After that we check if the reply markup is still there and that +// there are enough button rows and buttons in the row. +// 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. +bool ReplyMarkupClickHandler::getItemAndButton( + const HistoryItem **outItem, + const HistoryMessageReplyMarkup::Button **outButton) const { if (HistoryItem *item = App::histItemById(_msgId)) { if (auto *markup = item->Get()) { if (_row < markup->rows.size()) { const HistoryMessageReplyMarkup::ButtonRow &row(markup->rows.at(_row)); if (_col < row.size()) { - App::activateBotCommand(item->history()->peer, row.at(_col), _msgId.msg); + if (outItem) *outItem = item; + if (outButton) *outButton = &row.at(_col); + return true; } } } } + return false; } ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) @@ -2731,6 +2749,7 @@ ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) for (int j = 0; j != s; ++j) { Button &button(newRow[j]); QString str = row.at(j).text; + button.type = row.at(j).type; button.link.reset(new ReplyMarkupClickHandler(item->fullId(), i, j)); button.text.setText(_st->textFont(), textOneLine(str), _textPlainOptions); button.characters = str.isEmpty() ? 1 : str.size(); @@ -2756,12 +2775,14 @@ void ReplyKeyboard::resize(int width, int height) { float64 x = 0, coef = widthForText / widthOfText; for (Button &button : row) { float64 tw = widthForText / float64(s), w = 2 * _st->buttonPadding() + tw; - if (w < _st->buttonPadding()) w = _st->buttonPadding(); + int minw = _st->minButtonWidth(button.type); + if (w < minw) w = minw; button.rect = QRect(qRound(x), qRound(y), qRound(w), qRound(buttonHeight - _st->buttonSkip())); + if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width()); x += w + _st->buttonSkip(); - button.full = (tw >= button.text.maxWidth()); + button.link->setFullDisplayed(tw >= button.text.maxWidth()); } y += buttonHeight; } @@ -2789,6 +2810,23 @@ void ReplyKeyboard::setStyle(StylePtr &&st) { _st = std_::move(st); } +int ReplyKeyboard::naturalWidth() const { + int result = 0; + + auto *markup = _item->Get(); + for_const (const ButtonRow &row, _rows) { + int rowSize = row.size(); + int rowWidth = (rowSize - 1) * _st->buttonSkip() + rowSize * 2 * _st->buttonPadding(); + for_const(const Button &button, row) { + rowWidth += qMax(button.text.maxWidth(), 1); + } + if (rowWidth > result) { + result = rowWidth; + } + } + return result; +} + int ReplyKeyboard::naturalHeight() const { return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight(); } @@ -2804,9 +2842,10 @@ void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { if (rect.y() >= clip.y() + clip.height()) return; if (rect.y() + rect.height() < clip.y()) continue; - if (rtl()) rect.moveLeft(_width - rect.left() - rect.width()); + // just ignore the buttons that didn't layout well + if (rect.x() + rect.width() > _width) break; - _st->paintButton(p, rect, button.text, ClickHandler::showAsPressed(button.link), button.howMuchOver); + _st->paintButton(p, button); } } } @@ -2815,11 +2854,12 @@ void ReplyKeyboard::getState(ClickHandlerPtr &lnk, int x, int y) const { t_assert(_width > 0); lnk.clear(); - for_const(const ButtonRow &row, _rows) { - for_const(const Button &button, row) { + for_const (const ButtonRow &row, _rows) { + for_const (const Button &button, row) { QRect rect(button.rect); - if (rtl()) rect.moveLeft(_width - rect.left() - rect.width()); + // just ignore the buttons that didn't layout well + if (rect.x() + rect.width() > _width) break; if (rect.contains(x, y)) { lnk = button.link; @@ -2889,8 +2929,12 @@ void ReplyKeyboard::clearSelection() { _a_selected.stop(); } -void ReplyKeyboard::Style::paintButton(Painter &p, const QRect &rect, const Text &text, bool pressed, float64 howMuchOver) const { - paintButtonBg(p, rect, pressed, howMuchOver); +void ReplyKeyboard::Style::paintButton(Painter &p, const ReplyKeyboard::Button &button) const { + const QRect &rect = button.rect; + bool pressed = ClickHandler::showAsPressed(button.link); + + paintButtonBg(p, rect, pressed, button.howMuchOver); + paintButtonIcon(p, rect, button.type); int tx = rect.x(), tw = rect.width(); if (tw > st::botKbFont->elidew + _st->padding * 2) { @@ -2901,7 +2945,7 @@ void ReplyKeyboard::Style::paintButton(Painter &p, const QRect &rect, const Text tw = st::botKbFont->elidew; } int textTop = rect.y() + (pressed ? _st->downTextTop : _st->textTop); - text.drawElided(p, tx, textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); + button.text.drawElided(p, tx, textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top); } void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) { @@ -6457,12 +6501,40 @@ void HistoryMessage::KeyboardStyle::paintButtonBg(Painter &p, const QRect &rect, howMuchOver = 1.; } if (howMuchOver > 0) { - p.setOpacity(howMuchOver * 0.1); + float64 o = p.opacity(); + p.setOpacity(o * (howMuchOver * st::msgBotKbOverOpacity)); App::roundRect(p, rect, st::white, WhiteCorners); - p.setOpacity(1); + p.setOpacity(o); } } +void HistoryMessage::KeyboardStyle::paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const { + style::sprite sprite; + switch (type) { + case HistoryMessageReplyMarkup::Button::Url: sprite = st::msgBotKbUrlIcon; break; + case HistoryMessageReplyMarkup::Button::Callback: sprite = st::msgBotKbCallbackIcon; break; + case HistoryMessageReplyMarkup::Button::RequestPhone: sprite = st::msgBotKbRequestPhoneIcon; break; + case HistoryMessageReplyMarkup::Button::RequestLocation: sprite = st::msgBotKbRequestLocationIcon; break; + } + if (!sprite.isEmpty()) { + p.drawSprite(rect.x() + rect.width() - sprite.pxWidth() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, sprite); + } +} + +int HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const { + int result = 2 * buttonPadding(), iconWidth = 0; + switch (type) { + case HistoryMessageReplyMarkup::Button::Url: iconWidth = st::msgBotKbUrlIcon.pxWidth(); break; + case HistoryMessageReplyMarkup::Button::Callback: iconWidth = st::msgBotKbCallbackIcon.pxWidth(); break; + case HistoryMessageReplyMarkup::Button::RequestPhone: iconWidth = st::msgBotKbRequestPhoneIcon.pxWidth(); break; + case HistoryMessageReplyMarkup::Button::RequestLocation: iconWidth = st::msgBotKbRequestLocationIcon.pxWidth(); break; + } + if (iconWidth > 0) { + result = std::min(result, iconWidth + 2 * int(st::msgBotKbIconPadding)); + } + return result; +} + HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg) : HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { CreateConfig config; @@ -6748,7 +6820,7 @@ void HistoryMessage::initDimensions() { } if (auto *reply = Get()) { reply->updateName(); - if (!_media) { + if (!_text.isEmpty()) { int replyw = st::msgPadding.left() + reply->_maxReplyWidth - st::msgReplyPadding.left() - st::msgReplyPadding.right() + st::msgPadding.right(); if (reply->_replyToVia) { replyw += st::msgServiceFont->spacew + reply->_replyToVia->_maxWidth; @@ -6760,6 +6832,12 @@ void HistoryMessage::initDimensions() { if (!markup->inlineKeyboard) { markup->inlineKeyboard = new ReplyKeyboard(this, MakeUnique(st::msgBotKbButton)); } + + // if we have a text bubble we can resize it to fit the keyboard + // but if we have only media we don't do that + if (!_text.isEmpty()) { + _maxw = qMax(_maxw, markup->inlineKeyboard->naturalWidth()); + } } } @@ -7270,9 +7348,12 @@ int HistoryMessage::resizeGetHeight_(int width) { _height = _media->resize(width, this); } if (ReplyKeyboard *keyboard = inlineReplyKeyboard()) { + int32 l = 0, w = 0; + countPositionAndSize(l, w); + int h = st::msgBotKbButton.margin + keyboard->naturalHeight(); _height += h; - keyboard->resize(width, h - st::msgBotKbButton.margin); + keyboard->resize(w, h - st::msgBotKbButton.margin); } _height += marginTop() + marginBottom(); diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 458963445..70db17196 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1052,94 +1052,7 @@ struct HistoryMessageReply : public BaseComponent { }; Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags); -class ReplyMarkupClickHandler : public LeftButtonClickHandler { -public: - ReplyMarkupClickHandler(const FullMsgId &msgId, int row, int col) : _msgId(msgId), _row(row), _col(col) { - } - -protected: - void onClickImpl() const override; - -private: - FullMsgId _msgId; - int _row, _col; - -}; - -class ReplyKeyboard { -public: - class Style { - public: - Style(const style::botKeyboardButton &st) : _st(&st) { - } - - virtual void startPaint(Painter &p) const = 0; - virtual style::font textFont() const = 0; - - void paintButton(Painter &p, const QRect &rect, const Text &text, bool pressed, float64 howMuchOver) const; - - int buttonSkip() const { - return _st->margin; - } - int buttonPadding() const { - return _st->padding; - } - int buttonHeight() const { - return _st->height; - } - - virtual void repaint(const HistoryItem *item) const = 0; - - protected: - virtual void paintButtonBg(Painter &p, const QRect &rect, bool pressed, float64 howMuchOver) const = 0; - - private: - const style::botKeyboardButton *_st; - - }; - typedef UniquePointer