Show / expand / collapse / hide reactions strip.

This commit is contained in:
John Preston 2023-06-08 19:54:21 +04:00
parent c1be4d6451
commit 0ed200beee
22 changed files with 490 additions and 54 deletions

View File

@ -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

View File

@ -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);

View File

@ -125,6 +125,7 @@ public:
[[nodiscard]] rpl::producer<EmojiChosen> chosen() const;
[[nodiscard]] rpl::producer<FileChosen> customChosen() const;
[[nodiscard]] rpl::producer<> jumpedToPremium() const;
[[nodiscard]] rpl::producer<> escapes() const;
void provideRecent(const std::vector<DocumentId> &customRecentList);
@ -133,7 +134,8 @@ public:
Painter &p,
QRect clip,
int finalBottom,
float64 progress,
float64 geometryProgress,
float64 fullProgress,
RectPart origin);
base::unique_qptr<Ui::PopupMenu> fillContextMenu(

View File

@ -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;

View File

@ -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<bool> ComposeControls::recordingValue() const {
return _recording.value();
}
rpl::producer<bool> ComposeControls::hasSendTextValue() const {
return _hasSendText.value();
}
bool ComposeControls::preventsClose(Fn<void()> &&continueCallback) const {
if (_voiceRecordBar->isActive()) {
_voiceRecordBar->showDiscardBox(std::move(continueCallback));

View File

@ -215,6 +215,7 @@ public:
[[nodiscard]] bool isLockPresent() const;
[[nodiscard]] bool isRecording() const;
[[nodiscard]] rpl::producer<bool> recordingValue() const;
[[nodiscard]] rpl::producer<bool> hasSendTextValue() const;
void applyCloudDraft();
void applyDraft(
@ -382,6 +383,7 @@ private:
rpl::event_stream<ReplyNextRequest> _replyNextRequests;
rpl::event_stream<> _focusRequests;
rpl::variable<bool> _recording;
rpl::variable<bool> _hasSendText;
TextUpdateEvents _textUpdateEvents = TextUpdateEvents()
| TextUpdateEvent::SaveDraft

View File

@ -105,48 +105,53 @@ bool StripEmoji::readyInDefaultState() {
Selector::Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
std::shared_ptr<ChatHelpers::Show> show,
const Data::PossibleItemReactionsRef &reactions,
IconFactory iconFactory,
Fn<void(bool fast)> close)
Fn<void(bool fast)> 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<QWidget*> parent,
not_null<Window::SessionController*> parentController,
std::shared_ptr<ChatHelpers::Show> show,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
Fn<void(bool fast)> close)
Fn<void(bool fast)> close,
bool child)
: Selector(
parent,
parentController,
std::move(show),
{ .customAllowed = true },
mode,
std::move(recent),
nullptr,
close) {
close,
child) {
}
Selector::Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
std::shared_ptr<ChatHelpers::Show> show,
const Data::PossibleItemReactionsRef &reactions,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
IconFactory iconFactory,
Fn<void(bool fast)> close)
Fn<void(bool fast)> 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<Window::SessionController*> controller) {
void Selector::createList() {
using namespace ChatHelpers;
auto recent = _recent;
auto defaultReactionIds = base::flat_map<DocumentId, QString>();
@ -725,7 +728,7 @@ void Selector::createList(not_null<Window::SessionController*> 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<Window::SessionController*> controller) {
}
_list = _scroll->setOwnedWidget(
object_ptr<EmojiListWidget>(_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<Window::SessionController*> 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<void(ChosenReaction)> chosen) {
const auto selector = Ui::CreateChild<Selector>(
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<Selector>(
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;
}

View File

@ -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<QWidget*> parent,
not_null<Window::SessionController*> parentController,
std::shared_ptr<ChatHelpers::Show> show,
const Data::PossibleItemReactionsRef &reactions,
IconFactory iconFactory,
Fn<void(bool fast)> close);
Fn<void(bool fast)> close,
bool child = false);
Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
std::shared_ptr<ChatHelpers::Show> show,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
Fn<void(bool fast)> close);
Fn<void(bool fast)> 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<QWidget*> parent,
not_null<Window::SessionController*> parentController,
std::shared_ptr<ChatHelpers::Show> show,
const Data::PossibleItemReactionsRef &reactions,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
IconFactory iconFactory,
Fn<void(bool fast)> close);
Fn<void(bool fast)> 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<Window::SessionController*> controller);
void createList();
void finishExpand();
ChosenReaction lookupChosen(const Data::ReactionId &id) const;
const base::weak_ptr<Window::SessionController> _parentController;
const std::shared_ptr<ChatHelpers::Show> _show;
const Data::PossibleItemReactions _reactions;
const std::vector<DocumentId> _recent;
const ChatHelpers::EmojiListMode _listMode;
@ -133,6 +142,8 @@ private:
rpl::event_stream<ChosenReaction> _chosen;
rpl::event_stream<> _premiumPromoChosen;
rpl::event_stream<> _willExpand;
rpl::event_stream<> _escapes;
Ui::ScrollArea *_scroll = nullptr;
ChatHelpers::EmojiListWidget *_list = nullptr;

View File

