2023-06-05 16:31:15 +00:00
|
|
|
/*
|
|
|
|
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 "info/stories/info_stories_inner_widget.h"
|
|
|
|
|
2023-06-07 15:56:38 +00:00
|
|
|
#include "data/data_peer.h"
|
2023-06-19 17:00:34 +00:00
|
|
|
#include "data/data_session.h"
|
|
|
|
#include "data/data_stories.h"
|
|
|
|
#include "data/data_user.h"
|
|
|
|
#include "dialogs/ui/dialogs_stories_content.h"
|
|
|
|
#include "dialogs/ui/dialogs_stories_list.h"
|
2023-06-05 16:31:15 +00:00
|
|
|
#include "info/media/info_media_list_widget.h"
|
2023-06-07 15:56:38 +00:00
|
|
|
#include "info/profile/info_profile_icon.h"
|
|
|
|
#include "info/stories/info_stories_widget.h"
|
2023-06-05 16:31:15 +00:00
|
|
|
#include "info/info_controller.h"
|
2023-06-07 15:56:38 +00:00
|
|
|
#include "info/info_memento.h"
|
|
|
|
#include "lang/lang_keys.h"
|
2023-06-19 17:00:34 +00:00
|
|
|
#include "main/main_session.h"
|
2023-06-07 15:56:38 +00:00
|
|
|
#include "settings/settings_common.h"
|
|
|
|
#include "ui/widgets/buttons.h"
|
2023-06-05 16:31:15 +00:00
|
|
|
#include "ui/widgets/labels.h"
|
2023-06-19 17:00:34 +00:00
|
|
|
#include "ui/wrap/slide_wrap.h"
|
2023-06-07 15:56:38 +00:00
|
|
|
#include "ui/wrap/vertical_layout.h"
|
2023-06-19 17:00:34 +00:00
|
|
|
#include "styles/style_dialogs.h"
|
2023-06-05 16:31:15 +00:00
|
|
|
#include "styles/style_info.h"
|
2023-06-19 17:00:34 +00:00
|
|
|
#include "styles/style_settings.h"
|
2023-06-05 16:31:15 +00:00
|
|
|
|
|
|
|
namespace Info::Stories {
|
|
|
|
|
|
|
|
class EmptyWidget : public Ui::RpWidget {
|
|
|
|
public:
|
|
|
|
EmptyWidget(QWidget *parent);
|
|
|
|
|
|
|
|
void setFullHeight(rpl::producer<int> fullHeightValue);
|
|
|
|
|
|
|
|
protected:
|
|
|
|
int resizeGetHeight(int newWidth) override;
|
|
|
|
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
object_ptr<Ui::FlatLabel> _text;
|
|
|
|
int _height = 0;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
EmptyWidget::EmptyWidget(QWidget *parent)
|
|
|
|
: RpWidget(parent)
|
|
|
|
, _text(this, st::infoEmptyLabel) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmptyWidget::setFullHeight(rpl::producer<int> fullHeightValue) {
|
|
|
|
std::move(
|
|
|
|
fullHeightValue
|
|
|
|
) | rpl::start_with_next([this](int fullHeight) {
|
|
|
|
// Make icon center be on 1/3 height.
|
|
|
|
auto iconCenter = fullHeight / 3;
|
2023-06-21 05:34:59 +00:00
|
|
|
auto iconHeight = st::infoEmptyStories.height();
|
2023-06-05 16:31:15 +00:00
|
|
|
auto iconTop = iconCenter - iconHeight / 2;
|
|
|
|
_height = iconTop + st::infoEmptyIconTop;
|
|
|
|
resizeToWidth(width());
|
|
|
|
}, lifetime());
|
|
|
|
}
|
|
|
|
|
|
|
|
int EmptyWidget::resizeGetHeight(int newWidth) {
|
|
|
|
auto labelTop = _height - st::infoEmptyLabelTop;
|
|
|
|
auto labelWidth = newWidth - 2 * st::infoEmptyLabelSkip;
|
|
|
|
_text->resizeToNaturalWidth(labelWidth);
|
|
|
|
|
|
|
|
auto labelLeft = (newWidth - _text->width()) / 2;
|
|
|
|
_text->moveToLeft(labelLeft, labelTop, newWidth);
|
|
|
|
|
|
|
|
update();
|
|
|
|
return _height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EmptyWidget::paintEvent(QPaintEvent *e) {
|
|
|
|
auto p = QPainter(this);
|
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
const auto iconLeft = (width() - st::infoEmptyStories.width()) / 2;
|
2023-06-05 16:31:15 +00:00
|
|
|
const auto iconTop = height() - st::infoEmptyIconTop;
|
2023-06-21 05:34:59 +00:00
|
|
|
st::infoEmptyStories.paint(p, iconLeft, iconTop, width());
|
2023-06-05 16:31:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
InnerWidget::InnerWidget(
|
|
|
|
QWidget *parent,
|
|
|
|
not_null<Controller*> controller)
|
|
|
|
: RpWidget(parent)
|
|
|
|
, _controller(controller)
|
|
|
|
, _empty(this) {
|
|
|
|
_empty->heightValue(
|
|
|
|
) | rpl::start_with_next(
|
|
|
|
[this] { refreshHeight(); },
|
|
|
|
_empty->lifetime());
|
|
|
|
_list = setupList();
|
|
|
|
}
|
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
void InnerWidget::setupTop() {
|
2023-06-07 15:56:38 +00:00
|
|
|
const auto key = _controller->key();
|
|
|
|
const auto peer = key.storiesPeer();
|
|
|
|
if (peer
|
|
|
|
&& key.storiesTab() == Stories::Tab::Saved
|
2023-09-05 07:07:32 +00:00
|
|
|
&& peer->owner().stories().hasArchive(peer)
|
2023-06-07 15:56:38 +00:00
|
|
|
&& _isStackBottom) {
|
2023-06-21 05:34:59 +00:00
|
|
|
createButtons();
|
2023-09-05 07:07:32 +00:00
|
|
|
} else if (peer && key.storiesTab() == Stories::Tab::Archive) {
|
2023-06-21 05:34:59 +00:00
|
|
|
createAboutArchive();
|
2023-06-07 15:56:38 +00:00
|
|
|
} else {
|
2023-06-21 05:34:59 +00:00
|
|
|
_top.destroy();
|
2023-06-07 15:56:38 +00:00
|
|
|
refreshHeight();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
void InnerWidget::createButtons() {
|
|
|
|
_top.create(this);
|
|
|
|
_top->show();
|
|
|
|
_topHeight = _top->heightValue();
|
2023-06-07 15:56:38 +00:00
|
|
|
|
2023-09-05 07:07:32 +00:00
|
|
|
const auto key = _controller->key();
|
|
|
|
const auto peer = key.storiesPeer();
|
|
|
|
const auto stories = &peer->owner().stories();
|
2023-11-14 20:59:21 +00:00
|
|
|
const auto archive = _top->add(object_ptr<Ui::SettingsButton>(
|
2023-06-21 05:34:59 +00:00
|
|
|
_top,
|
2023-06-07 15:56:38 +00:00
|
|
|
tr::lng_stories_archive_button(),
|
2023-11-14 20:59:21 +00:00
|
|
|
st::infoSharedMediaButton));
|
2023-06-19 17:00:34 +00:00
|
|
|
archive->addClickHandler([=] {
|
2023-06-07 15:56:38 +00:00
|
|
|
_controller->showSection(Info::Stories::Make(
|
|
|
|
_controller->key().storiesPeer(),
|
|
|
|
Stories::Tab::Archive));
|
|
|
|
});
|
2023-06-19 17:00:34 +00:00
|
|
|
auto count = rpl::single(
|
|
|
|
rpl::empty
|
2023-09-05 07:07:32 +00:00
|
|
|
) | rpl::then(
|
|
|
|
stories->archiveChanged(
|
|
|
|
) | rpl::filter(
|
|
|
|
rpl::mappers::_1 == peer->id
|
|
|
|
) | rpl::to_empty
|
|
|
|
) | rpl::map([=] {
|
|
|
|
const auto value = stories->archiveCount(peer->id);
|
2023-06-19 17:00:34 +00:00
|
|
|
return (value > 0) ? QString::number(value) : QString();
|
|
|
|
});
|
|
|
|
::Settings::CreateRightLabel(
|
|
|
|
archive,
|
|
|
|
std::move(count),
|
|
|
|
st::infoSharedMediaButton,
|
|
|
|
tr::lng_stories_archive_button());
|
|
|
|
object_ptr<Profile::FloatingIcon>(
|
|
|
|
archive,
|
|
|
|
st::infoIconMediaStoriesArchive,
|
|
|
|
st::infoSharedMediaButtonIconPosition)->show();
|
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
const auto recentWrap = _top->add(
|
2023-06-19 17:00:34 +00:00
|
|
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
2023-06-21 05:34:59 +00:00
|
|
|
_top,
|
2023-11-14 20:59:21 +00:00
|
|
|
object_ptr<Ui::SettingsButton>(
|
2023-06-21 05:34:59 +00:00
|
|
|
_top,
|
2023-06-19 17:00:34 +00:00
|
|
|
tr::lng_stories_recent_button(),
|
|
|
|
st::infoSharedMediaButton)));
|
|
|
|
|
|
|
|
using namespace Dialogs::Stories;
|
|
|
|
auto last = LastForPeer(
|
2023-09-05 07:07:32 +00:00
|
|
|
peer
|
2023-06-19 17:00:34 +00:00
|
|
|
) | rpl::map([=](Content &&content) {
|
|
|
|
for (auto &element : content.elements) {
|
2023-07-03 20:05:11 +00:00
|
|
|
element.unreadCount = 0;
|
2023-06-19 17:00:34 +00:00
|
|
|
}
|
|
|
|
return std::move(content);
|
|
|
|
}) | rpl::start_spawning(recentWrap->lifetime());
|
|
|
|
const auto recent = recentWrap->entity();
|
|
|
|
const auto thumbs = Ui::CreateChild<List>(
|
|
|
|
recent,
|
|
|
|
st::dialogsStoriesListMine,
|
|
|
|
rpl::duplicate(last) | rpl::filter([](const Content &content) {
|
|
|
|
return !content.elements.empty();
|
2023-07-11 07:15:32 +00:00
|
|
|
}));
|
2023-06-30 11:24:59 +00:00
|
|
|
thumbs->show();
|
2023-06-19 17:00:34 +00:00
|
|
|
rpl::combine(
|
|
|
|
recent->sizeValue(),
|
|
|
|
rpl::duplicate(last)
|
|
|
|
) | rpl::start_with_next([=](QSize size, const Content &content) {
|
|
|
|
if (content.elements.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto &small = st::dialogsStories;
|
2023-07-13 07:15:37 +00:00
|
|
|
const auto height = small.photo + 2 * small.photoTop;
|
|
|
|
const auto top = (size.height() - height) / 2;
|
|
|
|
const auto right = st::settingsButtonRightSkip
|
|
|
|
- small.left
|
|
|
|
- small.photoLeft;
|
|
|
|
const auto left = size.width() - right;
|
|
|
|
thumbs->setLayoutConstraints({ left, top }, style::al_right);
|
2023-06-19 17:00:34 +00:00
|
|
|
}, thumbs->lifetime());
|
|
|
|
thumbs->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
|
|
recent->addClickHandler([=] {
|
2023-09-05 07:07:32 +00:00
|
|
|
_controller->parentController()->openPeerStories(peer->id);
|
2023-06-19 17:00:34 +00:00
|
|
|
});
|
2023-06-07 15:56:38 +00:00
|
|
|
object_ptr<Profile::FloatingIcon>(
|
2023-06-19 17:00:34 +00:00
|
|
|
recent,
|
|
|
|
st::infoIconMediaStoriesRecent,
|
2023-06-07 15:56:38 +00:00
|
|
|
st::infoSharedMediaButtonIconPosition)->show();
|
2023-06-19 17:00:34 +00:00
|
|
|
recentWrap->toggleOn(rpl::duplicate(
|
|
|
|
last
|
|
|
|
) | rpl::map([](const Content &content) {
|
|
|
|
return !content.elements.empty();
|
|
|
|
}));
|
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
_top->add(object_ptr<Ui::FixedHeightWidget>(
|
|
|
|
_top,
|
2023-06-07 15:56:38 +00:00
|
|
|
st::infoProfileSkip));
|
2023-06-21 05:34:59 +00:00
|
|
|
_top->add(object_ptr<Ui::BoxContentDivider>(_top));
|
2023-06-07 15:56:38 +00:00
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
_top->resizeToWidth(width());
|
|
|
|
_top->heightValue(
|
2023-06-07 15:56:38 +00:00
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
refreshHeight();
|
2023-06-21 05:34:59 +00:00
|
|
|
}, _top->lifetime());
|
|
|
|
}
|
|
|
|
|
|
|
|
void InnerWidget::createAboutArchive() {
|
|
|
|
_top.create(this);
|
|
|
|
_top->show();
|
|
|
|
_topHeight = _top->heightValue();
|
|
|
|
|
2023-09-05 07:07:32 +00:00
|
|
|
const auto peer = _controller->key().storiesPeer();
|
2023-06-21 05:34:59 +00:00
|
|
|
_top->add(object_ptr<Ui::DividerLabel>(
|
|
|
|
_top,
|
|
|
|
object_ptr<Ui::FlatLabel>(
|
|
|
|
_top,
|
2023-09-05 07:07:32 +00:00
|
|
|
(peer->isChannel()
|
|
|
|
? tr::lng_stories_channel_archive_about
|
|
|
|
: tr::lng_stories_archive_about)(),
|
2023-06-21 05:34:59 +00:00
|
|
|
st::infoStoriesAboutArchive),
|
|
|
|
st::infoStoriesAboutArchivePadding));
|
|
|
|
|
|
|
|
_top->resizeToWidth(width());
|
|
|
|
_top->heightValue(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
refreshHeight();
|
|
|
|
}, _top->lifetime());
|
2023-06-07 15:56:38 +00:00
|
|
|
}
|
|
|
|
|
2023-06-05 16:31:15 +00:00
|
|
|
void InnerWidget::visibleTopBottomUpdated(
|
|
|
|
int visibleTop,
|
|
|
|
int visibleBottom) {
|
|
|
|
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InnerWidget::showInternal(not_null<Memento*> memento) {
|
|
|
|
if (memento->section().type() == Section::Type::Stories) {
|
|
|
|
restoreState(memento);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
object_ptr<Media::ListWidget> InnerWidget::setupList() {
|
|
|
|
auto result = object_ptr<Media::ListWidget>(
|
|
|
|
this,
|
|
|
|
_controller);
|
|
|
|
result->heightValue(
|
|
|
|
) | rpl::start_with_next(
|
|
|
|
[this] { refreshHeight(); },
|
|
|
|
result->lifetime());
|
|
|
|
using namespace rpl::mappers;
|
|
|
|
result->scrollToRequests(
|
|
|
|
) | rpl::map([widget = result.data()](int to) {
|
|
|
|
return Ui::ScrollToRequest {
|
|
|
|
widget->y() + to,
|
|
|
|
-1
|
|
|
|
};
|
|
|
|
}) | rpl::start_to_stream(
|
|
|
|
_scrollToRequests,
|
|
|
|
result->lifetime());
|
|
|
|
_selectedLists.fire(result->selectedListValue());
|
|
|
|
_listTops.fire(result->topValue());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InnerWidget::saveState(not_null<Memento*> memento) {
|
|
|
|
_list->saveState(&memento->media());
|
|
|
|
}
|
|
|
|
|
|
|
|
void InnerWidget::restoreState(not_null<Memento*> memento) {
|
|
|
|
_list->restoreState(&memento->media());
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<SelectedItems> InnerWidget::selectedListValue() const {
|
|
|
|
return _selectedLists.events_starting_with(
|
|
|
|
_list->selectedListValue()
|
|
|
|
) | rpl::flatten_latest();
|
|
|
|
}
|
|
|
|
|
|
|
|
void InnerWidget::selectionAction(SelectionAction action) {
|
|
|
|
_list->selectionAction(action);
|
|
|
|
}
|
|
|
|
|
|
|
|
InnerWidget::~InnerWidget() = default;
|
|
|
|
|
|
|
|
int InnerWidget::resizeGetHeight(int newWidth) {
|
|
|
|
_inResize = true;
|
|
|
|
auto guard = gsl::finally([this] { _inResize = false; });
|
|
|
|
|
2023-06-21 05:34:59 +00:00
|
|
|
if (_top) {
|
|
|
|
_top->resizeToWidth(newWidth);
|
2023-06-07 15:56:38 +00:00
|
|
|
}
|
2023-06-05 16:31:15 +00:00
|
|
|
_list->resizeToWidth(newWidth);
|
|
|
|
_empty->resizeToWidth(newWidth);
|
|
|
|
return recountHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
void InnerWidget::refreshHeight() {
|
|
|
|
if (_inResize) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resize(width(), recountHeight());
|
|
|
|
}
|
|
|
|
|
|
|
|
int InnerWidget::recountHeight() {
|
|
|
|
auto top = 0;
|
2023-06-21 05:34:59 +00:00
|
|
|
if (_top) {
|
|
|
|
_top->moveToLeft(0, top);
|
|
|
|
top += _top->heightNoMargins() - st::lineWidth;
|
2023-06-07 15:56:38 +00:00
|
|
|
}
|
2023-06-05 16:31:15 +00:00
|
|
|
auto listHeight = 0;
|
|
|
|
if (_list) {
|
|
|
|
_list->moveToLeft(0, top);
|
|
|
|
listHeight = _list->heightNoMargins();
|
|
|
|
top += listHeight;
|
|
|
|
}
|
|
|
|
if (listHeight > 0) {
|
|
|
|
_empty->hide();
|
|
|
|
} else {
|
|
|
|
_empty->show();
|
|
|
|
_empty->moveToLeft(0, top);
|
|
|
|
top += _empty->heightNoMargins();
|
|
|
|
}
|
|
|
|
return top;
|
|
|
|
}
|
|
|
|
|
|
|
|
void InnerWidget::setScrollHeightValue(rpl::producer<int> value) {
|
|
|
|
using namespace rpl::mappers;
|
|
|
|
_empty->setFullHeight(rpl::combine(
|
|
|
|
std::move(value),
|
|
|
|
_listTops.events_starting_with(
|
|
|
|
_list->topValue()
|
|
|
|
) | rpl::flatten_latest(),
|
2023-06-21 05:34:59 +00:00
|
|
|
_topHeight.value(),
|
|
|
|
_1 - _2 + _3));
|
2023-06-05 16:31:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
|
|
|
return _scrollToRequests.events();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Info::Stories
|