mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-23 16:56:55 +00:00
Show / expand / collapse / hide reactions strip.
This commit is contained in:
parent
c1be4d6451
commit
0ed200beee
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
239
Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
Normal file
239
Telegram/SourceFiles/media/stories/media_stories_reactions.cpp
Normal 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
|
57
Telegram/SourceFiles/media/stories/media_stories_reactions.h
Normal file
57
Telegram/SourceFiles/media/stories/media_stories_reactions.h
Normal 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
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool paused() const;
|
||||
void togglePaused(bool paused);
|
||||
void contentPressed(bool pressed);
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
|
@ -747,3 +747,6 @@ storiesWhoViewed: WhoRead(defaultWhoRead) {
|
||||
align: align(left);
|
||||
}
|
||||
}
|
||||
storiesReactionsWidth: 210px;
|
||||
storiesReactionsBottomSkip: 29px;
|
||||
storiesReactionsAddedTop: 200px;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>>;
|
||||
|
Loading…
Reference in New Issue
Block a user