From 027bd89e5bf201292c98d0e1859d30ff170f98d7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 5 May 2023 14:40:51 +0400 Subject: [PATCH] Apply geometry constraints in stories viewer. --- Telegram/CMakeLists.txt | 2 + .../history_view_compose_controls.cpp | 58 ++++--- .../controls/history_view_compose_controls.h | 5 +- .../stories/media_stories_controller.cpp | 155 ++++++++++++++++++ .../media/stories/media_stories_controller.h | 91 ++++++++++ .../media/stories/media_stories_header.cpp | 33 ++-- .../media/stories/media_stories_header.h | 8 +- .../media/stories/media_stories_reply.cpp | 144 ++++++++++++++-- .../media/stories/media_stories_reply.h | 36 +++- .../media/stories/media_stories_slider.cpp | 58 ++++++- .../media/stories/media_stories_slider.h | 24 ++- .../media/stories/media_stories_view.cpp | 17 +- .../media/stories/media_stories_view.h | 20 +-- .../SourceFiles/media/view/media_view.style | 39 +++++ .../media/view/media_view_overlay_widget.cpp | 8 + 15 files changed, 607 insertions(+), 91 deletions(-) create mode 100644 Telegram/SourceFiles/media/stories/media_stories_controller.cpp create mode 100644 Telegram/SourceFiles/media/stories/media_stories_controller.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4b8b8b331..1a76befc0 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -962,6 +962,8 @@ PRIVATE media/player/media_player_volume_controller.h media/player/media_player_widget.cpp media/player/media_player_widget.h + media/stories/media_stories_controller.cpp + media/stories/media_stories_controller.h media/stories/media_stories_delegate.cpp media/stories/media_stories_delegate.h media/stories/media_stories_header.cpp 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 75a46de3e..6c0f635e8 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -355,6 +355,7 @@ public: rpl::producer title, rpl::producer description, rpl::producer page); + void previewUnregister(); [[nodiscard]] bool isDisplayed() const; [[nodiscard]] bool isEditingMessage() const; @@ -413,6 +414,7 @@ private: rpl::event_stream<> _replyCancelled; rpl::event_stream<> _forwardCancelled; rpl::event_stream<> _previewCancelled; + rpl::lifetime _previewLifetime; rpl::variable _editMsgId; rpl::variable _replyToId; @@ -677,17 +679,18 @@ void FieldHeader::resolveMessageData() { } void FieldHeader::previewRequested( - rpl::producer title, - rpl::producer description, - rpl::producer page) { + rpl::producer title, + rpl::producer description, + rpl::producer page) { + _previewLifetime.destroy(); std::move( title ) | rpl::filter([=] { return !_preview.cancelled; - }) | start_with_next([=](const QString &t) { + }) | rpl::start_with_next([=](const QString &t) { _title = t; - }, lifetime()); + }, _previewLifetime); std::move( description @@ -695,7 +698,7 @@ void FieldHeader::previewRequested( return !_preview.cancelled; }) | rpl::start_with_next([=](const QString &d) { _description = d; - }, lifetime()); + }, _previewLifetime); std::move( page @@ -704,8 +707,11 @@ void FieldHeader::previewRequested( }) | rpl::start_with_next([=](WebPageData *p) { _preview.data = p; updateVisible(); - }, lifetime()); + }, _previewLifetime); +} +void FieldHeader::previewUnregister() { + _previewLifetime.destroy(); } void FieldHeader::paintWebPage(Painter &p, not_null context) { @@ -996,10 +1002,6 @@ Main::Session &ComposeControls::session() const { } void ComposeControls::setHistory(SetHistoryArgs &&args) { - // Right now only single non-null set of history is supported. - // Otherwise initWebpageProcess should be updated / rewritten. - Expects(!_history && (*args.history)); - _showSlowmodeError = std::move(args.showSlowmodeError); _sendActionFactory = std::move(args.sendActionFactory); _slowmodeSecondsLeft = rpl::single(0) @@ -1014,6 +1016,7 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { } unregisterDraftSources(); _history = history; + _historyLifetime.destroy(); _header->setHistory(args); registerDraftSource(); _selector->setCurrentPeer(history ? history->peer.get() : nullptr); @@ -1025,9 +1028,12 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { updateControlsVisibility(); updateFieldPlaceholder(); updateAttachBotsMenu(); - //if (!_history) { - // return; - //} + + _sendAs = nullptr; + _silent = nullptr; + if (!_history) { + return; + } const auto peer = _history->peer; initSendAsButton(peer); if (peer->isChat() && peer->asChat()->noParticipantInfo()) { @@ -2134,7 +2140,7 @@ void ComposeControls::initSendAsButton(not_null peer) { updateControlsGeometry(_wrap->size()); orderControls(); } - }, _wrap->lifetime()); + }, _historyLifetime); updateSendAsButton(); } @@ -2218,7 +2224,7 @@ void ComposeControls::initVoiceRecordBar() { _voiceRecordBar->setStartRecordingFilter([=] { const auto error = [&]() -> std::optional { const auto peer = _history ? _history->peer.get() : nullptr; - if (!peer) { + if (peer) { if (const auto error = Data::RestrictionError( peer, ChatRestriction::SendVoiceMessages)) { @@ -2448,10 +2454,9 @@ void ComposeControls::updateMessagesTTLShown() { } bool ComposeControls::updateSendAsButton() { - Expects(_history != nullptr); - - const auto peer = _history->peer; - if (!_regularWindow + const auto peer = _history ? _history->peer.get() : nullptr; + if (!peer + || !_regularWindow || isEditingMessage() || !session().sendAsPeers().shouldChoose(peer)) { if (!_sendAs) { @@ -2467,7 +2472,7 @@ bool ComposeControls::updateSendAsButton() { st::sendAsButton); Ui::SetupSendAsButton( _sendAs.get(), - rpl::single(peer.get()), + rpl::single(peer), _regularWindow); return true; } @@ -2781,15 +2786,18 @@ bool ComposeControls::handleCancelRequest() { } void ComposeControls::initWebpageProcess() { - Expects(_history); + if (!_history) { + _preview = nullptr; + _header->previewUnregister(); + return; + } - auto &lifetime = _wrap->lifetime(); _preview = std::make_unique(_history, _field); _preview->paintRequests( ) | rpl::start_with_next(crl::guard(_header.get(), [=] { _header->update(); - }), lifetime); + }), _historyLifetime); session().changes().peerUpdates( Data::PeerUpdate::Flag::Rights @@ -2818,7 +2826,7 @@ void ComposeControls::initWebpageProcess() { updateControlsGeometry(_wrap->size()); } } - }, lifetime); + }, _historyLifetime); _header->previewRequested( _preview->titleChanges(), 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 a24b5940a..2f5b12491 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -389,10 +389,11 @@ private: std::unique_ptr _preview; - rpl::lifetime _uploaderSubscriptions; - Fn _raiseEmojiSuggestions; + rpl::lifetime _historyLifetime; + rpl::lifetime _uploaderSubscriptions; + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp new file mode 100644 index 000000000..3fbbdb1ef --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -0,0 +1,155 @@ +/* +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_controller.h" + +#include "data/data_stories.h" +#include "media/stories/media_stories_delegate.h" +#include "media/stories/media_stories_header.h" +#include "media/stories/media_stories_slider.h" +#include "media/stories/media_stories_reply.h" +#include "ui/rp_widget.h" +#include "styles/style_media_view.h" +#include "styles/style_widgets.h" +#include "styles/style_boxes.h" // UserpicButton + +namespace Media::Stories { + +Controller::Controller(not_null delegate) +: _delegate(delegate) +, _wrap(_delegate->storiesWrap()) +, _header(std::make_unique
(this)) +, _slider(std::make_unique(this)) +, _replyArea(std::make_unique(this)) { + initLayout(); +} + +void Controller::initLayout() { + const auto headerHeight = st::storiesHeaderMargin.top() + + st::storiesHeaderPhoto.photoSize + + st::storiesHeaderMargin.bottom(); + const auto sliderHeight = st::storiesSliderMargin.top() + + st::storiesSlider.width + + st::storiesSliderMargin.bottom(); + const auto outsideHeaderHeight = headerHeight + sliderHeight; + const auto fieldMinHeight = st::storiesFieldMargin.top() + + st::storiesAttach.height + + st::storiesFieldMargin.bottom(); + const auto minHeightForOutsideHeader = st::storiesMaxSize.height() + + outsideHeaderHeight + + fieldMinHeight; + _layout = _wrap->sizeValue( + ) | rpl::map([=](QSize size) { + size = QSize( + std::max(size.width(), st::mediaviewMinWidth), + std::max(size.height(), st::mediaviewMinHeight)); + + auto layout = Layout(); + layout.headerLayout = (size.height() >= minHeightForOutsideHeader) + ? HeaderLayout::Outside + : HeaderLayout::Normal; + + const auto topSkip = (layout.headerLayout == HeaderLayout::Outside) + ? outsideHeaderHeight + : st::storiesFieldMargin.bottom(); + const auto bottomSkip = fieldMinHeight; + const auto maxWidth = size.width() - 2 * st::storiesSideSkip; + const auto availableHeight = size.height() - topSkip - bottomSkip; + const auto maxContentHeight = std::min( + availableHeight, + st::storiesMaxSize.height()); + const auto nowWidth = maxContentHeight * st::storiesMaxSize.width() + / st::storiesMaxSize.height(); + const auto contentWidth = std::min(nowWidth, maxWidth); + const auto contentHeight = (contentWidth < nowWidth) + ? (contentWidth * st::storiesMaxSize.height() + / st::storiesMaxSize.width()) + : maxContentHeight; + const auto addedTopSkip = (availableHeight - contentHeight) / 2; + layout.content = QRect( + (size.width() - contentWidth) / 2, + addedTopSkip + topSkip, + contentWidth, + contentHeight); + + if (layout.headerLayout == HeaderLayout::Outside) { + layout.header = QRect( + layout.content.topLeft() - QPoint(0, outsideHeaderHeight), + QSize(contentWidth, outsideHeaderHeight)); + layout.slider = QRect( + layout.header.topLeft() + QPoint(0, headerHeight), + QSize(contentWidth, sliderHeight)); + } else { + layout.slider = QRect( + layout.content.topLeft(), + QSize(contentWidth, sliderHeight)); + layout.header = QRect( + layout.slider.topLeft() + QPoint(0, sliderHeight), + QSize(contentWidth, headerHeight)); + } + layout.controlsWidth = std::max( + layout.content.width(), + st::storiesControlsMinWidth); + layout.controlsBottomPosition = QPoint( + (size.width() - layout.controlsWidth) / 2, + (layout.content.y() + + layout.content.height() + + fieldMinHeight + - st::storiesFieldMargin.bottom())); + layout.autocompleteRect = QRect( + layout.controlsBottomPosition.x(), + 0, + layout.controlsWidth, + layout.controlsBottomPosition.y()); + + return layout; + }); +} + +not_null Controller::wrap() const { + return _wrap; +} + +Layout Controller::layout() const { + Expects(_layout.current().has_value()); + + return *_layout.current(); +} + +rpl::producer Controller::layoutValue() const { + return _layout.value() | rpl::filter_optional(); +} + +std::shared_ptr Controller::uiShow() const { + return _delegate->storiesShow(); +} + +auto Controller::stickerOrEmojiChosen() const +->rpl::producer { + return _delegate->storiesStickerOrEmojiChosen(); +} + +void Controller::show(const Data::StoriesList &list, int index) { + Expects(index < list.items.size()); + + const auto &item = list.items[index]; + + const auto id = ShownId{ + .user = list.user, + .id = item.id, + }; + if (_shown == id) { + return; + } + _shown = id; + + _header->show({ .user = list.user, .date = item.date }); + _slider->show({ .index = index, .total = int(list.items.size()) }); + _replyArea->show({ .user = list.user }); +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h new file mode 100644 index 000000000..a50ff6c8a --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -0,0 +1,91 @@ +/* +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 + +namespace ChatHelpers { +class Show; +struct FileChosen; +} // namespace ChatHelpers + +namespace Data { +struct StoriesList; +} // namespace Data + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Media::Stories { + +class Header; +class Slider; +class ReplyArea; +class Delegate; + +struct ShownId { + UserData *user = nullptr; + StoryId id = 0; + + explicit operator bool() const { + return user != nullptr && id != 0; + } + friend inline auto operator<=>(ShownId, ShownId) = default; + friend inline bool operator==(ShownId, ShownId) = default; +}; + +enum class HeaderLayout { + Normal, + Outside, +}; + +struct Layout { + QRect content; + QRect header; + QRect slider; + int controlsWidth = 0; + QPoint controlsBottomPosition; + QRect autocompleteRect; + HeaderLayout headerLayout = HeaderLayout::Normal; + + friend inline auto operator<=>(Layout, Layout) = default; + friend inline bool operator==(Layout, Layout) = default; +}; + +class Controller final { +public: + explicit Controller(not_null delegate); + + [[nodiscard]] not_null wrap() const; + [[nodiscard]] Layout layout() const; + [[nodiscard]] rpl::producer layoutValue() const; + + [[nodiscard]] std::shared_ptr uiShow() const; + [[nodiscard]] auto stickerOrEmojiChosen() const + -> rpl::producer; + + void show(const Data::StoriesList &list, int index); + +private: + void initLayout(); + + const not_null _delegate; + + rpl::variable> _layout; + + const not_null _wrap; + const std::unique_ptr
_header; + const std::unique_ptr _slider; + const std::unique_ptr _replyArea; + + ShownId _shown; + + rpl::lifetime _lifetime; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index 08e81fccc..849e07e97 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -9,18 +9,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/unixtime.h" #include "data/data_user.h" -#include "media/stories/media_stories_delegate.h" +#include "media/stories/media_stories_controller.h" #include "ui/controls/userpic_button.h" #include "ui/text/format_values.h" #include "ui/widgets/labels.h" #include "ui/painter.h" #include "ui/rp_widget.h" -#include "styles/style_boxes.h" // defaultUserpicButton. +#include "styles/style_media_view.h" namespace Media::Stories { -Header::Header(not_null delegate) -: _delegate(delegate) { +Header::Header(not_null controller) +: _controller(controller) { } Header::~Header() { @@ -34,32 +34,37 @@ void Header::show(HeaderData data) { _data = data; if (userChanged) { _date = nullptr; - const auto parent = _delegate->storiesWrap(); + const auto parent = _controller->wrap(); auto widget = std::make_unique(parent); const auto raw = widget.get(); - parent->sizeValue() | rpl::start_with_next([=](QSize size) { - raw->setGeometry(50, 50, 600, 100); - }, raw->lifetime()); raw->setAttribute(Qt::WA_TransparentForMouseEvents); const auto userpic = Ui::CreateChild( raw, data.user, - st::defaultUserpicButton); - userpic->move(0, 0); + st::storiesHeaderPhoto); + userpic->show(); + userpic->move( + st::storiesHeaderMargin.left(), + st::storiesHeaderMargin.top()); const auto name = Ui::CreateChild( raw, data.user->firstName, - st::defaultFlatLabel); - name->move(100, 0); + st::storiesHeaderName); + name->move(st::storiesHeaderNamePosition); raw->show(); _widget = std::move(widget); + + _controller->layoutValue( + ) | rpl::start_with_next([=](const Layout &layout) { + raw->setGeometry(layout.header); + }, raw->lifetime()); } _date = std::make_unique( _widget.get(), Ui::FormatDateTime(base::unixtime::parse(data.date)), - st::defaultFlatLabel); - _date->move(100, 50); + st::storiesHeaderDate); _date->show(); + _date->move(st::storiesHeaderDatePosition); } } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h index 8df09a64f..3f0be2e5c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.h +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -16,24 +16,26 @@ class FlatLabel; namespace Media::Stories { -class Delegate; +class Controller; struct HeaderData { not_null user; TimeId date = 0; friend inline auto operator<=>(HeaderData, HeaderData) = default; + friend inline bool operator==(HeaderData, HeaderData) = default; }; class Header final { public: - explicit Header(not_null delegate); + explicit Header(not_null controller); ~Header(); void show(HeaderData data); private: - const not_null _delegate; + const not_null _controller; + std::unique_ptr _widget; std::unique_ptr _date; std::optional _data; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index cf2226bd1..e4a24807c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -7,42 +7,156 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_reply.h" +#include "base/call_delayed.h" #include "chat_helpers/compose/compose_show.h" #include "chat_helpers/tabbed_selector.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "history/view/controls/compose_controls_common.h" #include "history/view/controls/history_view_compose_controls.h" -#include "media/stories/media_stories_delegate.h" +#include "inline_bots/inline_bot_result.h" +#include "media/stories/media_stories_controller.h" #include "menu/menu_send.h" +#include "styles/style_media_view.h" namespace Media::Stories { -ReplyArea::ReplyArea(not_null delegate) -: _delegate(delegate) +ReplyArea::ReplyArea(not_null controller) +: _controller(controller) , _controls(std::make_unique( - _delegate->storiesWrap(), + _controller->wrap(), HistoryView::ComposeControlsDescriptor{ - .show = _delegate->storiesShow(), + .show = _controller->uiShow(), .unavailableEmojiPasted = [=](not_null emoji) { showPremiumToast(emoji); }, .mode = HistoryView::ComposeControlsMode::Normal, .sendMenuType = SendMenu::Type::SilentOnly, - .stickerOrEmojiChosen = _delegate->storiesStickerOrEmojiChosen(), + .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), } )) { - _delegate->storiesWrap()->sizeValue( - ) | rpl::start_with_next([=](QSize size) { - _controls->resizeToWidth(size.width() - 200); - _controls->move(100, size.height() - _controls->heightCurrent() - 20); - _controls->setAutocompleteBoundingRect({ QPoint() ,size }); - }, _lifetime); - - _controls->show(); - _controls->showFinished(); + initGeometry(); + initActions(); } ReplyArea::~ReplyArea() { } +void ReplyArea::initGeometry() { + _controller->layoutValue( + ) | rpl::start_with_next([=](const Layout &layout) { + _controls->resizeToWidth(layout.content.width()); + const auto position = layout.controlsBottomPosition + - QPoint(0, _controls->heightCurrent()); + _controls->move(position.x(), position.y()); + _controls->setAutocompleteBoundingRect(layout.autocompleteRect); + }, _lifetime); +} + +void ReplyArea::send(Api::SendOptions options) { + // #TODO stories +} + +void ReplyArea::sendVoice(VoiceToSend &&data) { + // #TODO stories +} + +void ReplyArea::chooseAttach(std::optional overrideCompress) { + // #TODO stories +} + +void ReplyArea::initActions() { + _controls->cancelRequests( + ) | rpl::start_with_next([=] { + // #TODO stories + }, _lifetime); + + _controls->sendRequests( + ) | rpl::start_with_next([=](Api::SendOptions options) { + send(options); + }, _lifetime); + + _controls->sendVoiceRequests( + ) | rpl::start_with_next([=](VoiceToSend &&data) { + sendVoice(std::move(data)); + }, _lifetime); + + _controls->attachRequests( + ) | rpl::filter([=] { + return !_choosingAttach; + }) | rpl::start_with_next([=](std::optional overrideCompress) { + _choosingAttach = true; + base::call_delayed( + st::storiesAttach.ripple.hideDuration, + this, + [=] { chooseAttach(overrideCompress); }); + }, _lifetime); + + _controls->fileChosen( + ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) { + _controller->uiShow()->hideLayer(); + //controller()->sendingAnimation().appendSending( + // data.messageSendingFrom); + //const auto localId = data.messageSendingFrom.localId; + //sendExistingDocument(data.document, data.options, localId); + }, _lifetime); + + _controls->photoChosen( + ) | rpl::start_with_next([=](ChatHelpers::PhotoChosen chosen) { + //sendExistingPhoto(chosen.photo, chosen.options); + }, _lifetime); + + _controls->inlineResultChosen( + ) | rpl::start_with_next([=](ChatHelpers::InlineChosen chosen) { + //controller()->sendingAnimation().appendSending( + // chosen.messageSendingFrom); + //const auto localId = chosen.messageSendingFrom.localId; + //sendInlineResult(chosen.result, chosen.bot, chosen.options, localId); + }, _lifetime); + + _controls->setMimeDataHook([=]( + not_null data, + Ui::InputField::MimeAction action) { + if (action == Ui::InputField::MimeAction::Check) { + return false;// checkSendingFiles(data); + } else if (action == Ui::InputField::MimeAction::Insert) { + return false;/* confirmSendingFiles( + data, + std::nullopt, + Core::ReadMimeText(data));*/ + } + Unexpected("action in MimeData hook."); + }); + + _controls->lockShowStarts( + ) | rpl::start_with_next([=] { + }, _lifetime); + + _controls->show(); + _controls->finishAnimating(); + _controls->showFinished(); +} + +void ReplyArea::show(ReplyAreaData data) { + if (_data == data) { + return; + } + const auto userChanged = (_data.user != data.user); + _data = data; + if (!userChanged) { + if (_data.user) { + _controls->clear(); + } + return; + } + const auto user = data.user; + const auto history = user ? user->owner().history(user).get() : nullptr; + _controls->setHistory({ + .history = history, + }); + _controls->clear(); +} + void ReplyArea::showPremiumToast(not_null emoji) { // #TODO stories } diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index 4d13cff79..571c9c88c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -7,31 +7,57 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/weak_ptr.h" + +namespace Api { +struct SendOptions; +} // namespace Api + namespace HistoryView { class ComposeControls; } // namespace HistoryView +namespace HistoryView::Controls { +struct VoiceToSend; +} // namespace HistoryView::Controls + namespace Media::Stories { -class Delegate; +class Controller; struct ReplyAreaData { - not_null user; + UserData *user = nullptr; + StoryId id = 0; friend inline auto operator<=>(ReplyAreaData, ReplyAreaData) = default; + friend inline bool operator==(ReplyAreaData, ReplyAreaData) = default; }; -class ReplyArea final { +class ReplyArea final : public base::has_weak_ptr { public: - explicit ReplyArea(not_null delegate); + explicit ReplyArea(not_null controller); ~ReplyArea(); + void show(ReplyAreaData data); + private: + using VoiceToSend = HistoryView::Controls::VoiceToSend; + + void initGeometry(); + void initActions(); + + void send(Api::SendOptions options); + void sendVoice(VoiceToSend &&data); + void chooseAttach(std::optional overrideSendImagesAsPhotos); + void showPremiumToast(not_null emoji); - const not_null _delegate; + const not_null _controller; const std::unique_ptr _controls; + ReplyAreaData _data; + bool _choosingAttach = false; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_slider.cpp b/Telegram/SourceFiles/media/stories/media_stories_slider.cpp index cc64c952e..2fdb53884 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_slider.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_slider.cpp @@ -7,12 +7,68 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_slider.h" +#include "media/stories/media_stories_controller.h" +#include "ui/painter.h" +#include "ui/rp_widget.h" +#include "styles/style_widgets.h" +#include "styles/style_media_view.h" + namespace Media::Stories { -Slider::Slider() { +Slider::Slider(not_null controller) +: _controller(controller) { } Slider::~Slider() { } +void Slider::show(SliderData data) { + if (_data == data) { + return; + } + _data = data; + + const auto parent = _controller->wrap(); + auto widget = std::make_unique(parent); + const auto raw = widget.get(); + + raw->paintRequest( + ) | rpl::filter([=] { + return (raw->width() >= st::storiesSlider.width); + }) | rpl::start_with_next([=](QRect clip) { + auto clipf = QRectF(clip); + auto p = QPainter(raw); + const auto single = st::storiesSlider.width; + const auto skip = st::storiesSliderSkip; + // width() == single * max + skip * (max - 1); + // max == (width() + skip) / (single + skip); + const auto max = (raw->width() + skip) / (single + skip); + Assert(max > 0); + const auto count = std::clamp(_data.total, 1, max); + const auto index = std::clamp(data.index, 0, count - 1); + const auto radius = st::storiesSlider.width / 2.; + const auto width = (raw->width() - (count - 1) * skip) + / float64(count); + auto hq = PainterHighQualityEnabler(p); + auto left = 0.; + for (auto i = 0; i != count; ++i) { + const auto rect = QRectF(left, 0, width, single); + p.setBrush((i == index) // #TODO stories + ? st::mediaviewPipControlsFgOver + : st::mediaviewPipPlaybackInactive); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, radius, radius); + left += width + skip; + } + }, raw->lifetime()); + + raw->show(); + _widget = std::move(widget); + + _controller->layoutValue( + ) | rpl::start_with_next([=](const Layout &layout) { + raw->setGeometry(layout.slider - st::storiesSliderMargin); + }, raw->lifetime()); +} + } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_slider.h b/Telegram/SourceFiles/media/stories/media_stories_slider.h index bb908a9e2..0dd18ef08 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_slider.h +++ b/Telegram/SourceFiles/media/stories/media_stories_slider.h @@ -7,13 +7,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +namespace Ui { +class RpWidget; +} // namespace Ui + namespace Media::Stories { +class Controller; + +struct SliderData { + int index = 0; + int total = 0; + + friend inline auto operator<=>(SliderData, SliderData) = default; + friend inline bool operator==(SliderData, SliderData) = default; +}; + class Slider final { public: - Slider(); + explicit Slider(not_null controller); ~Slider(); + void show(SliderData data); + +private: + const not_null _controller; + + std::unique_ptr _widget; + + SliderData _data; }; diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 131a4d8d2..6fcf4fd1c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "media/stories/media_stories_view.h" +#include "media/stories/media_stories_controller.h" #include "media/stories/media_stories_delegate.h" #include "media/stories/media_stories_header.h" #include "media/stories/media_stories_slider.h" @@ -15,23 +16,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Media::Stories { View::View(not_null delegate) -: _delegate(delegate) -, _wrap(_delegate->storiesWrap()) -, _header(std::make_unique
(_delegate)) -, _slider(std::make_unique()) -, _replyArea(std::make_unique(_delegate)) { +: _controller(std::make_unique(delegate)) { } View::~View() = default; void View::show(const Data::StoriesList &list, int index) { - Expects(index < list.items.size()); + _controller->show(list, index); +} - const auto &item = list.items[index]; - _header->show({ - .user = list.user, - .date = item.date, - }); +QRect View::contentGeometry() const { + return _controller->layout().content; } } // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index 884d44219..133652e82 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -7,18 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "data/data_stories.h" - -namespace Ui { -class RpWidget; -} // namespace Ui +namespace Data { +struct StoriesList; +} // namespace Data namespace Media::Stories { -class Header; -class Slider; -class ReplyArea; class Delegate; +class Controller; class View final { public: @@ -26,14 +22,10 @@ public: ~View(); void show(const Data::StoriesList &list, int index); + [[nodiscard]] QRect contentGeometry() const; private: - const not_null _delegate; - const not_null _wrap; - - std::unique_ptr
_header; - std::unique_ptr _slider; - std::unique_ptr _replyArea; + const std::unique_ptr _controller; }; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index c250e4616..fc2b6b18f 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -10,6 +10,7 @@ using "ui/basic.style"; using "ui/widgets/widgets.style"; using "ui/menu_icons.style"; using "media/player/media_player.style"; +using "boxes/boxes.style"; mediaviewOverDuration: 150; @@ -403,3 +404,41 @@ pipVolumeIcon2: icon {{ "player/player_volume_on", mediaviewPipControlsFg }}; pipVolumeIcon2Over: icon {{ "player/player_volume_on", mediaviewPipControlsFgOver }}; speedSliderDividerSize: size(2px, 8px); + +storiesMaxSize: size(405px, 720px); +storiesSlider: MediaSlider(mediaviewPlayback) { + width: 2px; + seekSize: size(2px, 2px); +} +storiesSliderMargin: margins(8px, 7px, 8px, 10px); +storiesSliderSkip: 4px; +storiesHeaderMargin: margins(12px, 3px, 12px, 8px); +storiesHeaderPhoto: UserpicButton(defaultUserpicButton) { + size: size(28px, 28px); + photoSize: 28px; +} +storiesHeaderName: FlatLabel(defaultFlatLabel) { + textFg: mediaviewPipControlsFgOver; // #TODO stories + style: semiboldTextStyle; +} +storiesHeaderNamePosition: point(50px, 2px); +storiesHeaderDate: FlatLabel(defaultFlatLabel) { + textFg: mediaviewPipControlsFg; // #TODO stories +} +storiesHeaderDatePosition: point(50px, 19px); +storiesControlsMinWidth: 200px; +storiesFieldMargin: margins(0px, 14px, 0px, 16px); +storiesAttach: IconButton(defaultIconButton) { + width: 44px; + height: 46px; + + icon: icon {{ "chat/input_attach", historyComposeIconFg }}; + iconOver: icon {{ "chat/input_attach", historyComposeIconFgOver }}; + + rippleAreaPosition: point(2px, 3px); + rippleAreaSize: 40px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +storiesSideSkip: 145px; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index cfedf5ef8..edb309b69 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1543,6 +1543,14 @@ void OverlayWidget::recountSkipTop() { } void OverlayWidget::resizeContentByScreenSize() { + if (_stories) { + const auto content = _stories->contentGeometry(); + _x = content.x(); + _y = content.y(); + _w = content.width(); + _h = content.height(); + return; + } recountSkipTop(); const auto availableWidth = width(); const auto countZoomFor = [&](int outerw, int outerh) {