init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
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/global_media/info_global_media_inner_widget.h"
|
||||
|
||||
#include "info/global_media/info_global_media_provider.h"
|
||||
#include "info/global_media/info_global_media_widget.h"
|
||||
#include "info/media/info_media_empty_widget.h"
|
||||
#include "info/media/info_media_list_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::GlobalMedia {
|
||||
|
||||
InnerWidget::InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _empty(this) {
|
||||
_empty->setType(type());
|
||||
_empty->heightValue(
|
||||
) | rpl::on_next(
|
||||
[this] { refreshHeight(); },
|
||||
_empty->lifetime());
|
||||
_list = setupList();
|
||||
}
|
||||
|
||||
object_ptr<Media::ListWidget> InnerWidget::setupList() {
|
||||
auto result = object_ptr<Media::ListWidget>(this, _controller);
|
||||
|
||||
// Setup list widget connections
|
||||
result->heightValue(
|
||||
) | rpl::on_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());
|
||||
|
||||
_controller->searchQueryValue(
|
||||
) | rpl::on_next([this](const QString &query) {
|
||||
_empty->setSearchQuery(query);
|
||||
}, result->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Storage::SharedMediaType InnerWidget::type() const {
|
||||
return _controller->section().mediaType();
|
||||
}
|
||||
|
||||
void InnerWidget::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
|
||||
}
|
||||
|
||||
bool InnerWidget::showInternal(not_null<Memento*> memento) {
|
||||
if (memento->section().type() == Section::Type::GlobalMedia
|
||||
&& memento->section().mediaType() == type()) {
|
||||
restoreState(memento);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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; });
|
||||
|
||||
_list->resizeToWidth(newWidth);
|
||||
_empty->resizeToWidth(newWidth);
|
||||
return recountHeight();
|
||||
}
|
||||
|
||||
void InnerWidget::refreshHeight() {
|
||||
if (_inResize) {
|
||||
return;
|
||||
}
|
||||
resize(width(), recountHeight());
|
||||
}
|
||||
|
||||
int InnerWidget::recountHeight() {
|
||||
auto top = 0;
|
||||
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(),
|
||||
_1 - _2));
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
} // namespace Info::GlobalMedia
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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/rp_widget.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
class SearchFieldController;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Storage {
|
||||
enum class SharedMediaType : signed char;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Info {
|
||||
class Controller;
|
||||
struct SelectedItems;
|
||||
enum class SelectionAction;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info::Media {
|
||||
class ListWidget;
|
||||
class EmptyWidget;
|
||||
} // namespace Info::Media
|
||||
|
||||
namespace Info::GlobalMedia {
|
||||
|
||||
class Memento;
|
||||
class EmptyWidget;
|
||||
|
||||
class InnerWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller);
|
||||
|
||||
bool showInternal(not_null<Memento*> memento);
|
||||
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
void setScrollHeightValue(rpl::producer<int> value);
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<SelectedItems> selectedListValue() const;
|
||||
void selectionAction(SelectionAction action);
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) override;
|
||||
|
||||
private:
|
||||
int recountHeight();
|
||||
void refreshHeight();
|
||||
|
||||
Storage::SharedMediaType type() const;
|
||||
|
||||
object_ptr<Media::ListWidget> setupList();
|
||||
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
object_ptr<Media::ListWidget> _list = { nullptr };
|
||||
object_ptr<Media::EmptyWidget> _empty;
|
||||
|
||||
bool _inResize = false;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
|
||||
rpl::event_stream<rpl::producer<int>> _listTops;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::GlobalMedia
|
||||
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
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/global_media/info_global_media_provider.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/media/info_media_list_section.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/text/format_song_document_name.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/history.h"
|
||||
#include "core/application.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "layout/layout_selection.h"
|
||||
#include "styles/style_overview.h"
|
||||
|
||||
namespace Info::GlobalMedia {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPerPage = 50;
|
||||
constexpr auto kPreloadedScreensCount = 4;
|
||||
constexpr auto kPreloadedScreensCountFull
|
||||
= kPreloadedScreensCount + 1 + kPreloadedScreensCount;
|
||||
|
||||
} // namespace
|
||||
|
||||
GlobalMediaSlice::GlobalMediaSlice(
|
||||
Key key,
|
||||
std::vector<Data::MessagePosition> items,
|
||||
std::optional<int> fullCount,
|
||||
int skippedAfter)
|
||||
: _key(key)
|
||||
, _items(std::move(items))
|
||||
, _fullCount(fullCount)
|
||||
, _skippedAfter(skippedAfter) {
|
||||
}
|
||||
|
||||
std::optional<int> GlobalMediaSlice::fullCount() const {
|
||||
return _fullCount;
|
||||
}
|
||||
|
||||
std::optional<int> GlobalMediaSlice::skippedBefore() const {
|
||||
return _fullCount
|
||||
? int(*_fullCount - _skippedAfter - _items.size())
|
||||
: std::optional<int>();
|
||||
}
|
||||
|
||||
std::optional<int> GlobalMediaSlice::skippedAfter() const {
|
||||
return _skippedAfter;
|
||||
}
|
||||
|
||||
std::optional<int> GlobalMediaSlice::indexOf(Value position) const {
|
||||
const auto it = ranges::find(_items, position);
|
||||
return (it != end(_items))
|
||||
? std::make_optional(int(it - begin(_items)))
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
int GlobalMediaSlice::size() const {
|
||||
return _items.size();
|
||||
}
|
||||
|
||||
GlobalMediaSlice::Value GlobalMediaSlice::operator[](int index) const {
|
||||
Expects(index >= 0 && index < size());
|
||||
|
||||
return _items[index];
|
||||
}
|
||||
|
||||
std::optional<int> GlobalMediaSlice::distance(
|
||||
const Key &a,
|
||||
const Key &b) const {
|
||||
const auto i = indexOf(a.aroundId);
|
||||
const auto j = indexOf(b.aroundId);
|
||||
return (i && j) ? std::make_optional(*j - *i) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<GlobalMediaSlice::Value> GlobalMediaSlice::nearest(
|
||||
Value position) const {
|
||||
if (_items.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto it = ranges::lower_bound(
|
||||
_items,
|
||||
position,
|
||||
std::greater<>{});
|
||||
|
||||
if (it == end(_items)) {
|
||||
return _items.back();
|
||||
} else if (it == begin(_items)) {
|
||||
return _items.front();
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
|
||||
Provider::Provider(not_null<AbstractController*> controller)
|
||||
: _controller(controller)
|
||||
, _type(_controller->section().mediaType())
|
||||
, _slice(sliceKey(_aroundId)) {
|
||||
_controller->session().data().itemRemoved(
|
||||
) | rpl::on_next([this](auto item) {
|
||||
itemRemoved(item);
|
||||
}, _lifetime);
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::on_next([=] {
|
||||
for (auto &layout : _layouts) {
|
||||
layout.second.item->invalidateCache();
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Provider::Type Provider::type() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
bool Provider::hasSelectRestriction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
|
||||
return rpl::never<bool>();
|
||||
}
|
||||
|
||||
bool Provider::sectionHasFloatingHeader() {
|
||||
switch (_type) {
|
||||
case Type::Photo:
|
||||
case Type::GIF:
|
||||
case Type::Video:
|
||||
case Type::RoundFile:
|
||||
case Type::RoundVoiceFile:
|
||||
case Type::MusicFile:
|
||||
return false;
|
||||
case Type::File:
|
||||
case Type::Link:
|
||||
return true;
|
||||
}
|
||||
Unexpected("Type in HasFloatingHeader()");
|
||||
}
|
||||
|
||||
QString Provider::sectionTitle(not_null<const BaseLayout*> item) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool Provider::sectionItemBelongsHere(
|
||||
not_null<const BaseLayout*> item,
|
||||
not_null<const BaseLayout*> previous) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
|
||||
return item->media() != nullptr;
|
||||
}
|
||||
|
||||
std::optional<int> Provider::fullCount() {
|
||||
return _slice.fullCount();
|
||||
}
|
||||
|
||||
void Provider::restart() {
|
||||
_layouts.clear();
|
||||
_aroundId = Data::MaxMessagePosition;
|
||||
_idsLimit = kMinimalIdsLimit;
|
||||
_slice = GlobalMediaSlice(sliceKey(_aroundId));
|
||||
refreshViewer();
|
||||
}
|
||||
|
||||
void Provider::checkPreload(
|
||||
QSize viewport,
|
||||
not_null<BaseLayout*> topLayout,
|
||||
not_null<BaseLayout*> bottomLayout,
|
||||
bool preloadTop,
|
||||
bool preloadBottom) {
|
||||
const auto visibleWidth = viewport.width();
|
||||
const auto visibleHeight = viewport.height();
|
||||
const auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight;
|
||||
const auto minItemHeight = Media::MinItemHeight(_type, visibleWidth);
|
||||
const auto preloadedCount = preloadedHeight / minItemHeight;
|
||||
const auto preloadIdsLimitMin = (preloadedCount / 2) + 1;
|
||||
const auto preloadIdsLimit = preloadIdsLimitMin
|
||||
+ (visibleHeight / minItemHeight);
|
||||
const auto after = _slice.skippedAfter();
|
||||
const auto topLoaded = after && (*after == 0);
|
||||
const auto before = _slice.skippedBefore();
|
||||
const auto bottomLoaded = before && (*before == 0);
|
||||
|
||||
const auto minScreenDelta = kPreloadedScreensCount
|
||||
- Media::kPreloadIfLessThanScreens;
|
||||
const auto minUniversalIdDelta = (minScreenDelta * visibleHeight)
|
||||
/ minItemHeight;
|
||||
const auto preloadAroundItem = [&](not_null<BaseLayout*> layout) {
|
||||
auto preloadRequired = false;
|
||||
auto aroundId = layout->getItem()->position();
|
||||
if (!preloadRequired) {
|
||||
preloadRequired = (_idsLimit < preloadIdsLimitMin);
|
||||
}
|
||||
if (!preloadRequired) {
|
||||
auto delta = _slice.distance(
|
||||
sliceKey(_aroundId),
|
||||
sliceKey(aroundId));
|
||||
Assert(delta != std::nullopt);
|
||||
preloadRequired = (qAbs(*delta) >= minUniversalIdDelta);
|
||||
}
|
||||
if (preloadRequired) {
|
||||
_idsLimit = preloadIdsLimit;
|
||||
_aroundId = aroundId;
|
||||
refreshViewer();
|
||||
}
|
||||
};
|
||||
|
||||
if (preloadTop && !topLoaded) {
|
||||
preloadAroundItem(topLayout);
|
||||
} else if (preloadBottom && !bottomLoaded) {
|
||||
preloadAroundItem(bottomLayout);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<GlobalMediaSlice> Provider::source(
|
||||
Type type,
|
||||
Data::MessagePosition aroundId,
|
||||
QString query,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(_type == type);
|
||||
|
||||
_totalListQuery = query;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto session = &_controller->session();
|
||||
|
||||
struct State : base::has_weak_ptr {
|
||||
State(not_null<Main::Session*> session) : session(session) {
|
||||
}
|
||||
~State() {
|
||||
session->api().request(requestId).cancel();
|
||||
}
|
||||
|
||||
const not_null<Main::Session*> session;
|
||||
Fn<void()> pushAndLoadMore;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>(session);
|
||||
const auto guard = base::make_weak(state);
|
||||
|
||||
state->pushAndLoadMore = [=] {
|
||||
auto result = fillRequest(aroundId, limitBefore, limitAfter);
|
||||
|
||||
// May destroy 'state' by calling source() with different args.
|
||||
consumer.put_next(std::move(result.slice));
|
||||
|
||||
if (guard && !currentList()->loaded && result.notEnough) {
|
||||
state->requestId = requestMore(state->pushAndLoadMore);
|
||||
}
|
||||
};
|
||||
state->pushAndLoadMore();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
mtpRequestId Provider::requestMore(Fn<void()> loaded) {
|
||||
const auto done = [=](const Api::GlobalMediaResult &result) {
|
||||
const auto list = currentList();
|
||||
if (result.messageIds.empty()) {
|
||||
list->loaded = true;
|
||||
list->fullCount = list->list.size();
|
||||
} else {
|
||||
list->list.reserve(list->list.size() + result.messageIds.size());
|
||||
list->fullCount = result.fullCount;
|
||||
for (const auto &position : result.messageIds) {
|
||||
_seenIds.emplace(position.fullId);
|
||||
list->offsetPosition = position;
|
||||
list->list.push_back(position);
|
||||
}
|
||||
}
|
||||
if (!result.offsetRate) {
|
||||
list->loaded = true;
|
||||
} else {
|
||||
list->offsetRate = result.offsetRate;
|
||||
}
|
||||
loaded();
|
||||
};
|
||||
const auto list = currentList();
|
||||
return _controller->session().api().requestGlobalMedia(
|
||||
_type,
|
||||
_totalListQuery,
|
||||
list->offsetRate,
|
||||
list->offsetPosition,
|
||||
done);
|
||||
}
|
||||
|
||||
Provider::FillResult Provider::fillRequest(
|
||||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
const auto list = currentList();
|
||||
const auto i = ranges::lower_bound(
|
||||
list->list,
|
||||
aroundId,
|
||||
std::greater<>());
|
||||
const auto hasAfter = int(i - begin(list->list));
|
||||
const auto hasBefore = int(end(list->list) - i);
|
||||
const auto takeAfter = std::min(limitAfter, hasAfter);
|
||||
const auto takeBefore = std::min(limitBefore, hasBefore);
|
||||
auto messages = std::vector<Data::MessagePosition>{
|
||||
i - takeAfter,
|
||||
i + takeBefore,
|
||||
};
|
||||
return FillResult{
|
||||
.slice = GlobalMediaSlice(
|
||||
GlobalMediaKey{ aroundId },
|
||||
std::move(messages),
|
||||
((!list->list.empty() || list->loaded)
|
||||
? list->fullCount
|
||||
: std::optional<int>()),
|
||||
hasAfter - takeAfter),
|
||||
.notEnough = (takeBefore < limitBefore),
|
||||
};
|
||||
}
|
||||
|
||||
void Provider::refreshViewer() {
|
||||
_viewerLifetime.destroy();
|
||||
_controller->searchQueryValue(
|
||||
) | rpl::map([=](QString query) {
|
||||
return source(
|
||||
_type,
|
||||
sliceKey(_aroundId).aroundId,
|
||||
query,
|
||||
_idsLimit,
|
||||
_idsLimit);
|
||||
}) | rpl::flatten_latest(
|
||||
) | rpl::on_next([=](GlobalMediaSlice &&slice) {
|
||||
if (!slice.fullCount()) {
|
||||
// Don't display anything while full count is unknown.
|
||||
return;
|
||||
}
|
||||
_slice = std::move(slice);
|
||||
if (auto nearest = _slice.nearest(_aroundId)) {
|
||||
_aroundId = *nearest;
|
||||
}
|
||||
_refreshed.fire({});
|
||||
}, _viewerLifetime);
|
||||
}
|
||||
|
||||
rpl::producer<> Provider::refreshed() {
|
||||
return _refreshed.events();
|
||||
}
|
||||
|
||||
std::vector<Media::ListSection> Provider::fillSections(
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
markLayoutsStale();
|
||||
const auto guard = gsl::finally([&] { clearStaleLayouts(); });
|
||||
|
||||
auto result = std::vector<Media::ListSection>();
|
||||
result.emplace_back(_type, sectionDelegate());
|
||||
auto §ion = result.back();
|
||||
for (auto i = 0, count = int(_slice.size()); i != count; ++i) {
|
||||
auto position = _slice[i];
|
||||
if (auto layout = getLayout(position.fullId, delegate)) {
|
||||
section.addItem(layout);
|
||||
}
|
||||
}
|
||||
if (section.empty()) {
|
||||
result.pop_back();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Provider::markLayoutsStale() {
|
||||
for (auto &layout : _layouts) {
|
||||
layout.second.stale = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::clearStaleLayouts() {
|
||||
for (auto i = _layouts.begin(); i != _layouts.end();) {
|
||||
if (i->second.stale) {
|
||||
_layoutRemoved.fire(i->second.item.get());
|
||||
i = _layouts.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Provider::List *Provider::currentList() {
|
||||
return &_totalLists[_totalListQuery];
|
||||
}
|
||||
|
||||
rpl::producer<not_null<Media::BaseLayout*>> Provider::layoutRemoved() {
|
||||
return _layoutRemoved.events();
|
||||
}
|
||||
|
||||
Media::BaseLayout *Provider::lookupLayout(
|
||||
const HistoryItem *item) {
|
||||
const auto i = _layouts.find(item ? item->fullId() : FullMsgId());
|
||||
return (i != _layouts.end()) ? i->second.item.get() : nullptr;
|
||||
}
|
||||
|
||||
bool Provider::isMyItem(not_null<const HistoryItem*> item) {
|
||||
return _seenIds.contains(item->fullId());
|
||||
}
|
||||
|
||||
bool Provider::isAfter(
|
||||
not_null<const HistoryItem*> a,
|
||||
not_null<const HistoryItem*> b) {
|
||||
return (a->fullId() < b->fullId());
|
||||
}
|
||||
|
||||
void Provider::setSearchQuery(QString query) {
|
||||
Unexpected("Media::Provider::setSearchQuery.");
|
||||
}
|
||||
|
||||
GlobalMediaKey Provider::sliceKey(Data::MessagePosition aroundId) const {
|
||||
return GlobalMediaKey{ aroundId };
|
||||
}
|
||||
|
||||
void Provider::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
const auto id = item->fullId();
|
||||
if (const auto i = _layouts.find(id); i != end(_layouts)) {
|
||||
_layoutRemoved.fire(i->second.item.get());
|
||||
_layouts.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
Media::BaseLayout *Provider::getLayout(
|
||||
FullMsgId itemId,
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
auto it = _layouts.find(itemId);
|
||||
if (it == _layouts.end()) {
|
||||
if (auto layout = createLayout(itemId, delegate, _type)) {
|
||||
layout->initDimensions();
|
||||
it = _layouts.emplace(
|
||||
itemId,
|
||||
std::move(layout)).first;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
it->second.stale = false;
|
||||
return it->second.item.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<Media::BaseLayout> Provider::createLayout(
|
||||
FullMsgId itemId,
|
||||
not_null<Overview::Layout::Delegate*> delegate,
|
||||
Type type) {
|
||||
const auto item = _controller->session().data().message(itemId);
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto getPhoto = [&]() -> PhotoData* {
|
||||
if (const auto media = item->media()) {
|
||||
return media->photo();
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto getFile = [&]() -> DocumentData* {
|
||||
if (const auto media = item->media()) {
|
||||
return media->document();
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
const auto &songSt = st::overviewFileLayout;
|
||||
using namespace Overview::Layout;
|
||||
const auto options = [&] {
|
||||
const auto media = item->media();
|
||||
return MediaOptions{ .spoiler = media && media->hasSpoiler() };
|
||||
};
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
if (const auto photo = getPhoto()) {
|
||||
return std::make_unique<Photo>(
|
||||
delegate,
|
||||
item,
|
||||
photo,
|
||||
options());
|
||||
}
|
||||
return nullptr;
|
||||
case Type::GIF:
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Gif>(delegate, item, file);
|
||||
}
|
||||
return nullptr;
|
||||
case Type::Video:
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Video>(delegate, item, file, options());
|
||||
}
|
||||
return nullptr;
|
||||
case Type::File:
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Document>(
|
||||
delegate,
|
||||
item,
|
||||
DocumentFields{ .document = file },
|
||||
songSt);
|
||||
}
|
||||
return nullptr;
|
||||
case Type::MusicFile:
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Document>(
|
||||
delegate,
|
||||
item,
|
||||
DocumentFields{ .document = file },
|
||||
songSt);
|
||||
}
|
||||
return nullptr;
|
||||
case Type::RoundVoiceFile:
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Voice>(delegate, item, file, songSt);
|
||||
}
|
||||
return nullptr;
|
||||
case Type::Link:
|
||||
return std::make_unique<Link>(delegate, item, item->media());
|
||||
case Type::RoundFile:
|
||||
return nullptr;
|
||||
}
|
||||
Unexpected("Type in ListWidget::createLayout()");
|
||||
}
|
||||
|
||||
Media::ListItemSelectionData Provider::computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) {
|
||||
auto result = Media::ListItemSelectionData(selection);
|
||||
result.canDelete = item->canDelete();
|
||||
result.canForward = item->allowsForward();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Provider::allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
return item->allowsForward();
|
||||
}
|
||||
|
||||
QString Provider::showInFolderPath(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
return document->filepath(true);
|
||||
}
|
||||
|
||||
void Provider::applyDragSelection(
|
||||
Media::ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
bool skipFrom,
|
||||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) {
|
||||
#if 0 // not used for now
|
||||
const auto fromId = GetUniversalId(fromItem) - (skipFrom ? 1 : 0);
|
||||
const auto tillId = GetUniversalId(tillItem) - (skipTill ? 0 : 1);
|
||||
for (auto i = selected.begin(); i != selected.end();) {
|
||||
const auto itemId = GetUniversalId(i->first);
|
||||
if (itemId > fromId || itemId <= tillId) {
|
||||
i = selected.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
for (auto &layoutItem : _layouts) {
|
||||
auto &&universalId = layoutItem.first;
|
||||
if (universalId <= fromId && universalId > tillId) {
|
||||
const auto item = layoutItem.second.item->getItem();
|
||||
ChangeItemSelection(
|
||||
selected,
|
||||
item,
|
||||
computeSelectionData(item, FullSelection));
|
||||
}
|
||||
}
|
||||
#endif // todo global media
|
||||
}
|
||||
|
||||
int64 Provider::scrollTopStatePosition(not_null<HistoryItem*> item) {
|
||||
return item->position().date;
|
||||
}
|
||||
|
||||
HistoryItem *Provider::scrollTopStateItem(Media::ListScrollTopState state) {
|
||||
const auto maybe = Data::MessagePosition{
|
||||
.date = TimeId(state.position),
|
||||
};
|
||||
if (state.item && _slice.indexOf(state.item->position())) {
|
||||
return state.item;
|
||||
} else if (const auto position = _slice.nearest(maybe)) {
|
||||
const auto id = position->fullId;
|
||||
if (const auto item = _controller->session().data().message(id)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return state.item;
|
||||
}
|
||||
|
||||
void Provider::saveState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Media::ListScrollTopState scrollState) {
|
||||
if (_aroundId != Data::MaxMessagePosition && scrollState.item) {
|
||||
memento->setAroundId(_aroundId.fullId);
|
||||
memento->setIdsLimit(_idsLimit);
|
||||
memento->setScrollTopItem(scrollState.item->globalId());
|
||||
memento->setScrollTopItemPosition(scrollState.position);
|
||||
memento->setScrollTopShift(scrollState.shift);
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::restoreState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Fn<void(Media::ListScrollTopState)> restoreScrollState) {
|
||||
if (const auto limit = memento->idsLimit()) {
|
||||
_idsLimit = limit;
|
||||
_aroundId = { memento->aroundId() };
|
||||
restoreScrollState({
|
||||
.position = memento->scrollTopItemPosition(),
|
||||
.item = MessageByGlobalId(memento->scrollTopItem()),
|
||||
.shift = memento->scrollTopShift(),
|
||||
});
|
||||
refreshViewer();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Info::GlobalMedia
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
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 "data/data_messages.h"
|
||||
#include "info/media/info_media_common.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
namespace Info {
|
||||
class AbstractController;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info::GlobalMedia {
|
||||
|
||||
struct GlobalMediaKey {
|
||||
Data::MessagePosition aroundId;
|
||||
|
||||
friend inline constexpr bool operator==(
|
||||
const GlobalMediaKey &,
|
||||
const GlobalMediaKey &) = default;
|
||||
};
|
||||
|
||||
class GlobalMediaSlice final {
|
||||
public:
|
||||
using Key = GlobalMediaKey;
|
||||
using Value = Data::MessagePosition;
|
||||
|
||||
explicit GlobalMediaSlice(
|
||||
Key key,
|
||||
std::vector<Data::MessagePosition> items = {},
|
||||
std::optional<int> fullCount = std::nullopt,
|
||||
int skippedAfter = 0);
|
||||
|
||||
[[nodiscard]] std::optional<int> fullCount() const;
|
||||
[[nodiscard]] std::optional<int> skippedBefore() const;
|
||||
[[nodiscard]] std::optional<int> skippedAfter() const;
|
||||
[[nodiscard]] std::optional<int> indexOf(Value fullId) const;
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] Value operator[](int index) const;
|
||||
[[nodiscard]] std::optional<int> distance(
|
||||
const Key &a,
|
||||
const Key &b) const;
|
||||
[[nodiscard]] std::optional<Value> nearest(Value id) const;
|
||||
|
||||
private:
|
||||
GlobalMediaKey _key;
|
||||
std::vector<Data::MessagePosition> _items;
|
||||
std::optional<int> _fullCount;
|
||||
int _skippedAfter = 0;
|
||||
|
||||
};
|
||||
|
||||
class Provider final
|
||||
: public Media::ListProvider
|
||||
, private Media::ListSectionDelegate {
|
||||
public:
|
||||
using Type = Media::Type;
|
||||
using BaseLayout = Media::BaseLayout;
|
||||
|
||||
explicit Provider(not_null<AbstractController*> controller);
|
||||
|
||||
Type type() override;
|
||||
bool hasSelectRestriction() override;
|
||||
rpl::producer<bool> hasSelectRestrictionChanges() override;
|
||||
bool isPossiblyMyItem(not_null<const HistoryItem*> item) override;
|
||||
|
||||
std::optional<int> fullCount() override;
|
||||
|
||||
void restart() override;
|
||||
void checkPreload(
|
||||
QSize viewport,
|
||||
not_null<BaseLayout*> topLayout,
|
||||
not_null<BaseLayout*> bottomLayout,
|
||||
bool preloadTop,
|
||||
bool preloadBottom) override;
|
||||
void refreshViewer() override;
|
||||
rpl::producer<> refreshed() override;
|
||||
|
||||
std::vector<Media::ListSection> fillSections(
|
||||
not_null<Overview::Layout::Delegate*> delegate) override;
|
||||
rpl::producer<not_null<BaseLayout*>> layoutRemoved() override;
|
||||
BaseLayout *lookupLayout(const HistoryItem *item) override;
|
||||
bool isMyItem(not_null<const HistoryItem*> item) override;
|
||||
bool isAfter(
|
||||
not_null<const HistoryItem*> a,
|
||||
not_null<const HistoryItem*> b) override;
|
||||
|
||||
void setSearchQuery(QString query) override;
|
||||
|
||||
Media::ListItemSelectionData computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) override;
|
||||
void applyDragSelection(
|
||||
Media::ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
bool skipFrom,
|
||||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) override;
|
||||
|
||||
bool allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) override;
|
||||
QString showInFolderPath(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) override;
|
||||
|
||||
int64 scrollTopStatePosition(not_null<HistoryItem*> item) override;
|
||||
HistoryItem *scrollTopStateItem(
|
||||
Media::ListScrollTopState state) override;
|
||||
void saveState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Media::ListScrollTopState scrollState) override;
|
||||
void restoreState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Fn<void(Media::ListScrollTopState)> restoreScrollState) override;
|
||||
|
||||
private:
|
||||
static constexpr auto kMinimalIdsLimit = 16;
|
||||
|
||||
struct FillResult {
|
||||
GlobalMediaSlice slice;
|
||||
bool notEnough = false;
|
||||
};
|
||||
struct List {
|
||||
std::vector<Data::MessagePosition> list;
|
||||
Data::MessagePosition offsetPosition;
|
||||
int32 offsetRate = 0;
|
||||
int fullCount = 0;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
bool sectionHasFloatingHeader() override;
|
||||
QString sectionTitle(not_null<const BaseLayout*> item) override;
|
||||
bool sectionItemBelongsHere(
|
||||
not_null<const BaseLayout*> item,
|
||||
not_null<const BaseLayout*> previous) override;
|
||||
|
||||
[[nodiscard]] rpl::producer<GlobalMediaSlice> source(
|
||||
Type type,
|
||||
Data::MessagePosition aroundId,
|
||||
QString query,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
[[nodiscard]] BaseLayout *getLayout(
|
||||
FullMsgId itemId,
|
||||
not_null<Overview::Layout::Delegate*> delegate);
|
||||
[[nodiscard]] std::unique_ptr<BaseLayout> createLayout(
|
||||
FullMsgId itemId,
|
||||
not_null<Overview::Layout::Delegate*> delegate,
|
||||
Type type);
|
||||
|
||||
[[nodiscard]] GlobalMediaKey sliceKey(
|
||||
Data::MessagePosition aroundId) const;
|
||||
|
||||
void itemRemoved(not_null<const HistoryItem*> item);
|
||||
void markLayoutsStale();
|
||||
void clearStaleLayouts();
|
||||
[[nodiscard]] List *currentList();
|
||||
[[nodiscard]] FillResult fillRequest(
|
||||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
mtpRequestId requestMore(Fn<void()> loaded);
|
||||
|
||||
const not_null<AbstractController*> _controller;
|
||||
const Type _type = {};
|
||||
|
||||
Data::MessagePosition _aroundId = Data::MaxMessagePosition;
|
||||
int _idsLimit = kMinimalIdsLimit;
|
||||
GlobalMediaSlice _slice;
|
||||
|
||||
base::flat_set<FullMsgId> _seenIds;
|
||||
std::unordered_map<FullMsgId, Media::CachedItem> _layouts;
|
||||
rpl::event_stream<not_null<BaseLayout*>> _layoutRemoved;
|
||||
rpl::event_stream<> _refreshed;
|
||||
|
||||
QString _totalListQuery;
|
||||
base::flat_map<QString, List> _totalLists;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
rpl::lifetime _viewerLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::GlobalMedia
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
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/global_media/info_global_media_widget.h"
|
||||
|
||||
#include "info/global_media/info_global_media_inner_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "data/data_download_manager.h"
|
||||
#include "data/data_user.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace Info::GlobalMedia {
|
||||
|
||||
Memento::Memento(not_null<Controller*> controller)
|
||||
: ContentMemento(Tag{ controller->session().user() })
|
||||
, _media(controller) {
|
||||
}
|
||||
|
||||
Memento::Memento(not_null<UserData*> self, Storage::SharedMediaType type)
|
||||
: ContentMemento(Tag{ self })
|
||||
, _media(self, 0, type) {
|
||||
}
|
||||
|
||||
Memento::~Memento() = default;
|
||||
|
||||
Section Memento::section() const {
|
||||
return Section(_media.type(), Section::Type::GlobalMedia);
|
||||
}
|
||||
|
||||
object_ptr<ContentWidget> Memento::createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
const QRect &geometry) {
|
||||
auto result = object_ptr<Widget>(parent, controller);
|
||||
result->setInternalState(geometry, this);
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget::Widget(QWidget *parent, not_null<Controller*> controller)
|
||||
: ContentWidget(parent, controller) {
|
||||
_inner = setInnerWidget(object_ptr<InnerWidget>(
|
||||
this,
|
||||
controller));
|
||||
_inner->setScrollHeightValue(scrollHeightValue());
|
||||
_inner->scrollToRequests(
|
||||
) | rpl::on_next([this](Ui::ScrollToRequest request) {
|
||||
scrollTo(request);
|
||||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||
if (auto globalMediaMemento = dynamic_cast<Memento*>(memento.get())) {
|
||||
restoreState(globalMediaMemento);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Widget::setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<Memento*> memento) {
|
||||
setGeometry(geometry);
|
||||
Ui::SendPendingMoveResizeEvents(this);
|
||||
restoreState(memento);
|
||||
}
|
||||
|
||||
std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
|
||||
auto result = std::make_shared<Memento>(controller());
|
||||
saveState(result.get());
|
||||
return result;
|
||||
}
|
||||
|
||||
void Widget::saveState(not_null<Memento*> memento) {
|
||||
memento->setScrollTop(scrollTopSave());
|
||||
_inner->saveState(memento);
|
||||
}
|
||||
|
||||
void Widget::restoreState(not_null<Memento*> memento) {
|
||||
_inner->restoreState(memento);
|
||||
scrollTopRestore(memento->scrollTop());
|
||||
}
|
||||
|
||||
rpl::producer<SelectedItems> Widget::selectedListValue() const {
|
||||
return _inner->selectedListValue();
|
||||
}
|
||||
|
||||
void Widget::selectionAction(SelectionAction action) {
|
||||
_inner->selectionAction(action);
|
||||
}
|
||||
|
||||
void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
|
||||
const auto window = controller()->parentController();
|
||||
const auto deleteAll = [=] {
|
||||
auto &manager = Core::App().downloadManager();
|
||||
const auto phrase = tr::lng_downloads_delete_sure_all(tr::now);
|
||||
const auto added = manager.loadedHasNonCloudFile()
|
||||
? QString()
|
||||
: tr::lng_downloads_delete_in_cloud(tr::now);
|
||||
const auto deleteSure = [=, &manager](Fn<void()> close) {
|
||||
Ui::PostponeCall(this, close);
|
||||
manager.deleteAll();
|
||||
};
|
||||
window->show(Ui::MakeConfirmBox({
|
||||
.text = phrase + (added.isEmpty() ? QString() : "\n\n" + added),
|
||||
.confirmed = deleteSure,
|
||||
.confirmText = tr::lng_box_delete(tr::now),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
addAction(
|
||||
tr::lng_context_delete_all_files(tr::now),
|
||||
deleteAll,
|
||||
&st::menuIconDelete);
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
return tr::lng_profile_shared_media();
|
||||
}
|
||||
|
||||
std::shared_ptr<Info::Memento> Make(
|
||||
not_null<UserData*> self,
|
||||
Storage::SharedMediaType type) {
|
||||
return std::make_shared<Info::Memento>(
|
||||
std::vector<std::shared_ptr<ContentMemento>>(
|
||||
1,
|
||||
std::make_shared<Memento>(self, type)));
|
||||
}
|
||||
|
||||
} // namespace Info::GlobalMedia
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 "info/info_content_widget.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
|
||||
namespace Storage {
|
||||
enum class SharedMediaType : signed char;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Info::GlobalMedia {
|
||||
|
||||
class InnerWidget;
|
||||
|
||||
class Memento final : public ContentMemento {
|
||||
public:
|
||||
Memento(not_null<Controller*> controller);
|
||||
Memento(not_null<UserData*> self, Storage::SharedMediaType type);
|
||||
~Memento();
|
||||
|
||||
object_ptr<ContentWidget> createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller,
|
||||
const QRect &geometry) override;
|
||||
|
||||
Section section() const override;
|
||||
|
||||
[[nodiscard]] Media::Memento &media() {
|
||||
return _media;
|
||||
}
|
||||
[[nodiscard]] const Media::Memento &media() const {
|
||||
return _media;
|
||||
}
|
||||
|
||||
private:
|
||||
Media::Memento _media;
|
||||
|
||||
};
|
||||
|
||||
class Widget final : public ContentWidget {
|
||||
public:
|
||||
Widget(QWidget *parent, not_null<Controller*> controller);
|
||||
|
||||
bool showInternal(
|
||||
not_null<ContentMemento*> memento) override;
|
||||
|
||||
void setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<Memento*> memento);
|
||||
|
||||
rpl::producer<SelectedItems> selectedListValue() const override;
|
||||
void selectionAction(SelectionAction action) override;
|
||||
|
||||
void fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) override;
|
||||
|
||||
rpl::producer<QString> title() override;
|
||||
|
||||
private:
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||
|
||||
InnerWidget *_inner = nullptr;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Info::Memento> Make(
|
||||
not_null<UserData*> self,
|
||||
Storage::SharedMediaType type);
|
||||
|
||||
} // namespace Info::GlobalMedia
|
||||
Reference in New Issue
Block a user