@ -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*> delegate)
, _header(std::make_unique<Header>(this))
, _slider(std::make_unique<Slider>(this))
, _replyArea(std::make_unique<ReplyArea>(this))
, _reactions(std::make_unique<Reactions>(this))
, _recentViews(std::make_unique<RecentViews>(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*> 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*> 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<Data::Story*> 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;

View File

@ -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<ChatHelpers::Show> uiShow() const;
[[nodiscard]] auto stickerOrEmojiChosen() const
-> rpl::producer<ChatHelpers::FileChosen>;
[[nodiscard]] auto cachedReactionIconFactory() const
-> HistoryView::Reactions::CachedIconFactory &;
void show(not_null<Data::Story*> 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> _header;
const std::unique_ptr<Slider> _slider;
const std::unique_ptr<ReplyArea> _replyArea;
const std::unique_ptr<Reactions> _reactions;
const std::unique_ptr<RecentViews> _recentViews;
std::unique_ptr<PhotoPlayback> _photoPlayback;
std::unique_ptr<CaptionFullView> _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;

View File

@ -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<ChatHelpers::Show> = 0;
[[nodiscard]] virtual auto storiesStickerOrEmojiChosen()
-> rpl::producer<ChatHelpers::FileChosen> = 0;
[[nodiscard]] virtual auto storiesCachedReactionIconFactory()
-> HistoryView::Reactions::CachedIconFactory & = 0;
virtual void storiesJumpTo(
not_null<Main::Session*> session,
FullStoryId id,

View File

@ -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<Main::Session*> 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<Data::ReactionId>();
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<QWidget*> parent) : widget(parent) {
}
Ui::RpWidget widget;
Ui::Animations::Simple animation;
QImage frame;
};
Reactions::Reactions(not_null<Controller*> 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<Ui::RpWidget>(_controller->wrap().get());
_parent->show();
_parent->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::MouseButtonPress) {
const auto event = static_cast<QMouseEvent*>(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<HistoryView::Reactions::Selector>(
_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<Hiding>(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<Hiding>::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

View File

@ -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*> controller);
~Reactions();
[[nodiscard]] rpl::producer<bool> 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*> _controller;
std::unique_ptr<Ui::RpWidget> _parent;
std::unique_ptr<HistoryView::Reactions::Selector> _selector;
std::vector<std::unique_ptr<Hiding>> _hiding;
Ui::Animations::Simple _showing;
rpl::variable<float64> _shownValue;
rpl::variable<bool> _expanded;
bool _shown = false;
};
} // namespace Media::Stories

View File

@ -579,6 +579,10 @@ rpl::producer<bool> ReplyArea::focusedValue() const {
return _controls->focusedValue();
}
rpl::producer<bool> ReplyArea::hasSendTextValue() const {
return _controls->hasSendTextValue();
}
rpl::producer<bool> ReplyArea::activeValue() const {
using namespace rpl::mappers;
return rpl::combine(

View File

@ -59,6 +59,7 @@ public:
[[nodiscard]] rpl::producer<bool> focusedValue() const;
[[nodiscard]] rpl::producer<bool> activeValue() const;
[[nodiscard]] rpl::producer<bool> hasSendTextValue() const;
private:
using VoiceToSend = HistoryView::Controls::VoiceToSend;

View File

@ -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);
}

View File

@ -73,6 +73,7 @@ public:
[[nodiscard]] bool paused() const;
void togglePaused(bool paused);
void contentPressed(bool pressed);
[[nodiscard]] rpl::lifetime &lifetime();

View File

@ -747,3 +747,6 @@ storiesWhoViewed: WhoRead(defaultWhoRead) {
align: align(left);
}
}
storiesReactionsWidth: 210px;
storiesReactionsBottomSkip: 29px;
storiesReactionsAddedTop: 200px;

View File

@ -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<ReactionIconFactory>())
, _layerBg(std::make_unique<Ui::LayerManager>(_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<Main::Session*> 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();
}

View File

@ -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<ChatHelpers::Show> storiesShow() override;
auto storiesStickerOrEmojiChosen()
-> rpl::producer<ChatHelpers::FileChosen> override;
auto storiesCachedReactionIconFactory()
-> HistoryView::Reactions::CachedIconFactory & override;
void storiesJumpTo(
not_null<Main::Session*> session,
FullStoryId id,
@ -600,6 +604,8 @@ private:
bool _showAsPip = false;
std::unique_ptr<Stories::View> _stories;
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
std::shared_ptr<Show> _cachedShow;
rpl::event_stream<> _storiesChanged;
Main::Session *_storiesSession = nullptr;

View File

@ -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<std::vector<QString>> 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<std::vector<QString>> TabbedSearch::queryValue() const {
return _search.queryValue();
}

View File

@ -50,6 +50,7 @@ class SearchWithGroups final : public RpWidget {
public:
SearchWithGroups(QWidget *parent, SearchDescriptor descriptor);
[[nodiscard]] rpl::producer<> escapes() const;
[[nodiscard]] rpl::producer<std::vector<QString>> queryValue() const;
[[nodiscard]] auto debouncedQueryValue() const
-> rpl::producer<std::vector<QString>>;
@ -116,6 +117,7 @@ public:
[[nodiscard]] int height() const;
[[nodiscard]] QImage grab();
[[nodiscard]] rpl::producer<> escapes() const;
[[nodiscard]] rpl::producer<std::vector<QString>> queryValue() const;
[[nodiscard]] auto debouncedQueryValue() const
->rpl::producer<std::vector<QString>>;