mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Apply geometry constraints in stories viewer.
This commit is contained in:
parent
89ca38ed29
commit
027bd89e5b
15 changed files with 607 additions and 91 deletions
|
@ -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
|
||||
|
|
|
@ -355,6 +355,7 @@ public:
|
|||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> description,
|
||||
rpl::producer<WebPageData*> 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<FullMsgId> _editMsgId;
|
||||
rpl::variable<FullMsgId> _replyToId;
|
||||
|
@ -677,17 +679,18 @@ void FieldHeader::resolveMessageData() {
|
|||
}
|
||||
|
||||
void FieldHeader::previewRequested(
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> description,
|
||||
rpl::producer<WebPageData*> page) {
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> description,
|
||||
rpl::producer<WebPageData*> 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<PeerData*> 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<PeerData*> peer) {
|
|||
updateControlsGeometry(_wrap->size());
|
||||
orderControls();
|
||||
}
|
||||
}, _wrap->lifetime());
|
||||
}, _historyLifetime);
|
||||
|
||||
updateSendAsButton();
|
||||
}
|
||||
|
@ -2218,7 +2224,7 @@ void ComposeControls::initVoiceRecordBar() {
|
|||
_voiceRecordBar->setStartRecordingFilter([=] {
|
||||
const auto error = [&]() -> std::optional<QString> {
|
||||
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<WebpageProcessor>(_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(),
|
||||
|
|
|
@ -389,10 +389,11 @@ private:
|
|||
|
||||
std::unique_ptr<WebpageProcessor> _preview;
|
||||
|
||||
rpl::lifetime _uploaderSubscriptions;
|
||||
|
||||
Fn<void()> _raiseEmojiSuggestions;
|
||||
|
||||
rpl::lifetime _historyLifetime;
|
||||
rpl::lifetime _uploaderSubscriptions;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
|
|
155
Telegram/SourceFiles/media/stories/media_stories_controller.cpp
Normal file
155
Telegram/SourceFiles/media/stories/media_stories_controller.cpp
Normal file
|
@ -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(delegate)
|
||||
, _wrap(_delegate->storiesWrap())
|
||||
, _header(std::make_unique<Header>(this))
|
||||
, _slider(std::make_unique<Slider>(this))
|
||||
, _replyArea(std::make_unique<ReplyArea>(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<Ui::RpWidget*> Controller::wrap() const {
|
||||
return _wrap;
|
||||
}
|
||||
|
||||
Layout Controller::layout() const {
|
||||
Expects(_layout.current().has_value());
|
||||
|
||||
return *_layout.current();
|
||||
}
|
||||
|
||||
rpl::producer<Layout> Controller::layoutValue() const {
|
||||
return _layout.value() | rpl::filter_optional();
|
||||
}
|
||||
|
||||
std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const {
|
||||
return _delegate->storiesShow();
|
||||
}
|
||||
|
||||
auto Controller::stickerOrEmojiChosen() const
|
||||
->rpl::producer<ChatHelpers::FileChosen> {
|
||||
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
|
|
@ -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*> delegate);
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> wrap() const;
|
||||
[[nodiscard]] Layout layout() const;
|
||||
[[nodiscard]] rpl::producer<Layout> layoutValue() const;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
|
||||
[[nodiscard]] auto stickerOrEmojiChosen() const
|
||||
-> rpl::producer<ChatHelpers::FileChosen>;
|
||||
|
||||
void show(const Data::StoriesList &list, int index);
|
||||
|
||||
private:
|
||||
void initLayout();
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
|
||||
rpl::variable<std::optional<Layout>> _layout;
|
||||
|
||||
const not_null<Ui::RpWidget*> _wrap;
|
||||
const std::unique_ptr<Header> _header;
|
||||
const std::unique_ptr<Slider> _slider;
|
||||
const std::unique_ptr<ReplyArea> _replyArea;
|
||||
|
||||
ShownId _shown;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::Stories
|
|
@ -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(delegate) {
|
||||
Header::Header(not_null<Controller*> 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<Ui::RpWidget>(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<Ui::UserpicButton>(
|
||||
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<Ui::FlatLabel>(
|
||||
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<Ui::FlatLabel>(
|
||||
_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
|
||||
|
|
|
@ -16,24 +16,26 @@ class FlatLabel;
|
|||
|
||||
namespace Media::Stories {
|
||||
|
||||
class Delegate;
|
||||
class Controller;
|
||||
|
||||
struct HeaderData {
|
||||
not_null<UserData*> 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*> delegate);
|
||||
explicit Header(not_null<Controller*> controller);
|
||||
~Header();
|
||||
|
||||
void show(HeaderData data);
|
||||
|
||||
private:
|
||||
const not_null<Delegate*> _delegate;
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
std::unique_ptr<Ui::RpWidget> _widget;
|
||||
std::unique_ptr<Ui::FlatLabel> _date;
|
||||
std::optional<HeaderData> _data;
|
||||
|
|
|
@ -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(delegate)
|
||||
ReplyArea::ReplyArea(not_null<Controller*> controller)
|
||||
: _controller(controller)
|
||||
, _controls(std::make_unique<HistoryView::ComposeControls>(
|
||||
_delegate->storiesWrap(),
|
||||
_controller->wrap(),
|
||||
HistoryView::ComposeControlsDescriptor{
|
||||
.show = _delegate->storiesShow(),
|
||||
.show = _controller->uiShow(),
|
||||
.unavailableEmojiPasted = [=](not_null<DocumentData*> 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<bool> 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<bool> 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<const QMimeData*> 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<DocumentData*> emoji) {
|
||||
// #TODO stories
|
||||
}
|
||||
|
|
|
@ -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<UserData*> 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*> delegate);
|
||||
explicit ReplyArea(not_null<Controller*> 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<bool> overrideSendImagesAsPhotos);
|
||||
|
||||
void showPremiumToast(not_null<DocumentData*> emoji);
|
||||
|
||||
const not_null<Delegate*> _delegate;
|
||||
const not_null<Controller*> _controller;
|
||||
const std::unique_ptr<HistoryView::ComposeControls> _controls;
|
||||
|
||||
ReplyAreaData _data;
|
||||
bool _choosingAttach = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
|
|
@ -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(controller) {
|
||||
}
|
||||
|
||||
Slider::~Slider() {
|
||||
}
|
||||
|
||||
void Slider::show(SliderData data) {
|
||||
if (_data == data) {
|
||||
return;
|
||||
}
|
||||
_data = data;
|
||||
|
||||
const auto parent = _controller->wrap();
|
||||
auto widget = std::make_unique<Ui::RpWidget>(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
|
||||
|
|
|
@ -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*> controller);
|
||||
~Slider();
|
||||
|
||||
void show(SliderData data);
|
||||
|
||||
private:
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
std::unique_ptr<Ui::RpWidget> _widget;
|
||||
|
||||
SliderData _data;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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(delegate)
|
||||
, _wrap(_delegate->storiesWrap())
|
||||
, _header(std::make_unique<Header>(_delegate))
|
||||
, _slider(std::make_unique<Slider>())
|
||||
, _replyArea(std::make_unique<ReplyArea>(_delegate)) {
|
||||
: _controller(std::make_unique<Controller>(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
|
||||
|
|
|
@ -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*> _delegate;
|
||||
const not_null<Ui::RpWidget*> _wrap;
|
||||
|
||||
std::unique_ptr<Header> _header;
|
||||
std::unique_ptr<Slider> _slider;
|
||||
std::unique_ptr<ReplyArea> _replyArea;
|
||||
const std::unique_ptr<Controller> _controller;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue