diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c42e141da..8f6ea9975 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -981,6 +981,8 @@ PRIVATE media/stories/media_stories_delegate.h media/stories/media_stories_header.cpp media/stories/media_stories_header.h + media/stories/media_stories_reactions.cpp + media/stories/media_stories_reactions.h media/stories/media_stories_recent_views.cpp media/stories/media_stories_recent_views.h media/stories/media_stories_reply.cpp diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 9f29463ce..f41482c19 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -623,6 +623,10 @@ rpl::producer<> EmojiListWidget::jumpedToPremium() const { return _jumpedToPremium.events(); } +rpl::producer<> EmojiListWidget::escapes() const { + return _search ? _search->escapes() : rpl::never<>(); +} + void EmojiListWidget::prepareExpanding() { if (_search) { _searchExpandCache = _search->grab(); @@ -633,13 +637,14 @@ void EmojiListWidget::paintExpanding( Painter &p, QRect clip, int finalBottom, - float64 progress, + float64 geometryProgress, + float64 fullProgress, RectPart origin) { const auto searchShift = _search ? anim::interpolate( st().padding.top() - _search->height(), 0, - progress) + geometryProgress) : 0; const auto shift = clip.topLeft() + QPoint(0, searchShift); const auto adjusted = clip.translated(-shift); @@ -654,7 +659,7 @@ void EmojiListWidget::paintExpanding( p.translate(shift); p.setClipRect(adjusted); paint(p, ExpandingContext{ - .progress = progress, + .progress = fullProgress, .finalHeight = finalHeight, .expanding = true, }, adjusted); diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index cc97d64db..374b6e7b3 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -125,6 +125,7 @@ public: [[nodiscard]] rpl::producer chosen() const; [[nodiscard]] rpl::producer customChosen() const; [[nodiscard]] rpl::producer<> jumpedToPremium() const; + [[nodiscard]] rpl::producer<> escapes() const; void provideRecent(const std::vector &customRecentList); @@ -133,7 +134,8 @@ public: Painter &p, QRect clip, int finalBottom, - float64 progress, + float64 geometryProgress, + float64 fullProgress, RectPart origin); base::unique_qptr fillContextMenu( diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp index 52eb79672..c1b08a657 100644 --- a/Telegram/SourceFiles/data/data_stories.cpp +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -1224,7 +1224,7 @@ void Stories::loadViewsSlice( MTP_int(id), MTP_int(offset ? offset->date : 0), MTP_long(offset ? peerToUser(offset->peer->id).bare : 0), - MTP_int(2) + MTP_int(kViewsPerPage) )).done([=](const MTPstories_StoryViewsList &result) { _viewsRequestId = 0; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 45c07c9ee..c53650919 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1854,7 +1854,8 @@ void ComposeControls::fieldChanged() { && !_header->isEditingMessage() && (_textUpdateEvents & TextUpdateEvent::SendTyping)); updateSendButtonType(); - if (!HasSendText(_field) && _preview) { + _hasSendText = HasSendText(_field); + if (!_hasSendText.current() && _preview) { _preview->setState(Data::PreviewState::Allowed); } if (updateBotCommandShown()) { @@ -2953,6 +2954,10 @@ rpl::producer ComposeControls::recordingValue() const { return _recording.value(); } +rpl::producer ComposeControls::hasSendTextValue() const { + return _hasSendText.value(); +} + bool ComposeControls::preventsClose(Fn &&continueCallback) const { if (_voiceRecordBar->isActive()) { _voiceRecordBar->showDiscardBox(std::move(continueCallback)); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 11b2efe22..f8c887593 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -215,6 +215,7 @@ public: [[nodiscard]] bool isLockPresent() const; [[nodiscard]] bool isRecording() const; [[nodiscard]] rpl::producer recordingValue() const; + [[nodiscard]] rpl::producer hasSendTextValue() const; void applyCloudDraft(); void applyDraft( @@ -382,6 +383,7 @@ private: rpl::event_stream _replyNextRequests; rpl::event_stream<> _focusRequests; rpl::variable _recording; + rpl::variable _hasSendText; TextUpdateEvents _textUpdateEvents = TextUpdateEvents() | TextUpdateEvent::SaveDraft diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp index ca6ddf226..502c3d1e5 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp @@ -105,48 +105,53 @@ bool StripEmoji::readyInDefaultState() { Selector::Selector( not_null parent, - not_null parentController, + std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, IconFactory iconFactory, - Fn close) + Fn close, + bool child) : Selector( parent, - parentController, + std::move(show), reactions, (reactions.customAllowed ? ChatHelpers::EmojiListMode::FullReactions : ChatHelpers::EmojiListMode::RecentReactions), {}, iconFactory, - close) { + close, + child) { } Selector::Selector( not_null parent, - not_null parentController, + std::shared_ptr show, ChatHelpers::EmojiListMode mode, std::vector recent, - Fn close) + Fn close, + bool child) : Selector( parent, - parentController, + std::move(show), { .customAllowed = true }, mode, std::move(recent), nullptr, - close) { + close, + child) { } Selector::Selector( not_null parent, - not_null parentController, + std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, ChatHelpers::EmojiListMode mode, std::vector recent, IconFactory iconFactory, - Fn close) + Fn close, + bool child) : RpWidget(parent) -, _parentController(parentController.get()) +, _show(std::move(show)) , _reactions(reactions) , _recent(std::move(recent)) , _listMode(mode) @@ -167,12 +172,7 @@ Selector::Selector( , _skipy((st::reactStripHeight - st::reactStripSize) / 2) { setMouseTracking(true); - _useTransparency = Ui::Platform::TranslucentWindowsSupported(); - - parentController->content()->alive( - ) | rpl::start_with_done([=] { - close(true); - }, lifetime()); + _useTransparency = child || Ui::Platform::TranslucentWindowsSupported(); } bool Selector::useTransparency() const { @@ -288,6 +288,10 @@ void Selector::beforeDestroy() { } } +rpl::producer<> Selector::escapes() const { + return _escapes.events(); +} + void Selector::updateShowState( float64 progress, float64 opacity, @@ -424,6 +428,7 @@ void Selector::paintExpanding(Painter &p, float64 progress) { p, rects.list.marginsRemoved(st::reactPanelEmojiPan.margin), rects.finalBottom, + rects.expanding, progress, RectPart::TopRight); paintFadingExpandIcon(p, progress); @@ -479,6 +484,7 @@ auto Selector::paintExpandingBg(QPainter &p, float64 progress) .categories = QRect(inner.x(), inner.y(), inner.width(), categories), .list = inner.marginsRemoved({ 0, categories, 0, 0 }), .radius = radius, + .expanding = expanding, .finalBottom = height() - extents.bottom(), }; } @@ -538,10 +544,7 @@ void Selector::finishExpand() { } _scroll->show(); _list->afterShown(); - - if (const auto controller = _parentController.get()) { - controller->session().api().updateCustomEmoji(); - } + _show->session().api().updateCustomEmoji(); } void Selector::paintBubble(QPainter &p, int innerWidth) { @@ -662,6 +665,7 @@ void Selector::expand() { return; } _expandScheduled = true; + _willExpand.fire({}); const auto parent = parentWidget()->geometry(); const auto extents = extentsForShadow(); const auto heightLimit = _reactions.customAllowed @@ -672,15 +676,14 @@ void Selector::expand() { extents.top() + heightLimit + extents.bottom()); const auto additionalBottom = willBeHeight - height(); const auto additional = _specialExpandTopSkip + additionalBottom; - const auto strong = _parentController.get(); - if (additionalBottom < 0 || additional <= 0 || !strong) { + if (additionalBottom < 0 || additional <= 0) { return; } else if (additionalBottom > 0) { resize(width(), height() + additionalBottom); raise(); } - createList(strong); + createList(); cacheExpandIcon(); [[maybe_unused]] const auto grabbed = Ui::GrabWidget(_scroll); @@ -705,7 +708,7 @@ void Selector::cacheExpandIcon() { _strip->paintOne(q, _strip->count() - 1, { 0, 0 }, 1.); } -void Selector::createList(not_null controller) { +void Selector::createList() { using namespace ChatHelpers; auto recent = _recent; auto defaultReactionIds = base::flat_map(); @@ -725,7 +728,7 @@ void Selector::createList(not_null controller) { } }; } - const auto manager = &controller->session().data().customEmojiManager(); + const auto manager = &_show->session().data().customEmojiManager(); _stripPaintOneShift = [&] { // See EmojiListWidget custom emoji position resolving. const auto area = st::emojiPanArea; @@ -777,7 +780,7 @@ void Selector::createList(not_null controller) { } _list = _scroll->setOwnedWidget( object_ptr(_scroll, EmojiListDescriptor{ - .show = controller->uiShow(), + .show = _show, .mode = _listMode, .paused = [] { return false; }, .customRecentList = std::move(recent), @@ -786,6 +789,8 @@ void Selector::createList(not_null controller) { }) ).data(); + _list->escapes() | rpl::start_to_stream(_escapes, _list->lifetime()); + _list->customChosen( ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { const auto id = DocumentId{ data.document->id }; @@ -937,10 +942,11 @@ AttachSelectorResult MakeJustSelectorMenu( Fn chosen) { const auto selector = Ui::CreateChild( menu.get(), - controller, + controller->uiShow(), mode, std::move(recent), - [=](bool fast) { menu->hideMenu(fast); }); + [=](bool fast) { menu->hideMenu(fast); }, + false); // child if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { return AttachSelectorResult::Failed; } @@ -1011,10 +1017,11 @@ AttachSelectorResult AttachSelectorToMenu( const auto withSearch = reactions.customAllowed; const auto selector = Ui::CreateChild( menu.get(), - controller, + controller->uiShow(), std::move(reactions), std::move(iconFactory), - [=](bool fast) { menu->hideMenu(fast); }); + [=](bool fast) { menu->hideMenu(fast); }, + false); // child if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) { return AttachSelectorResult::Failed; } diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h index 6e6d119cd..5cf7ca294 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.h @@ -19,6 +19,7 @@ struct ReactionId; } // namespace Data namespace ChatHelpers { +class Show; class TabbedPanel; class EmojiListWidget; class StickersListFooter; @@ -41,16 +42,18 @@ class Selector final : public Ui::RpWidget { public: Selector( not_null parent, - not_null parentController, + std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, IconFactory iconFactory, - Fn close); + Fn close, + bool child = false); Selector( not_null parent, - not_null parentController, + std::shared_ptr show, ChatHelpers::EmojiListMode mode, std::vector recent, - Fn close); + Fn close, + bool child = false); [[nodiscard]] bool useTransparency() const; @@ -68,6 +71,10 @@ public: [[nodiscard]] rpl::producer<> premiumPromoChosen() const { return _premiumPromoChosen.events(); } + [[nodiscard]] rpl::producer<> willExpand() const { + return _willExpand.events(); + } + [[nodiscard]] rpl::producer<> escapes() const; void updateShowState( float64 progress, @@ -82,17 +89,19 @@ private: QRect categories; QRect list; float64 radius = 0.; + float64 expanding = 0.; int finalBottom = 0; }; Selector( not_null parent, - not_null parentController, + std::shared_ptr show, const Data::PossibleItemReactionsRef &reactions, ChatHelpers::EmojiListMode mode, std::vector recent, IconFactory iconFactory, - Fn close); + Fn close, + bool child); void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; @@ -116,11 +125,11 @@ private: void expand(); void cacheExpandIcon(); - void createList(not_null controller); + void createList(); void finishExpand(); ChosenReaction lookupChosen(const Data::ReactionId &id) const; - const base::weak_ptr _parentController; + const std::shared_ptr _show; const Data::PossibleItemReactions _reactions; const std::vector _recent; const ChatHelpers::EmojiListMode _listMode; @@ -133,6 +142,8 @@ private: rpl::event_stream _chosen; rpl::event_stream<> _premiumPromoChosen; + rpl::event_stream<> _willExpand; + rpl::event_stream<> _escapes; Ui::ScrollArea *_scroll = nullptr; ChatHelpers::EmojiListWidget *_list = nullptr; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 76f04f871..6ea4a3f82 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_header.h" #include "media/stories/media_stories_sibling.h" #include "media/stories/media_stories_slider.h" +#include "media/stories/media_stories_reactions.h" #include "media/stories/media_stories_recent_views.h" #include "media/stories/media_stories_reply.h" #include "media/stories/media_stories_view.h" @@ -125,10 +126,17 @@ Controller::Controller(not_null delegate) , _header(std::make_unique
(this)) , _slider(std::make_unique(this)) , _replyArea(std::make_unique(this)) +, _reactions(std::make_unique(this)) , _recentViews(std::make_unique(this)) { initLayout(); - _replyArea->activeValue( + using namespace rpl::mappers; + + rpl::combine( + _replyArea->activeValue(), + _reactions->expandedValue(), + _1 || _2 + ) | rpl::distinct_until_changed( ) | rpl::start_with_next([=](bool active) { if (active) { _captionFullView = nullptr; @@ -140,6 +148,23 @@ Controller::Controller(not_null delegate) _replyArea->focusedValue( ) | rpl::start_with_next([=](bool focused) { _replyFocused = focused; + if (!_replyFocused) { + _reactions->hideIfCollapsed(); + } else if (!_hasSendText) { + _reactions->show(); + } + }, _lifetime); + + _replyArea->hasSendTextValue( + ) | rpl::start_with_next([=](bool has) { + _hasSendText = has; + if (_replyFocused) { + if (_hasSendText) { + _reactions->hide(); + } else { + _reactions->show(); + } + } }, _lifetime); _delegate->storiesLayerShown( @@ -165,6 +190,9 @@ Controller::Controller(not_null delegate) Controller::~Controller() = default; void Controller::updateContentFaded() { + if (_contentFaded == _replyActive) { + return; + } _contentFaded = _replyActive; _contentFadeAnimation.start( [=] { _delegate->storiesRepaint(); }, @@ -227,6 +255,13 @@ void Controller::initLayout() { contentWidth, contentHeight); + const auto reactionsWidth = st::storiesReactionsWidth; + layout.reactions = QRect( + (size.width() - reactionsWidth) / 2, + layout.content.y(), + reactionsWidth, + contentHeight); + if (layout.headerLayout == HeaderLayout::Outside) { layout.header = QRect( layout.content.topLeft() - QPoint(0, outsideHeaderHeight), @@ -385,6 +420,11 @@ auto Controller::stickerOrEmojiChosen() const return _delegate->storiesStickerOrEmojiChosen(); } +auto Controller::cachedReactionIconFactory() const +-> HistoryView::Reactions::CachedIconFactory & { + return _delegate->storiesCachedReactionIconFactory(); +} + void Controller::show( not_null story, Data::StoriesContext context) { @@ -448,6 +488,7 @@ void Controller::show( _captionText = story->caption(); _captionFullView = nullptr; invalidate_weak_ptrs(&_viewsLoadGuard); + _reactions->hide(); if (_replyFocused) { unfocusReply(); } @@ -693,6 +734,13 @@ void Controller::togglePaused(bool paused) { } } +void Controller::contentPressed(bool pressed) { + togglePaused(pressed); + if (pressed) { + _reactions->collapse(); + } +} + void Controller::setMenuShown(bool shown) { if (_menuShown != shown) { _menuShown = shown; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 4fd02ac39..f59cb0f36 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -23,6 +23,10 @@ namespace Data { struct FileOrigin; } // namespace Data +namespace HistoryView::Reactions { +class CachedIconFactory; +} // namespace HistoryView::Reactions + namespace Ui { class RpWidget; } // namespace Ui @@ -40,6 +44,7 @@ namespace Media::Stories { class Header; class Slider; class ReplyArea; +class Reactions; class RecentViews; class Sibling; class Delegate; @@ -66,6 +71,7 @@ struct Layout { QRect content; QRect header; QRect slider; + QRect reactions; int controlsWidth = 0; QPoint controlsBottomPosition; QRect views; @@ -98,6 +104,8 @@ public: [[nodiscard]] std::shared_ptr uiShow() const; [[nodiscard]] auto stickerOrEmojiChosen() const -> rpl::producer; + [[nodiscard]] auto cachedReactionIconFactory() const + -> HistoryView::Reactions::CachedIconFactory &; void show(not_null story, Data::StoriesContext context); void ready(); @@ -109,6 +117,7 @@ public: [[nodiscard]] bool jumpFor(int delta); [[nodiscard]] bool paused() const; void togglePaused(bool paused); + void contentPressed(bool pressed); void setMenuShown(bool shown); [[nodiscard]] bool canDownload() const; @@ -163,6 +172,7 @@ private: const std::unique_ptr
_header; const std::unique_ptr _slider; const std::unique_ptr _replyArea; + const std::unique_ptr _reactions; const std::unique_ptr _recentViews; std::unique_ptr _photoPlayback; std::unique_ptr _captionFullView; @@ -173,6 +183,7 @@ private: bool _windowActive = false; bool _replyFocused = false; bool _replyActive = false; + bool _hasSendText = false; bool _layerShown = false; bool _menuShown = false; bool _paused = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_delegate.h b/Telegram/SourceFiles/media/stories/media_stories_delegate.h index 9f8d7ce4d..6dfbf3acd 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_delegate.h +++ b/Telegram/SourceFiles/media/stories/media_stories_delegate.h @@ -16,6 +16,10 @@ namespace Data { struct StoriesContext; } // namespace Data +namespace HistoryView::Reactions { +class CachedIconFactory; +} // namespace HistoryView::Reactions + namespace Main { class Session; } // namespace Main @@ -43,6 +47,8 @@ public: -> std::shared_ptr = 0; [[nodiscard]] virtual auto storiesStickerOrEmojiChosen() -> rpl::producer = 0; + [[nodiscard]] virtual auto storiesCachedReactionIconFactory() + -> HistoryView::Reactions::CachedIconFactory & = 0; virtual void storiesJumpTo( not_null session, FullStoryId id, diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp new file mode 100644 index 000000000..446c8f91b --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -0,0 +1,239 @@ +/* +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 "media/stories/media_stories_reactions.h" + +#include "boxes/premium_preview_box.h" +#include "chat_helpers/compose/compose_show.h" +#include "data/data_message_reactions.h" +#include "data/data_session.h" +#include "history/view/reactions/history_view_reactions_selector.h" +#include "main/main_session.h" +#include "media/stories/media_stories_controller.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_media_view.h" +#include "styles/style_widgets.h" + +namespace Media::Stories { +namespace { + +[[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleReactions( + not_null session) { + auto result = Data::PossibleItemReactionsRef(); + const auto reactions = &session->data().reactions(); + const auto &full = reactions->list(Data::Reactions::Type::Active); + const auto &top = reactions->list(Data::Reactions::Type::Top); + const auto &recent = reactions->list(Data::Reactions::Type::Recent); + const auto premiumPossible = session->premiumPossible(); + auto added = base::flat_set(); + result.recent.reserve(full.size()); + for (const auto &reaction : ranges::views::concat(top, recent, full)) { + if (premiumPossible || !reaction.id.custom()) { + if (added.emplace(reaction.id).second) { + result.recent.push_back(&reaction); + } + } + } + result.customAllowed = premiumPossible; + const auto i = ranges::find( + result.recent, + reactions->favoriteId(), + &Data::Reaction::id); + if (i != end(result.recent) && i != begin(result.recent)) { + std::rotate(begin(result.recent), i, i + 1); + } + return result; +} + +} // namespace + +struct Reactions::Hiding { + explicit Hiding(not_null parent) : widget(parent) { + } + + Ui::RpWidget widget; + Ui::Animations::Simple animation; + QImage frame; +}; + +Reactions::Reactions(not_null controller) +: _controller(controller) { +} + +Reactions::~Reactions() = default; + +void Reactions::show() { + if (_shown) { + return; + } + create(); + if (!_selector) { + return; + } + const auto duration = st::defaultPanelAnimation.heightDuration + * st::defaultPopupMenu.showDuration; + _shown = true; + _showing.start([=] { updateShowState(); }, 0., 1., duration); + updateShowState(); + _parent->show(); +} + +void Reactions::hide() { + if (!_selector) { + return; + } + _selector->beforeDestroy(); + if (!anim::Disabled()) { + fadeOutSelector(); + } + _shown = false; + _expanded = false; + _showing.stop(); + _selector = nullptr; + _parent = nullptr; +} + +void Reactions::hideIfCollapsed() { + if (!_expanded.current()) { + hide(); + } +} + +void Reactions::collapse() { + if (_expanded.current()) { + hide(); + show(); + } +} + +void Reactions::create() { + auto reactions = LookupPossibleReactions( + &_controller->uiShow()->session()); + if (reactions.recent.empty() && !reactions.morePremiumAvailable) { + return; + } + _parent = std::make_unique(_controller->wrap().get()); + _parent->show(); + + _parent->events() | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseButtonPress) { + const auto event = static_cast(e.get()); + if (event->button() == Qt::LeftButton) { + if (!_selector + || !_selector->geometry().contains(event->pos())) { + collapse(); + } + } + } + }, _parent->lifetime()); + + const auto withSearch = reactions.customAllowed; + _selector = std::make_unique( + _parent.get(), + _controller->uiShow(), + std::move(reactions), + _controller->cachedReactionIconFactory().createMethod(), + [=](bool fast) { hide(); }); + + _selector->chosen( + ) | rpl::start_with_next([=]( + HistoryView::Reactions::ChosenReaction reaction) { + hide(); + //reaction.context = itemId; + //chosen(std::move(reaction)); + }, _selector->lifetime()); + + _selector->premiumPromoChosen() | rpl::start_with_next([=] { + hide(); + ShowPremiumPreviewBox( + _controller->uiShow(), + PremiumPreview::InfiniteReactions); + }, _selector->lifetime()); + + const auto desiredWidth = st::storiesReactionsWidth; + const auto maxWidth = desiredWidth * 2; + const auto width = _selector->countWidth(desiredWidth, maxWidth); + const auto extents = _selector->extentsForShadow(); + const auto categoriesTop = _selector->extendTopForCategories(); + const auto full = extents.left() + width + extents.right(); + + _shownValue = 0.; + rpl::combine( + _controller->layoutValue(), + _shownValue.value() + ) | rpl::start_with_next([=](const Layout &layout, float64 shown) { + const auto shift = int(base::SafeRound((full / 2.) * shown)); + _parent->setGeometry(QRect( + layout.reactions.x() + layout.reactions.width() / 2 - shift, + layout.reactions.y(), + full, + layout.reactions.height())); + const auto innerTop = layout.reactions.height() + - st::storiesReactionsBottomSkip + - st::reactStripHeight; + const auto maxAdded = innerTop - extents.top() - categoriesTop; + const auto added = std::min(maxAdded, st::storiesReactionsAddedTop); + _selector->setSpecialExpandTopSkip(added); + _selector->initGeometry(innerTop); + }, _selector->lifetime()); + + _selector->willExpand( + ) | rpl::start_with_next([=] { + _expanded = true; + }, _selector->lifetime()); + + _selector->escapes() | rpl::start_with_next([=] { + collapse(); + }, _selector->lifetime()); +} + +void Reactions::fadeOutSelector() { + const auto wrap = _controller->wrap().get(); + const auto geometry = Ui::MapFrom( + wrap, + _parent.get(), + _selector->geometry()); + _hiding.push_back(std::make_unique(wrap)); + const auto raw = _hiding.back().get(); + raw->frame = Ui::GrabWidgetToImage(_selector.get()); + raw->widget.setGeometry(geometry); + raw->widget.show(); + raw->widget.paintRequest( + ) | rpl::start_with_next([=] { + if (const auto opacity = raw->animation.value(0.)) { + auto p = QPainter(&raw->widget); + p.setOpacity(opacity); + p.drawImage(0, 0, raw->frame); + } + }, raw->widget.lifetime()); + Ui::PostponeCall(&raw->widget, [=] { + raw->animation.start([=] { + if (raw->animation.animating()) { + raw->widget.update(); + } else { + const auto i = ranges::find( + _hiding, + raw, + &std::unique_ptr::get); + if (i != end(_hiding)) { + _hiding.erase(i); + } + } + }, 1., 0., st::slideWrapDuration); + }); +} + +void Reactions::updateShowState() { + const auto progress = _showing.value(_shown ? 1. : 0.); + const auto opacity = 1.; + const auto appearing = _showing.animating(); + const auto toggling = false; + _shownValue = progress; + _selector->updateShowState(progress, opacity, appearing, toggling); +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h new file mode 100644 index 000000000..e9a89a09c --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -0,0 +1,57 @@ +/* +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 "ui/effects/animations.h" + +namespace HistoryView::Reactions { +class Selector; +} // namespace HistoryView::Reactions + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Media::Stories { + +class Controller; + +class Reactions final { +public: + explicit Reactions(not_null controller); + ~Reactions(); + + [[nodiscard]] rpl::producer expandedValue() const { + return _expanded.value(); + } + + void show(); + void hide(); + void hideIfCollapsed(); + void collapse(); + +private: + struct Hiding; + + void create(); + void updateShowState(); + void fadeOutSelector(); + + const not_null _controller; + + std::unique_ptr _parent; + std::unique_ptr _selector; + std::vector> _hiding; + Ui::Animations::Simple _showing; + rpl::variable _shownValue; + rpl::variable _expanded; + bool _shown = false; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index cbff23d73..db914d816 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -579,6 +579,10 @@ rpl::producer ReplyArea::focusedValue() const { return _controls->focusedValue(); } +rpl::producer ReplyArea::hasSendTextValue() const { + return _controls->hasSendTextValue(); +} + rpl::producer ReplyArea::activeValue() const { using namespace rpl::mappers; return rpl::combine( diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index d50496d5d..30d15cb28 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -59,6 +59,7 @@ public: [[nodiscard]] rpl::producer focusedValue() const; [[nodiscard]] rpl::producer activeValue() const; + [[nodiscard]] rpl::producer hasSendTextValue() const; private: using VoiceToSend = HistoryView::Controls::VoiceToSend; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index de254b870..47f967160 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -75,6 +75,10 @@ void View::togglePaused(bool paused) { _controller->togglePaused(paused); } +void View::contentPressed(bool pressed) { + _controller->contentPressed(pressed); +} + SiblingView View::sibling(SiblingType type) const { return _controller->sibling(type); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index b25011de5..40ee13c91 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -73,6 +73,7 @@ public: [[nodiscard]] bool paused() const; void togglePaused(bool paused); + void contentPressed(bool pressed); [[nodiscard]] rpl::lifetime &lifetime(); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index b1674e41b..cd88a570f 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -747,3 +747,6 @@ storiesWhoViewed: WhoRead(defaultWhoRead) { align: align(left); } } +storiesReactionsWidth: 210px; +storiesReactionsBottomSkip: 29px; +storiesReactionsAddedTop: 200px; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 1d1880c83..8aa3612c6 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -54,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/view/media/history_view_media.h" +#include "history/view/reactions/history_view_reactions_strip.h" #include "data/data_media_types.h" #include "data/data_session.h" #include "data/data_stories.h" @@ -421,6 +422,7 @@ OverlayWidget::OverlayWidget() , _widget(_surface->rpWidget()) , _fullscreen(Core::App().settings().mediaViewPosition().maximized == 2) , _windowed(Core::App().settings().mediaViewPosition().maximized == 0) +, _cachedReactionIconFactory(std::make_unique()) , _layerBg(std::make_unique(_body)) , _docDownload(_body, tr::lng_media_download(tr::now), st::mediaviewFileLink) , _docSaveAs(_body, tr::lng_mediaview_save_as(tr::now), st::mediaviewFileLink) @@ -1600,9 +1602,11 @@ void OverlayWidget::waitingAnimationCallback() { } void OverlayWidget::updateCursor() { - setCursor(_controlsState == ControlsHidden + setCursor((_controlsState == ControlsHidden) ? Qt::BlankCursor - : (_over == OverNone ? style::cur_default : style::cur_pointer)); + : (_over == OverNone || (_over == OverVideo && _stories)) + ? style::cur_default + : style::cur_pointer); } int OverlayWidget::finalContentRotation() const { @@ -4045,6 +4049,11 @@ auto OverlayWidget::storiesStickerOrEmojiChosen() return _storiesStickerOrEmojiChosen.events(); } +auto OverlayWidget::storiesCachedReactionIconFactory() +-> HistoryView::Reactions::CachedIconFactory & { + return *_cachedReactionIconFactory; +} + void OverlayWidget::storiesJumpTo( not_null session, FullStoryId id, @@ -5192,7 +5201,7 @@ void OverlayWidget::handleMousePress( || _over == OverVideo) { _down = _over; if (_over == OverVideo && _stories) { - _stories->togglePaused(true); + _stories->contentPressed(true); } } else if (!_saveMsg.contains(position) || !isSaveMsgShown()) { _pressed = true; @@ -5491,7 +5500,7 @@ void OverlayWidget::handleMouseRelease( InvokeQueued(_widget, [=] { showDropdown(); }); } else if (_over == OverVideo && _down == OverVideo) { if (_stories) { - _stories->togglePaused(false); + _stories->contentPressed(false); } else if (_streamed) { playbackPauseResume(); } diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 2085fef99..01cc71f3d 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -51,11 +51,13 @@ namespace Platform { class OverlayWidgetHelper; } // namespace Platform -namespace Window { -namespace Theme { +namespace Window::Theme { struct Preview; -} // namespace Theme -} // namespace Window +} // namespace Window::Theme + +namespace HistoryView::Reactions { +class CachedIconFactory; +} // namespace HistoryView::Reactions namespace Media::Player { struct TrackState; @@ -245,6 +247,8 @@ private: std::shared_ptr storiesShow() override; auto storiesStickerOrEmojiChosen() -> rpl::producer override; + auto storiesCachedReactionIconFactory() + -> HistoryView::Reactions::CachedIconFactory & override; void storiesJumpTo( not_null session, FullStoryId id, @@ -600,6 +604,8 @@ private: bool _showAsPip = false; std::unique_ptr _stories; + using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory; + std::unique_ptr _cachedReactionIconFactory; std::shared_ptr _cachedShow; rpl::event_stream<> _storiesChanged; Main::Session *_storiesSession = nullptr; diff --git a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp index a4bf604a5..a4022d514 100644 --- a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp +++ b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/controls/tabbed_search.h" +#include "base/qt_signal_producer.h" #include "lang/lang_keys.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/fade_wrap.h" @@ -517,6 +518,12 @@ void SearchWithGroups::ensureRounding(int size, float64 ratio) { _rounding.setDevicePixelRatio(ratio); } +rpl::producer<> SearchWithGroups::escapes() const { + return base::qt_signal_producer( + _field.get(), + &Ui::InputField::cancelled); +} + rpl::producer> SearchWithGroups::queryValue() const { return _query.value(); } @@ -659,6 +666,10 @@ void TabbedSearch::returnFocus() { _search.returnFocus(); } +rpl::producer<> TabbedSearch::escapes() const { + return _search.escapes(); +} + rpl::producer> TabbedSearch::queryValue() const { return _search.queryValue(); } diff --git a/Telegram/SourceFiles/ui/controls/tabbed_search.h b/Telegram/SourceFiles/ui/controls/tabbed_search.h index ce5bb3e77..000cbccea 100644 --- a/Telegram/SourceFiles/ui/controls/tabbed_search.h +++ b/Telegram/SourceFiles/ui/controls/tabbed_search.h @@ -50,6 +50,7 @@ class SearchWithGroups final : public RpWidget { public: SearchWithGroups(QWidget *parent, SearchDescriptor descriptor); + [[nodiscard]] rpl::producer<> escapes() const; [[nodiscard]] rpl::producer> queryValue() const; [[nodiscard]] auto debouncedQueryValue() const -> rpl::producer>; @@ -116,6 +117,7 @@ public: [[nodiscard]] int height() const; [[nodiscard]] QImage grab(); + [[nodiscard]] rpl::producer<> escapes() const; [[nodiscard]] rpl::producer> queryValue() const; [[nodiscard]] auto debouncedQueryValue() const ->rpl::producer>;