From e8ed30727844496be31a22c8359c6f5a99b18adb Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 31 Mar 2017 18:50:02 +0300 Subject: [PATCH] Add search in GIFs to EmojiPanel. --- Telegram/Resources/langs/lang.strings | 4 +- Telegram/SourceFiles/app.cpp | 6 +- Telegram/SourceFiles/auth_session.h | 4 + Telegram/SourceFiles/boxes/boxes.style | 76 +-- Telegram/SourceFiles/config.h | 1 - Telegram/SourceFiles/historywidget.cpp | 7 +- .../inline_bot_layout_internal.cpp | 6 +- .../inline_bots/inline_results_widget.cpp | 8 +- .../inline_bots/inline_results_widget.h | 1 - Telegram/SourceFiles/mainwidget.h | 1 - Telegram/SourceFiles/mtproto/sender.h | 4 +- Telegram/SourceFiles/stickers/emoji_panel.cpp | 203 +------- Telegram/SourceFiles/stickers/emoji_panel.h | 42 +- .../SourceFiles/stickers/gifs_list_widget.cpp | 447 ++++++++++++------ .../SourceFiles/stickers/gifs_list_widget.h | 89 ++-- Telegram/SourceFiles/stickers/stickers.style | 7 + Telegram/SourceFiles/ui/widgets/buttons.cpp | 30 +- Telegram/SourceFiles/ui/widgets/buttons.h | 22 +- 18 files changed, 480 insertions(+), 478 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index cf94232ca..e62536ae9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -774,13 +774,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_recent_stickers" = "Frequently used"; "lng_switch_stickers" = "Stickers"; -"lng_switch_stickers_gifs" = "GIFs & Stickers"; "lng_switch_emoji" = "Emoji"; "lng_switch_gifs" = "GIFs"; "lng_stickers_featured_add" = "Add"; +"lng_gifs_search" = "Search GIFs"; -"lng_saved_gifs" = "Saved GIFs"; -"lng_inline_bot_results" = "Results from {inline_bot}"; "lng_inline_bot_no_results" = "No results"; "lng_inline_bot_via" = "via {inline_bot}"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 122d4d4de..506e10c2b 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1138,7 +1138,7 @@ namespace { if (saved.size() > Global::SavedGifsLimit()) saved.pop_back(); Local::writeSavedGifs(); - if (App::main()) emit App::main()->savedGifsUpdated(); + AuthSession::Current().data().savedGifsUpdated().notify(); cSetLastSavedGifsUpdate(0); App::main()->updateStickers(); } @@ -1146,8 +1146,8 @@ namespace { void checkSavedGif(HistoryItem *item) { if (!item->Has() && (item->out() || item->history()->peer == App::self())) { - if (HistoryMedia *media = item->getMedia()) { - if (DocumentData *doc = media->getDocument()) { + if (auto media = item->getMedia()) { + if (auto doc = media->getDocument()) { if (doc->isGifv()) { addSavedGif(doc); } diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h index d2cf0a61c..f76dbe392 100644 --- a/Telegram/SourceFiles/auth_session.h +++ b/Telegram/SourceFiles/auth_session.h @@ -47,6 +47,9 @@ public: base::Observable &moreChatsLoaded() { return _moreChatsLoaded; } + base::Observable &savedGifsUpdated() { + return _savedGifsUpdated; + } void copyFrom(const AuthSessionData &other) { _variables = other._variables; @@ -76,6 +79,7 @@ private: base::Variable _contactsLoaded = { false }; base::Variable _allChatsLoaded = { false }; base::Observable _moreChatsLoaded; + base::Observable _savedGifsUpdated; Variables _variables; }; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 089e7e506..babfc3b43 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -237,6 +237,43 @@ contactsAboutFg: windowSubTextFgOver; contactsAboutTop: 60px; contactsAboutBottom: 19px; +contactsSearchField: InputField(defaultInputField) { + textBg: transparent; + textMargins: margins(2px, 7px, 2px, 0px); + + placeholderFg: placeholderFg; + placeholderFgActive: placeholderFgActive; + placeholderFgError: placeholderFgActive; + placeholderMargins: margins(2px, 0px, 2px, 0px); + placeholderScale: 0.; + placeholderFont: normalFont; + + border: 0px; + borderActive: 0px; + + heightMin: 32px; + + font: normalFont; +} +contactsSearchCancel: CrossButton { + width: 44px; + height: 44px; + + cross: CrossAnimation { + size: 36px; + skip: 12px; + stroke: 2px; + minScale: 0.3; + } + crossFg: boxTitleCloseFg; + crossFgOver: boxTitleCloseFgOver; + crossPosition: point(4px, 4px); + + duration: 150; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} contactsMultiSelect: MultiSelect { bg: boxSearchBg; padding: margins(8px, 6px, 8px, 6px); @@ -271,47 +308,12 @@ contactsMultiSelect: MultiSelect { } itemSkip: 8px; - field: InputField(defaultInputField) { - textBg: transparent; - textMargins: margins(2px, 7px, 2px, 0px); - - placeholderFg: placeholderFg; - placeholderFgActive: placeholderFgActive; - placeholderFgError: placeholderFgActive; - placeholderMargins: margins(2px, 0px, 2px, 0px); - placeholderScale: 0.; - placeholderFont: normalFont; - - border: 0px; - borderActive: 0px; - - heightMin: 32px; - - font: normalFont; - } + field: contactsSearchField; fieldMinWidth: 42px; fieldIcon: boxFieldSearchIcon; fieldIconSkip: 36px; - fieldCancel: CrossButton { - width: 44px; - height: 44px; - - cross: CrossAnimation { - size: 36px; - skip: 12px; - stroke: 2px; - minScale: 0.3; - } - crossFg: boxTitleCloseFg; - crossFgOver: boxTitleCloseFgOver; - crossPosition: point(4px, 4px); - - duration: 150; - ripple: RippleAnimation(defaultRippleAnimation) { - color: windowBgOver; - } - } + fieldCancel: contactsSearchCancel; fieldCancelSkip: 40px; } contactsPhotoCheckbox: RoundImageCheckbox { diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 2939fd3f5..e7074b415 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -79,7 +79,6 @@ enum { ClipThreadsCount = 8, AverageGifSize = 320 * 240, WaitBeforeGifPause = 200, // wait 200ms for gif draw before pausing it - InlineBotRequestDelay = 400, // wait 400ms before context bot realtime request RecentInlineBotsLimit = 10, AVBlockSize = 4096, // 4Kb for ffmpeg blocksize diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 374e40f33..8765f2e8e 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3360,10 +3360,8 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null void HistoryWidget::start() { connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); - connect(App::main(), SIGNAL(savedGifsUpdated()), _emojiPanel, SLOT(refreshSavedGifs())); - updateRecentStickers(); - if (App::main()) emit App::main()->savedGifsUpdated(); + AuthSession::Current().data().savedGifsUpdated().notify(); connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*))); } @@ -4176,7 +4174,7 @@ void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) { Local::writeSavedGifs(); - if (App::main()) emit App::main()->savedGifsUpdated(); + AuthSession::Current().data().savedGifsUpdated().notify(); } void HistoryWidget::saveGif(DocumentData *doc) { @@ -4430,6 +4428,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re _peer = App::peer(peerId); _channel = peerToChannel(_peer->id); _canSendMessages = canSendMessages(_peer); + _emojiPanel->setInlineQueryPeer(_peer); } updateTopBarSelection(); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index d0316c9da..20319a8fa 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -30,7 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "media/player/media_player_instance.h" #include "history/history_location_manager.h" #include "storage/localstorage.h" -#include "mainwidget.h" +#include "auth_session.h" #include "lang.h" namespace InlineBots { @@ -122,14 +122,14 @@ void Gif::setPosition(int32 position) { } void DeleteSavedGifClickHandler::onClickImpl() const { - int32 index = cSavedGifs().indexOf(_data); + auto index = cSavedGifs().indexOf(_data); if (index >= 0) { cRefSavedGifs().remove(index); Local::writeSavedGifs(); MTP::send(MTPmessages_SaveGif(_data->mtpInput(), MTP_bool(true))); } - if (App::main()) emit App::main()->savedGifsUpdated(); + AuthSession::Current().data().savedGifsUpdated().notify(); } void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const { diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index be7ec30d7..9b702507a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -39,6 +39,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace InlineBots { namespace Layout { namespace internal { +namespace { + +constexpr auto kInlineBotRequestDelay = 400; + +} // namespace Inner::Inner(QWidget *parent) : TWidget(parent) { setMaxHeight(st::emojiPanMaxHeight - st::emojiCategory.height); @@ -413,7 +418,6 @@ int Inner::refreshInlineRows(UserData *bot, const CacheEntry *entry, bool result clearSelection(); t_assert(_inlineBot != 0); - _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); auto count = int(entry->results.size()); auto from = validateExistingInlineRows(entry->results); @@ -1048,7 +1052,7 @@ void Widget::queryInlineBot(UserData *bot, PeerData *peer, QString query) { showInlineRows(true); } else { _inlineNextQuery = query; - _inlineRequestTimer.start(InlineBotRequestDelay); + _inlineRequestTimer.start(internal::kInlineBotRequestDelay); } } } diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h index 8523ac900..09b869217 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -120,7 +120,6 @@ private: int _visibleBottom = 0; UserData *_inlineBot; - QString _inlineBotTitle; TimeMs _lastScrolled = 0; QTimer _updateInlineItems; bool _inlineWithThumb = false; diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 8c3a89432..caac06788 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -413,7 +413,6 @@ signals: void dialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow); void dialogsUpdated(); void stickersUpdated(); - void savedGifsUpdated(); public slots: void webPagesOrGamesUpdate(); diff --git a/Telegram/SourceFiles/mtproto/sender.h b/Telegram/SourceFiles/mtproto/sender.h index 40d93cc1a..43d967287 100644 --- a/Telegram/SourceFiles/mtproto/sender.h +++ b/Telegram/SourceFiles/mtproto/sender.h @@ -364,11 +364,11 @@ private: template Sender::SpecificRequestBuilder Sender::request(Request &&request) noexcept { - return SpecificRequestBuilder(this, std::move(request)); + return SpecificRequestBuilder(this, std::move(request)); } inline Sender::SentRequestWrap Sender::request(mtpRequestId requestId) noexcept { - return SentRequestWrap(this, requestId); + return SentRequestWrap(this, requestId); } } // namespace MTP diff --git a/Telegram/SourceFiles/stickers/emoji_panel.cpp b/Telegram/SourceFiles/stickers/emoji_panel.cpp index df696f448..946088841 100644 --- a/Telegram/SourceFiles/stickers/emoji_panel.cpp +++ b/Telegram/SourceFiles/stickers/emoji_panel.cpp @@ -28,7 +28,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/shadow.h" #include "ui/widgets/discrete_sliders.h" #include "ui/widgets/scroll_area.h" -#include "inline_bots/inline_bot_result.h" #include "stickers/stickers.h" #include "storage/localstorage.h" #include "lang.h" @@ -332,16 +331,14 @@ EmojiPanel::EmojiPanel(QWidget *parent) : TWidget(parent) connect(stickers(), SIGNAL(checkForHide()), this, SLOT(onCheckForHide())); connect(gifs(), SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); connect(gifs(), SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*))); - connect(gifs(), SIGNAL(selected(InlineBots::Result*, UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*, UserData*))); - connect(gifs(), SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); + connect(gifs(), SIGNAL(selected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*))); + connect(gifs(), &GifsListWidget::cancelled, this, [this] { + hideAnimated(); + }); _saveConfigTimer.setSingleShot(true); connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); - // inline bots - _inlineRequestTimer.setSingleShot(true); - connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest())); - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } @@ -436,6 +433,7 @@ void EmojiPanel::paintEvent(QPaintEvent *e) { _showAnimation.reset(); if (!switching && !opacityAnimating) { showAll(); + currentTab()->widget()->afterShown(); } } @@ -455,6 +453,7 @@ void EmojiPanel::paintEvent(QPaintEvent *e) { _slideAnimation.reset(); if (!opacityAnimating) { showAll(); + currentTab()->widget()->afterShown(); } InvokeQueued(this, [this] { if (_hideAfterSlide && !_a_slide.animating()) { @@ -567,15 +566,6 @@ void EmojiPanel::refreshStickers() { if (isHidden() || _currentTabType != TabType::Stickers) { stickers()->preloadImages(); } - update(); -} - -void EmojiPanel::refreshSavedGifs() { - gifs()->refreshSavedGifs(); - if (isHidden() || _currentTabType != TabType::Gifs) { - gifs()->preloadImages(); - } - update(); } void EmojiPanel::opacityAnimationCallback() { @@ -586,6 +576,7 @@ void EmojiPanel::opacityAnimationCallback() { hideFinished(); } else if (!_a_show.animating() && !_a_slide.animating()) { showAll(); + currentTab()->widget()->afterShown(); } } } @@ -613,6 +604,9 @@ void EmojiPanel::prepareCache() { } void EmojiPanel::startOpacityAnimation(bool hiding) { + if (!_scroll->isHidden()) { + currentTab()->widget()->beforeHiding(); + } _hiding = false; prepareCache(); _hiding = hiding; @@ -747,6 +741,10 @@ void EmojiPanel::stickersInstalled(uint64 setId) { showAnimated(); } +void EmojiPanel::setInlineQueryPeer(PeerData *peer) { + gifs()->setInlineQueryPeer(peer); +} + bool EmojiPanel::ui_isInlineItemBeingChosen() { return (_currentTabType == TabType::Gifs && !isHidden()); } @@ -816,6 +814,10 @@ void EmojiPanel::switchTab() { auto wasTab = _currentTabType; currentTab()->saveScrollTop(); + if (!_scroll->isHidden()) { + currentTab()->widget()->beforeHiding(); + } + auto wasCache = grabForComplexAnimation(GrabType::Slide); auto widget = _scroll->takeWidget(); @@ -828,6 +830,8 @@ void EmojiPanel::switchTab() { if (_currentTabType == TabType::Gifs) { Notify::clipStopperHidden(ClipStopperSavedGifsPanel); } + currentTab()->widget()->refreshRecent(); + currentTab()->widget()->preloadImages(); setWidgetToScrollArea(); auto nowCache = grabForComplexAnimation(GrabType::Slide); @@ -883,10 +887,6 @@ void EmojiPanel::onCheckForHide() { } } -void EmojiPanel::clearInlineBot() { - inlineBotChanged(); -} - bool EmojiPanel::overlaps(const QRect &globalRect) const { if (isHidden() || !_cache.isNull()) return false; @@ -896,147 +896,6 @@ bool EmojiPanel::overlaps(const QRect &globalRect) const { || inner.marginsRemoved(QMargins(0, st::buttonRadius, 0, st::buttonRadius)).contains(testRect); } -void EmojiPanel::inlineBotChanged() { - if (!_inlineBot) return; - - if (!isHidden() && !_hiding) { - if (!rect().contains(mapFromGlobal(QCursor::pos()))) { - hideAnimated(); - } - } - - if (_inlineRequestId) MTP::cancel(_inlineRequestId); - _inlineRequestId = 0; - _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); - _inlineBot = nullptr; - _inlineCache.clear(); - gifs()->inlineBotChanged(); - gifs()->hideInlineRowsPanel(); - - Notify::inlineBotRequesting(false); -} - -void EmojiPanel::inlineResultsDone(const MTPmessages_BotResults &result) { - _inlineRequestId = 0; - Notify::inlineBotRequesting(false); - - auto it = _inlineCache.find(_inlineQuery); - auto adding = (it != _inlineCache.cend()); - if (result.type() == mtpc_messages_botResults) { - auto &d = result.c_messages_botResults(); - auto &v = d.vresults.v; - auto queryId = d.vquery_id.v; - - if (it == _inlineCache.cend()) { - it = _inlineCache.emplace(_inlineQuery, std::make_unique()).first; - } - auto entry = it->second.get(); - entry->nextOffset = qs(d.vnext_offset); - if (d.has_switch_pm() && d.vswitch_pm.type() == mtpc_inlineBotSwitchPM) { - auto &switchPm = d.vswitch_pm.c_inlineBotSwitchPM(); - entry->switchPmText = qs(switchPm.vtext); - entry->switchPmStartToken = qs(switchPm.vstart_param); - } - - if (auto count = v.size()) { - entry->results.reserve(entry->results.size() + count); - } - auto added = 0; - for_const (const auto &res, v) { - if (auto result = InlineBots::Result::create(queryId, res)) { - ++added; - entry->results.push_back(std::move(result)); - } - } - - if (!added) { - entry->nextOffset = QString(); - } - } else if (adding) { - it->second->nextOffset = QString(); - } - - if (!showInlineRows(!adding)) { - it->second->nextOffset = QString(); - } - onScroll(); -} - -void EmojiPanel::queryInlineBot(UserData *bot, PeerData *peer, QString query) { - bool force = false; - _inlineQueryPeer = peer; - if (bot != _inlineBot) { - inlineBotChanged(); - _inlineBot = bot; - force = true; - //if (_inlineBot->isBotInlineGeo()) { - // Ui::show(Box(lang(lng_bot_inline_geo_unavailable))); - //} - } - //if (_inlineBot && _inlineBot->isBotInlineGeo()) { - // return; - //} - - if (_inlineQuery != query || force) { - if (_inlineRequestId) { - MTP::cancel(_inlineRequestId); - _inlineRequestId = 0; - Notify::inlineBotRequesting(false); - } - if (_inlineCache.find(query) != _inlineCache.cend()) { - _inlineRequestTimer.stop(); - _inlineQuery = _inlineNextQuery = query; - showInlineRows(true); - } else { - _inlineNextQuery = query; - _inlineRequestTimer.start(InlineBotRequestDelay); - } - } -} - -void EmojiPanel::onInlineRequest() { - if (_inlineRequestId || !_inlineBot || !_inlineQueryPeer) return; - _inlineQuery = _inlineNextQuery; - - QString nextOffset; - auto it = _inlineCache.find(_inlineQuery); - if (it != _inlineCache.cend()) { - nextOffset = it->second->nextOffset; - if (nextOffset.isEmpty()) return; - } - Notify::inlineBotRequesting(true); - _inlineRequestId = request(MTPmessages_GetInlineBotResults(MTP_flags(0), _inlineBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset))).done([this](const MTPmessages_BotResults &result, mtpRequestId requestId) { - inlineResultsDone(result); - }).fail([this](const RPCError &error) { - // show error? - Notify::inlineBotRequesting(false); - _inlineRequestId = 0; - }).handleAllErrors().send(); -} - -void EmojiPanel::onEmptyInlineRows() { - if (!_inlineBot) { - gifs()->hideInlineRowsPanel(); - } else { - gifs()->clearInlineRowsPanel(); - } -} - -bool EmojiPanel::refreshInlineRows(int32 *added) { - auto it = _inlineCache.find(_inlineQuery); - const InlineCacheEntry *entry = nullptr; - if (it != _inlineCache.cend()) { - if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) { - entry = it->second.get(); - } - _inlineNextOffset = it->second->nextOffset; - } - if (!entry) prepareCache(); - auto result = gifs()->refreshInlineRows(_inlineBot, entry, false); - if (added) *added = result; - return (entry != nullptr); -} - void EmojiPanel::scrollToY(int y) { _scroll->scrollToY(y); @@ -1044,28 +903,6 @@ void EmojiPanel::scrollToY(int y) { _topShadow->update(); } -int32 EmojiPanel::showInlineRows(bool newResults) { - int32 added = 0; - bool clear = !refreshInlineRows(&added); - if (newResults) { - scrollToY(0); - } - - auto hidden = isHidden(); - if (clear) { - if (!_hiding) { - _cache = QPixmap(); // clear after refreshInlineRows() - } - } else { - if (_currentTabType != TabType::Gifs) { - _tabsSlider->setActiveSection(static_cast(TabType::Gifs)); - } - showAnimated(); - } - - return added; -} - EmojiPanel::Inner::Inner(QWidget *parent) : TWidget(parent) { } diff --git a/Telegram/SourceFiles/stickers/emoji_panel.h b/Telegram/SourceFiles/stickers/emoji_panel.h index e9e4ec1d4..de67c29e3 100644 --- a/Telegram/SourceFiles/stickers/emoji_panel.h +++ b/Telegram/SourceFiles/stickers/emoji_panel.h @@ -23,13 +23,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/twidget.h" #include "ui/effects/panel_animation.h" #include "mtproto/sender.h" -#include "inline_bots/inline_bot_layout_item.h" #include "auth_session.h" namespace InlineBots { -namespace Layout { -class ItemBase; -} // namespace Layout class Result; } // namespace InlineBots @@ -46,17 +42,7 @@ class EmojiListWidget; class StickersListWidget; class GifsListWidget; -using InlineResult = InlineBots::Result; -using InlineResults = std::vector>; -using InlineItem = InlineBots::Layout::ItemBase; - -struct InlineCacheEntry { - QString nextOffset; - QString switchPmText, switchPmStartToken; - InlineResults results; -}; - -class EmojiPanel : public TWidget, private MTP::Sender { +class EmojiPanel : public TWidget { Q_OBJECT public: @@ -73,10 +59,8 @@ public: void stickersInstalled(uint64 setId); - void queryInlineBot(UserData *bot, PeerData *peer, QString query); - void clearInlineBot(); - bool overlaps(const QRect &globalRect) const; + void setInlineQueryPeer(PeerData *peer); bool ui_isInlineItemBeingChosen(); @@ -102,7 +86,6 @@ public slots: private slots: void hideByTimerOrLeave(); - void refreshSavedGifs(); void onWndActiveChanged(); void onScroll(); @@ -112,9 +95,6 @@ private slots: void onSaveConfig(); void onSaveConfigDelayed(int delay); - void onInlineRequest(); - void onEmptyInlineRows(); - signals: void emojiSelected(EmojiPtr emoji); void stickerSelected(DocumentData *sticker); @@ -255,19 +235,6 @@ private: QTimer _saveConfigTimer; - // inline bots - std::map> _inlineCache; - QTimer _inlineRequestTimer; - - void inlineBotChanged(); - int32 showInlineRows(bool newResults); - bool refreshInlineRows(int32 *added = 0); - UserData *_inlineBot = nullptr; - PeerData *_inlineQueryPeer = nullptr; - QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; - mtpRequestId _inlineRequestId = 0; - void inlineResultsDone(const MTPmessages_BotResults &result); - }; class EmojiPanel::Inner : public TWidget { @@ -292,6 +259,11 @@ public: void panelHideFinished(); virtual void clearSelection() = 0; + virtual void afterShown() { + } + virtual void beforeHiding() { + } + virtual object_ptr createFooter() = 0; signals: diff --git a/Telegram/SourceFiles/stickers/gifs_list_widget.cpp b/Telegram/SourceFiles/stickers/gifs_list_widget.cpp index 0f0a8d233..2223c951c 100644 --- a/Telegram/SourceFiles/stickers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/stickers/gifs_list_widget.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_stickers.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/input_fields.h" #include "ui/effects/ripple_animation.h" #include "boxes/stickers_box.h" #include "inline_bots/inline_bot_result.h" @@ -34,8 +35,10 @@ namespace ChatHelpers { namespace { constexpr auto kSaveChosenTabTimeout = 1000; +constexpr auto kSearchRequestDelay = 400; constexpr auto kStickersPanelPerRow = Stickers::kPanelPerRow; constexpr auto kInlineItemsMaxPerRow = 5; +constexpr auto kSearchBotUsername = "gif"; } // namespace @@ -43,20 +46,67 @@ class GifsListWidget::Footer : public EmojiPanel::InnerFooter { public: Footer(gsl::not_null parent); + void stealFocus(); + void returnFocus(); + protected: + void paintEvent(QPaintEvent *e) override; + void processPanelHideFinished() override; private: gsl::not_null _pan; + object_ptr _field; + object_ptr _cancel; + + QPointer _focusTakenFrom; + }; GifsListWidget::Footer::Footer(gsl::not_null parent) : InnerFooter(parent) -, _pan(parent) { +, _pan(parent) +, _field(this, st::gifsSearchField, lang(lng_gifs_search)) +, _cancel(this, st::gifsSearchCancel) { + _field->resize(width() - st::gifsSearchFieldPosition.x() - st::gifsSearchCancelPosition.x() - st::gifsSearchCancel.width, _field->height()); + _field->moveToLeft(st::gifsSearchFieldPosition.x(), st::gifsSearchFieldPosition.y()); + connect(_field, &Ui::InputField::submitted, this, [this](bool ctrlShiftEnter) { + _pan->sendInlineRequest(); + }); + connect(_field, &Ui::InputField::cancelled, this, [this] { + if (_field->getLastText().isEmpty()) { + emit _pan->cancelled(); + } else { + _field->setText(QString()); + } + }); + connect(_field, &Ui::InputField::changed, this, [this] { + _cancel->toggleAnimated(!_field->getLastText().isEmpty()); + _pan->searchForGifs(_field->getLastText()); + }); + _cancel->moveToRight(st::gifsSearchCancelPosition.x(), st::gifsSearchCancelPosition.y()); +} + +void GifsListWidget::Footer::stealFocus() { + if (!_focusTakenFrom) { + _focusTakenFrom = QApplication::focusWidget(); + } + _field->setFocus(); +} + +void GifsListWidget::Footer::returnFocus() { + if (_focusTakenFrom) { + _focusTakenFrom->setFocus(); + } +} + +void GifsListWidget::Footer::paintEvent(QPaintEvent *e) { + Painter p(this); + st::gifsSearchIcon.paint(p, st::gifsSearchIconPosition.x(), st::gifsSearchIconPosition.y(), width()); } void GifsListWidget::Footer::processPanelHideFinished() { - // TODO Clear search + _field->setText(QString()); } GifsListWidget::GifsListWidget(QWidget *parent) : Inner(parent) @@ -72,6 +122,12 @@ GifsListWidget::GifsListWidget(QWidget *parent) : Inner(parent) _updateInlineItems.setSingleShot(true); connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); + _inlineRequestTimer.setSingleShot(true); + connect(&_inlineRequestTimer, &QTimer::timeout, this, [this] { sendInlineRequest(); }); + + subscribe(AuthSession::Current().data().savedGifsUpdated(), [this] { + refreshSavedGifs(); + }); subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { update(); }); @@ -90,9 +146,13 @@ void GifsListWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { if (top != getVisibleTop()) { _lastScrolled = getms(); } - auto visibleHeight = (visibleBottom - visibleTop); - if (visibleBottom + visibleHeight > height()) { -// onInlineRequest(); // TODO + checkLoadMore(); +} + +void GifsListWidget::checkLoadMore() { + auto visibleHeight = (getVisibleBottom() - getVisibleTop()); + if (getVisibleBottom() + visibleHeight > height()) { + sendInlineRequest(); } } @@ -103,11 +163,8 @@ int GifsListWidget::countHeight() { } auto minimalLastHeight = (visibleHeight - st::stickerPanPadding); auto result = st::stickerPanPadding; - if (_switchPmButton) { - result += _switchPmButton->height() + st::inlineResultsSkip; - } - for (int i = 0, l = _inlineRows.count(); i < l; ++i) { - result += _inlineRows[i].height; + for (int i = 0, l = _rows.count(); i < l; ++i) { + result += _rows[i].height; } return qMax(minimalLastHeight, result) + st::stickerPanPadding; } @@ -118,6 +175,56 @@ GifsListWidget::~GifsListWidget() { deleteUnusedInlineLayouts(); } +void GifsListWidget::cancelGifsSearch() { + if (_inlineRequestId) { + request(_inlineRequestId).cancel(); + _inlineRequestId = 0; + } + _inlineRequestTimer.stop(); + _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); + _inlineCache.clear(); + refreshInlineRows(nullptr, true); +} + +void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) { + _inlineRequestId = 0; + + auto it = _inlineCache.find(_inlineQuery); + auto adding = (it != _inlineCache.cend()); + if (result.type() == mtpc_messages_botResults) { + auto &d = result.c_messages_botResults(); + auto &v = d.vresults.v; + auto queryId = d.vquery_id.v; + + if (it == _inlineCache.cend()) { + it = _inlineCache.emplace(_inlineQuery, std::make_unique()).first; + } + auto entry = it->second.get(); + entry->nextOffset = qs(d.vnext_offset); + if (auto count = v.size()) { + entry->results.reserve(entry->results.size() + count); + } + auto added = 0; + for_const (const auto &res, v) { + if (auto result = InlineBots::Result::create(queryId, res)) { + ++added; + entry->results.push_back(std::move(result)); + } + } + + if (!added) { + entry->nextOffset = QString(); + } + } else if (adding) { + it->second->nextOffset = QString(); + } + + if (!showInlineRows(!adding)) { + it->second->nextOffset = QString(); + } + checkLoadMore(); +} + void GifsListWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto clip = e->rect(); @@ -127,7 +234,7 @@ void GifsListWidget::paintEvent(QPaintEvent *e) { } void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { - if (_inlineRows.isEmpty() && !_switchPmButton) { + if (_rows.isEmpty()) { p.setFont(st::normalFont); p.setPen(st::noContactsColor); p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); @@ -137,14 +244,10 @@ void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { InlineBots::Layout::PaintContext context(getms(), false, gifPaused, false); auto top = st::stickerPanPadding; - if (_switchPmButton) { - top += _switchPmButton->height() + st::inlineResultsSkip; - } - auto fromx = rtl() ? (width() - clip.x() - clip.width()) : clip.x(); auto tox = rtl() ? (width() - clip.x()) : (clip.x() + clip.width()); - for (auto row = 0, rows = _inlineRows.size(); row != rows; ++row) { - auto &inlineRow = _inlineRows[row]; + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + auto &inlineRow = _rows[row]; if (top >= clip.top() + clip.height()) { break; } @@ -210,11 +313,11 @@ void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) { } void GifsListWidget::selectInlineResult(int row, int column) { - if (row >= _inlineRows.size() || column >= _inlineRows.at(row).items.size()) { + if (row >= _rows.size() || column >= _rows[row].items.size()) { return; } - auto item = _inlineRows[row].items[column]; + auto item = _rows[row].items[column]; if (auto photo = item->getPhoto()) { if (photo->medium->loaded() || photo->thumb->loaded()) { emit selected(photo); @@ -232,7 +335,7 @@ void GifsListWidget::selectInlineResult(int row, int column) { } } else if (auto inlineResult = item->getResult()) { if (inlineResult->onChoose(item)) { - emit selected(inlineResult, _inlineBot); + emit selected(inlineResult, _searchBot); } } } @@ -258,8 +361,8 @@ void GifsListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { void GifsListWidget::clearSelection() { if (_selected >= 0) { int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - ClickHandler::clearActive(_inlineRows.at(srow).items.at(scol)); + t_assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); + ClickHandler::clearActive(_rows[srow].items[scol]); setCursor(style::cur_default); } _selected = _pressed = -1; @@ -295,18 +398,18 @@ void GifsListWidget::processPanelHideFinished() { } } -bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { - InlineItem *layout = nullptr; +bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth) { + LayoutItem *layout = nullptr; if (savedGif) { - layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + layout = layoutPrepareSavedGif(savedGif, (_rows.size() * MatrixRowShift) + row.items.size()); } else if (result) { - layout = layoutPrepareInlineResult(result, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); } if (!layout) return false; layout->preload(); if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_inlineRows.size() * MatrixRowShift); + layout->setPosition(_rows.size() * MatrixRowShift); } sumWidth += layout->maxWidth(); @@ -318,14 +421,14 @@ bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *res return true; } -bool GifsListWidget::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { +bool GifsListWidget::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { if (row.items.isEmpty()) return false; auto full = (row.items.size() >= kInlineItemsMaxPerRow); auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); if (full || big || force) { - _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); - row = InlineRow(); + _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = Row(); row.items.reserve(kInlineItemsMaxPerRow); sumWidth = 0; return true; @@ -339,8 +442,8 @@ void GifsListWidget::refreshSavedGifs() { auto &saved = cSavedGifs(); if (!saved.isEmpty()) { - _inlineRows.reserve(saved.size()); - auto row = InlineRow(); + _rows.reserve(saved.size()); + auto row = Row(); row.items.reserve(kInlineItemsMaxPerRow); auto sumWidth = 0; for_const (auto &gif, saved) { @@ -350,16 +453,19 @@ void GifsListWidget::refreshSavedGifs() { } deleteUnusedGifLayouts(); - int32 h = countHeight(); - if (h != height()) resize(width(), h); + auto newHeight = countHeight(); + if (newHeight != height()) { + resize(width(), newHeight); + } update(); } - updateSelected(); -} -void GifsListWidget::inlineBotChanged() { - refreshInlineRows(nullptr, nullptr, true); + if (isVisible()) { + updateSelected(); + } else { + preloadImages(); + } } void GifsListWidget::clearInlineRows(bool resultsDeleted) { @@ -367,19 +473,19 @@ void GifsListWidget::clearInlineRows(bool resultsDeleted) { _selected = _pressed = -1; } else { clearSelection(); - for_const (auto &row, _inlineRows) { + for_const (auto &row, _rows) { for_const (auto &item, row.items) { item->setPosition(-1); } } } - _inlineRows.clear(); + _rows.clear(); } -InlineItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) { +GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) { auto it = _gifLayouts.find(doc); if (it == _gifLayouts.cend()) { - if (auto layout = InlineItem::createLayoutGif(this, doc)) { + if (auto layout = LayoutItem::createLayoutGif(this, doc)) { it = _gifLayouts.emplace(doc, std::move(layout)).first; it->second->initDimensions(); } else { @@ -392,10 +498,10 @@ InlineItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 posit return it->second.get(); } -InlineItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) { +GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) { auto it = _inlineLayouts.find(result); if (it == _inlineLayouts.cend()) { - if (auto layout = InlineItem::createLayout(this, result, _inlineWithThumb)) { + if (auto layout = LayoutItem::createLayout(this, result, _inlineWithThumb)) { it = _inlineLayouts.emplace(result, std::move(layout)).first; it->second->initDimensions(); } else { @@ -409,7 +515,7 @@ InlineItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int3 } void GifsListWidget::deleteUnusedGifLayouts() { - if (_inlineRows.isEmpty() || _section != Section::Gifs) { // delete all + if (_rows.isEmpty() || _section != Section::Gifs) { // delete all _gifLayouts.clear(); } else { for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { @@ -423,7 +529,7 @@ void GifsListWidget::deleteUnusedGifLayouts() { } void GifsListWidget::deleteUnusedInlineLayouts() { - if (_inlineRows.isEmpty() || _section == Section::Gifs) { // delete all + if (_rows.isEmpty() || _section == Section::Gifs) { // delete all _inlineLayouts.clear(); } else { for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { @@ -436,7 +542,7 @@ void GifsListWidget::deleteUnusedInlineLayouts() { } } -GifsListWidget::InlineRow &GifsListWidget::layoutInlineRow(InlineRow &row, int32 sumWidth) { +GifsListWidget::Row &GifsListWidget::layoutInlineRow(Row &row, int32 sumWidth) { auto count = int(row.items.size()); t_assert(count <= kInlineItemsMaxPerRow); @@ -456,7 +562,7 @@ GifsListWidget::InlineRow &GifsListWidget::layoutInlineRow(InlineRow &row, int32 int index = indices[i]; int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); int actualw = qMax(w, int(st::inlineResultsMinWidth)); - row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); + row.height = qMax(row.height, row.items[index]->resizeGetHeight(actualw)); if (sumWidth) { availw -= actualw; sumWidth -= row.items.at(index)->maxWidth(); @@ -470,14 +576,14 @@ GifsListWidget::InlineRow &GifsListWidget::layoutInlineRow(InlineRow &row, int32 } void GifsListWidget::preloadImages() { - for (auto row = 0, rows = _inlineRows.size(); row != rows; ++row) { - for (auto col = 0, cols = _inlineRows[row].items.size(); col != cols; ++col) { - _inlineRows[row].items[col]->preload(); + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { + _rows[row].items[col]->preload(); } } } -void GifsListWidget::hideInlineRowsPanel() { +void GifsListWidget::switchToSavedGifs() { clearInlineRows(false); _section = Section::Gifs; refreshSavedGifs(); @@ -485,66 +591,25 @@ void GifsListWidget::hideInlineRowsPanel() { emit scrollUpdated(); } -void GifsListWidget::clearInlineRowsPanel() { - clearInlineRows(false); -} - -void GifsListWidget::refreshSwitchPmButton(const InlineCacheEntry *entry) { - if (!entry || entry->switchPmText.isEmpty()) { - _switchPmButton.destroy(); - _switchPmStartToken.clear(); - } else { - if (!_switchPmButton) { - _switchPmButton.create(this, QString(), st::switchPmButton); - _switchPmButton->show(); - _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - connect(_switchPmButton, SIGNAL(clicked()), this, SLOT(onSwitchPm())); - } - _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() - _switchPmStartToken = entry->switchPmStartToken; - auto buttonTop = st::stickerPanPadding; - _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); - } - update(); -} - -int GifsListWidget::refreshInlineRows(UserData *bot, const InlineCacheEntry *entry, bool resultsDeleted) { - _inlineBot = bot; - refreshSwitchPmButton(entry); - auto clearResults = [this, entry]() { - if (!entry) { - return true; - } - if (entry->results.empty() && entry->switchPmText.isEmpty()) { - if (!_inlineBot) { - return true; - } - } - return false; - }; - auto clearResultsResult = clearResults(); - if (clearResultsResult) { +int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool resultsDeleted) { + if (!entry) { if (resultsDeleted) { clearInlineRows(true); deleteUnusedInlineLayouts(); } - emit emptyInlineRows(); + switchToSavedGifs(); return 0; } clearSelection(); - t_assert(_inlineBot != 0); - _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); - _section = Section::Inlines; auto count = int(entry->results.size()); auto from = validateExistingInlineRows(entry->results); auto added = 0; - if (count) { - _inlineRows.reserve(count); - auto row = InlineRow(); + _rows.reserve(count); + auto row = Row(); row.items.reserve(kInlineItemsMaxPerRow); auto sumWidth = 0; for (auto i = from; i != count; ++i) { @@ -568,52 +633,52 @@ int GifsListWidget::refreshInlineRows(UserData *bot, const InlineCacheEntry *ent int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { int count = results.size(), until = 0, untilrow = 0, untilcol = 0; for (; until < count;) { - if (untilrow >= _inlineRows.size() || _inlineRows[untilrow].items[untilcol]->getResult() != results[until].get()) { + if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { break; } ++until; - if (++untilcol == _inlineRows[untilrow].items.size()) { + if (++untilcol == _rows[untilrow].items.size()) { ++untilrow; untilcol = 0; } } if (until == count) { // all items are layed out - if (untilrow == _inlineRows.size()) { // nothing changed + if (untilrow == _rows.size()) { // nothing changed return until; } - for (int i = untilrow, l = _inlineRows.size(), skip = untilcol; i < l; ++i) { - for (int j = 0, s = _inlineRows[i].items.size(); j < s; ++j) { + for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { if (skip) { --skip; } else { - _inlineRows[i].items[j]->setPosition(-1); + _rows[i].items[j]->setPosition(-1); } } } if (!untilcol) { // all good rows are filled - _inlineRows.resize(untilrow); + _rows.resize(untilrow); return until; } - _inlineRows.resize(untilrow + 1); - _inlineRows[untilrow].items.resize(untilcol); - _inlineRows[untilrow] = layoutInlineRow(_inlineRows[untilrow]); + _rows.resize(untilrow + 1); + _rows[untilrow].items.resize(untilcol); + _rows[untilrow] = layoutInlineRow(_rows[untilrow]); return until; } if (untilrow && !untilcol) { // remove last row, maybe it is not full --untilrow; - untilcol = _inlineRows[untilrow].items.size(); + untilcol = _rows[untilrow].items.size(); } until -= untilcol; - for (int i = untilrow, l = _inlineRows.size(); i < l; ++i) { - for (int j = 0, s = _inlineRows[i].items.size(); j < s; ++j) { - _inlineRows[i].items[j]->setPosition(-1); + for (int i = untilrow, l = _rows.size(); i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + _rows[i].items[j]->setPosition(-1); } } - _inlineRows.resize(untilrow); + _rows.resize(untilrow); - if (_inlineRows.isEmpty()) { + if (_rows.isEmpty()) { _inlineWithThumb = false; for (int i = until; i < count; ++i) { if (results.at(i)->hasThumbDisplay()) { @@ -625,20 +690,20 @@ int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { return until; } -void GifsListWidget::inlineItemLayoutChanged(const InlineItem *layout) { +void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { if (_selected < 0 || !isVisible()) { return; } int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - if (layout == _inlineRows.at(row).items.at(col)) { + if (row < _rows.size() && col < _rows[row].items.size()) { + if (layout == _rows[row].items[col]) { updateSelected(); } } } -void GifsListWidget::inlineItemRepaint(const InlineItem *layout) { +void GifsListWidget::inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) { auto ms = getms(); if (_lastScrolled + 100 <= ms) { update(); @@ -647,7 +712,7 @@ void GifsListWidget::inlineItemRepaint(const InlineItem *layout) { } } -bool GifsListWidget::inlineItemVisible(const InlineItem *layout) { +bool GifsListWidget::inlineItemVisible(const InlineBots::Layout::ItemBase *layout) { auto position = layout->position(); if (position < 0 || !isVisible()) { return false; @@ -655,15 +720,113 @@ bool GifsListWidget::inlineItemVisible(const InlineItem *layout) { auto row = position / MatrixRowShift; auto col = position % MatrixRowShift; - t_assert((row < _inlineRows.size()) && (col < _inlineRows[row].items.size())); + t_assert((row < _rows.size()) && (col < _rows[row].items.size())); - auto &inlineItems = _inlineRows[row].items; + auto &inlineItems = _rows[row].items; auto top = 0; for (auto i = 0; i != row; ++i) { - top += _inlineRows[i].height; + top += _rows[i].height; } - return (top < getVisibleBottom()) && (top + _inlineRows[row].items[col]->height() > getVisibleTop()); + return (top < getVisibleBottom()) && (top + _rows[row].items[col]->height() > getVisibleTop()); +} + +void GifsListWidget::afterShown() { + if (_footer) { + _footer->stealFocus(); + } +} + +void GifsListWidget::beforeHiding() { + if (_footer) { + _footer->returnFocus(); + } +} + +bool GifsListWidget::refreshInlineRows(int32 *added) { + auto it = _inlineCache.find(_inlineQuery); + const InlineCacheEntry *entry = nullptr; + if (it != _inlineCache.cend()) { + if (!it->second->results.empty()) { + entry = it->second.get(); + } + _inlineNextOffset = it->second->nextOffset; + } + auto result = refreshInlineRows(entry, false); + if (added) *added = result; + return (entry != nullptr); +} + +int32 GifsListWidget::showInlineRows(bool newResults) { + auto added = 0; + auto clear = !refreshInlineRows(&added); + if (newResults) { + scrollToY(0); + } + return added; +} + +void GifsListWidget::searchForGifs(const QString &query) { + if (query.isEmpty()) { + cancelGifsSearch(); + return; + } + + if (_inlineQuery != query) { + if (_inlineRequestId) { + request(_inlineRequestId).cancel(); + _inlineRequestId = 0; + } + if (_inlineCache.find(query) != _inlineCache.cend()) { + _inlineRequestTimer.stop(); + _inlineQuery = _inlineNextQuery = query; + showInlineRows(true); + } else { + _inlineNextQuery = query; + _inlineRequestTimer.start(kSearchRequestDelay); + } + } + + if (!_searchBot && !_searchBotRequestId) { + _searchBotRequestId = request(MTPcontacts_ResolveUsername(MTP_string(kSearchBotUsername))).done([this](const MTPcontacts_ResolvedPeer &result) { + Expects(result.type() == mtpc_contacts_resolvedPeer); + auto &data = result.c_contacts_resolvedPeer(); + App::feedUsers(data.vusers); + App::feedChats(data.vchats); + if (auto peer = App::peerLoaded(peerFromMTP(data.vpeer))) { + if (auto user = peer->asUser()) { + _searchBot = user; + } + } + }).send(); + } +} + +void GifsListWidget::sendInlineRequest() { + if (_inlineRequestId || !_inlineQueryPeer || _inlineNextQuery.isEmpty()) { + return; + } + if (!_searchBot) { + // Wait for the bot being resolved. + _inlineRequestTimer.start(kSearchRequestDelay); + return; + } + _inlineRequestTimer.stop(); + _inlineQuery = _inlineNextQuery; + + auto nextOffset = QString(); + auto it = _inlineCache.find(_inlineQuery); + if (it != _inlineCache.cend()) { + nextOffset = it->second->nextOffset; + if (nextOffset.isEmpty()) return; + } + + _inlineRequestId = request(MTPmessages_GetInlineBotResults(MTP_flags(0), _searchBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset))).done([this](const MTPmessages_BotResults &result, mtpRequestId requestId) { + inlineResultsDone(result); + }).fail([this](const RPCError &error) { + // show error? + _inlineRequestId = 0; + }).handleAllErrors().send(); } void GifsListWidget::refreshRecent() { @@ -682,32 +845,29 @@ void GifsListWidget::updateSelected() { int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); int sy = p.y() - st::stickerPanPadding; - if (_switchPmButton) { - sy -= _switchPmButton->height() + st::inlineResultsSkip; - } int row = -1, col = -1, sel = -1; ClickHandlerPtr lnk; ClickHandlerHost *lnkhost = nullptr; HistoryCursorState cursor = HistoryDefaultCursorState; if (sy >= 0) { row = 0; - for (int rows = _inlineRows.size(); row < rows; ++row) { - if (sy < _inlineRows.at(row).height) { + for (int rows = _rows.size(); row < rows; ++row) { + if (sy < _rows[row].height) { break; } - sy -= _inlineRows.at(row).height; + sy -= _rows[row].height; } } - if (sx >= 0 && row >= 0 && row < _inlineRows.size()) { - auto &inlineItems = _inlineRows[row].items; + if (sx >= 0 && row >= 0 && row < _rows.size()) { + auto &inlineItems = _rows[row].items; col = 0; for (int cols = inlineItems.size(); col < cols; ++col) { - int width = inlineItems.at(col)->width(); + int width = inlineItems[col]->width(); if (sx < width) { break; } sx -= width; - if (inlineItems.at(col)->hasRightSkip()) { + if (inlineItems[col]->hasRightSkip()) { sx -= st::inlineResultsSkip; } } @@ -725,18 +885,18 @@ void GifsListWidget::updateSelected() { int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; if (_selected != sel) { if (srow >= 0 && scol >= 0) { - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - _inlineRows[srow].items[scol]->update(); + t_assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows[srow].items.size()); + _rows[srow].items[scol]->update(); } _selected = sel; if (row >= 0 && col >= 0) { - t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); - _inlineRows[row].items[col]->update(); + t_assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows[row].items.size()); + _rows[row].items[col]->update(); } if (_previewShown && _selected >= 0 && _pressed != _selected) { _pressed = _selected; if (row >= 0 && col >= 0) { - auto layout = _inlineRows.at(row).items.at(col); + auto layout = _rows[row].items[col]; if (auto previewDocument = layout->getPreviewDocument()) { Ui::showMediaPreview(previewDocument); } else if (auto previewPhoto = layout->getPreviewPhoto()) { @@ -753,8 +913,8 @@ void GifsListWidget::updateSelected() { void GifsListWidget::onPreview() { if (_pressed < 0) return; int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - auto layout = _inlineRows.at(row).items.at(col); + if (row < _rows.size() && col < _rows[row].items.size()) { + auto layout = _rows[row].items[col]; if (auto previewDocument = layout->getPreviewDocument()) { Ui::showMediaPreview(previewDocument); _previewShown = true; @@ -774,11 +934,4 @@ void GifsListWidget::onUpdateInlineItems() { } } -void GifsListWidget::onSwitchPm() { - if (_inlineBot && _inlineBot->botInfo) { - _inlineBot->botInfo->startToken = _switchPmStartToken; - Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); - } -} - } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/gifs_list_widget.h b/Telegram/SourceFiles/stickers/gifs_list_widget.h index a223e9a09..94fc4ecb0 100644 --- a/Telegram/SourceFiles/stickers/gifs_list_widget.h +++ b/Telegram/SourceFiles/stickers/gifs_list_widget.h @@ -21,6 +21,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "stickers/emoji_panel.h" +#include "inline_bots/inline_bot_layout_item.h" + +namespace InlineBots { +namespace Layout { +class ItemBase; +} // namespace Layout +class Result; +} // namespace InlineBots namespace Ui { class RoundButton; @@ -28,7 +36,7 @@ class RoundButton; namespace ChatHelpers { -class GifsListWidget : public EmojiPanel::Inner, public InlineBots::Layout::Context, private base::Subscriber { +class GifsListWidget : public EmojiPanel::Inner, public InlineBots::Layout::Context, private base::Subscriber, private MTP::Sender { Q_OBJECT public: @@ -39,17 +47,20 @@ public: void clearSelection() override; object_ptr createFooter() override; - void refreshSavedGifs(); - int refreshInlineRows(UserData *bot, const InlineCacheEntry *results, bool resultsDeleted); - void inlineBotChanged(); - void hideInlineRowsPanel(); - void clearInlineRowsPanel(); - void setVisibleTopBottom(int visibleTop, int visibleBottom) override; - void inlineItemLayoutChanged(const InlineItem *layout) override; - void inlineItemRepaint(const InlineItem *layout) override; - bool inlineItemVisible(const InlineItem *layout) override; + void inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) override; + void inlineItemRepaint(const InlineBots::Layout::ItemBase *layout) override; + bool inlineItemVisible(const InlineBots::Layout::ItemBase *layout) override; + + void afterShown() override; + void beforeHiding() override; + + void setInlineQueryPeer(PeerData *peer) { + _inlineQueryPeer = peer; + } + void searchForGifs(const QString &query); + void sendInlineRequest(); ~GifsListWidget(); @@ -70,12 +81,12 @@ protected: private slots: void onPreview(); void onUpdateInlineItems(); - void onSwitchPm(); signals: void selected(DocumentData *sticker); void selected(PhotoData *photo); void selected(InlineBots::Result *result, UserData *bot); + void cancelled(); void emptyInlineRows(); void scrollUpdated(); @@ -87,39 +98,50 @@ private: }; class Footer; + using InlineResult = InlineBots::Result; + using InlineResults = std::vector>; + using LayoutItem = InlineBots::Layout::ItemBase; + + struct InlineCacheEntry { + QString nextOffset; + InlineResults results; + }; + + void cancelGifsSearch(); + void switchToSavedGifs(); + void refreshSavedGifs(); + int refreshInlineRows(const InlineCacheEntry *results, bool resultsDeleted); + void checkLoadMore(); + + int32 showInlineRows(bool newResults); + bool refreshInlineRows(int32 *added = 0); + void inlineResultsDone(const MTPmessages_BotResults &result); + void updateSelected(); void paintInlineItems(Painter &p, QRect clip); - void refreshSwitchPmButton(const InlineCacheEntry *entry); Section _section = Section::Gifs; - UserData *_inlineBot; - QString _inlineBotTitle; TimeMs _lastScrolled = 0; QTimer _updateInlineItems; bool _inlineWithThumb = false; - object_ptr _switchPmButton = { nullptr }; - QString _switchPmStartToken; - - typedef QVector InlineItems; - struct InlineRow { + struct Row { int height = 0; - InlineItems items; + QVector items; }; - typedef QVector InlineRows; - InlineRows _inlineRows; + QVector _rows; void clearInlineRows(bool resultsDeleted); - std::map> _gifLayouts; - InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); + std::map> _gifLayouts; + LayoutItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); - std::map> _inlineLayouts; - InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); + std::map> _inlineLayouts; + LayoutItem *layoutPrepareInlineResult(InlineResult *result, int32 position); - bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); - bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); + bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, Row &row, int32 &sumWidth); + bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); - InlineRow &layoutInlineRow(InlineRow &row, int32 sumWidth = 0); + Row &layoutInlineRow(Row &row, int32 sumWidth = 0); void deleteUnusedGifLayouts(); void deleteUnusedInlineLayouts(); @@ -136,6 +158,15 @@ private: QTimer _previewTimer; bool _previewShown = false; + std::map> _inlineCache; + QTimer _inlineRequestTimer; + + UserData *_searchBot = nullptr; + mtpRequestId _searchBotRequestId = 0; + PeerData *_inlineQueryPeer = nullptr; + QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; + mtpRequestId _inlineRequestId = 0; + }; } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style index fb3f94ec3..527e0a4b2 100644 --- a/Telegram/SourceFiles/stickers/stickers.style +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -211,3 +211,10 @@ inlineBotsScroll: ScrollArea(defaultSolidScroll) { deltat: stickerPanPadding; deltab: stickerPanPadding; } + +gifsSearchField: contactsSearchField; +gifsSearchFieldPosition: point(42px, 7px); +gifsSearchCancel: contactsSearchCancel; +gifsSearchCancelPosition: point(1px, 1px); +gifsSearchIcon: boxFieldSearchIcon; +gifsSearchIconPosition: point(6px, 7px); diff --git a/Telegram/SourceFiles/ui/widgets/buttons.cpp b/Telegram/SourceFiles/ui/widgets/buttons.cpp index 47154a263..7d4856574 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.cpp +++ b/Telegram/SourceFiles/ui/widgets/buttons.cpp @@ -631,28 +631,14 @@ CrossButton::CrossButton(QWidget *parent, const style::CrossButton &st) : Ripple hide(); } -void CrossButton::showAnimated() { - startAnimation(true); -} - -void CrossButton::showFast() { - showAnimated(); - _a_show.finish(); -} - -void CrossButton::hideAnimated() { - startAnimation(false); -} - -void CrossButton::hideFast() { - hideAnimated(); - _a_show.finish(); -} - -void CrossButton::startAnimation(bool shown) { - if (_shown == shown) return; - _shown = shown; - if (isHidden()) show(); +void CrossButton::toggleAnimated(bool visible) { + if (_shown == visible) { + return; + } + _shown = visible; + if (isHidden()) { + show(); + } _a_show.start([this] { animationCallback(); }, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration); } diff --git a/Telegram/SourceFiles/ui/widgets/buttons.h b/Telegram/SourceFiles/ui/widgets/buttons.h index 0e4701def..1cd82b3c0 100644 --- a/Telegram/SourceFiles/ui/widgets/buttons.h +++ b/Telegram/SourceFiles/ui/widgets/buttons.h @@ -212,10 +212,23 @@ class CrossButton : public RippleButton { public: CrossButton(QWidget *parent, const style::CrossButton &st); - void showAnimated(); - void showFast(); - void hideAnimated(); - void hideFast(); + void showAnimated() { + toggleAnimated(true); + } + void hideAnimated() { + toggleAnimated(false); + } + void toggleAnimated(bool visible); + void showFast() { + toggleFast(true); + } + void hideFast() { + toggleFast(false); + } + void toggleFast(bool visible) { + toggleAnimated(visible); + _a_show.finish(); + } bool isShown() const { return _shown; @@ -230,7 +243,6 @@ protected: QPoint prepareRippleStartPosition() const override; private: - void startAnimation(bool shown); void animationCallback(); const style::CrossButton &_st;