Allow Info::Media::ListWidget use different data providers.

This commit is contained in:
John Preston 2022-02-24 18:04:24 +03:00
parent eefb9823e1
commit 91c46dbc85
36 changed files with 2491 additions and 1237 deletions

View file

@ -712,13 +712,25 @@ PRIVATE
info/common_groups/info_common_groups_inner_widget.h
info/common_groups/info_common_groups_widget.cpp
info/common_groups/info_common_groups_widget.h
info/downloads/info_downloads_inner_widget.cpp
info/downloads/info_downloads_inner_widget.h
info/downloads/info_downloads_provider.cpp
info/downloads/info_downloads_provider.h
info/downloads/info_downloads_widget.cpp
info/downloads/info_downloads_widget.h
info/media/info_media_buttons.h
info/media/info_media_common.cpp
info/media/info_media_common.h
info/media/info_media_empty_widget.cpp
info/media/info_media_empty_widget.h
info/media/info_media_inner_widget.cpp
info/media/info_media_inner_widget.h
info/media/info_media_list_section.cpp
info/media/info_media_list_section.h
info/media/info_media_list_widget.cpp
info/media/info_media_list_widget.h
info/media/info_media_provider.cpp
info/media/info_media_provider.h
info/media/info_media_widget.cpp
info/media/info_media_widget.h
info/members/info_members_widget.cpp

View file

@ -24,31 +24,31 @@ public:
, _skippedAfter(skippedAfter) {
}
std::optional<int> fullCount() const {
[[nodiscard]] std::optional<int> fullCount() const {
return _fullCount;
}
std::optional<int> skippedBefore() const {
[[nodiscard]] std::optional<int> skippedBefore() const {
return _skippedBefore;
}
std::optional<int> skippedAfter() const {
[[nodiscard]] std::optional<int> skippedAfter() const {
return _skippedAfter;
}
std::optional<int> indexOf(Id id) const {
[[nodiscard]] std::optional<int> indexOf(Id id) const {
const auto it = ranges::find(_ids, id);
if (it != _ids.end()) {
return (it - _ids.begin());
}
return std::nullopt;
}
int size() const {
[[nodiscard]] int size() const {
return _ids.size();
}
Id operator[](int index) const {
[[nodiscard]] Id operator[](int index) const {
Expects(index >= 0 && index < size());
return *(_ids.begin() + index);
}
std::optional<int> distance(Id a, Id b) const {
[[nodiscard]] std::optional<int> distance(Id a, Id b) const {
if (const auto i = indexOf(a)) {
if (const auto j = indexOf(b)) {
return *j - *i;
@ -56,7 +56,8 @@ public:
}
return std::nullopt;
}
std::optional<Id> nearest(Id id) const {
[[nodiscard]] std::optional<Id> nearest(Id id) const {
static_assert(std::is_same_v<IdsContainer, base::flat_set<MsgId>>);
if (const auto it = ranges::lower_bound(_ids, id); it != _ids.end()) {
return *it;
} else if (_ids.empty()) {

View file

@ -181,6 +181,60 @@ struct FullMsgId {
Q_DECLARE_METATYPE(FullMsgId);
struct GlobalMsgId {
FullMsgId itemId;
uint64 sessionUniqueId = 0;
constexpr explicit operator bool() const noexcept {
return itemId && sessionUniqueId;
}
constexpr bool operator!() const noexcept {
return !itemId || !sessionUniqueId;
}
};
[[nodiscard]] inline constexpr bool operator<(
const GlobalMsgId &a,
const GlobalMsgId &b) noexcept {
if (a.itemId < b.itemId) {
return true;
} else if (a.itemId > b.itemId) {
return false;
}
return a.sessionUniqueId < b.sessionUniqueId;
}
[[nodiscard]] inline constexpr bool operator>(
const GlobalMsgId &a,
const GlobalMsgId &b) noexcept {
return b < a;
}
[[nodiscard]] inline constexpr bool operator<=(
const GlobalMsgId &a,
const GlobalMsgId &b) noexcept {
return !(b < a);
}
[[nodiscard]] inline constexpr bool operator>=(
const GlobalMsgId &a,
const GlobalMsgId &b) noexcept {
return !(a < b);
}
[[nodiscard]] inline constexpr bool operator==(
const GlobalMsgId &a,
const GlobalMsgId &b) noexcept {
return (a.itemId == b.itemId)
&& (a.sessionUniqueId == b.sessionUniqueId);
}
[[nodiscard]] inline constexpr bool operator!=(
const GlobalMsgId &a,
const GlobalMsgId &b) noexcept {
return !(a == b);
}
namespace std {
template <>

View file

@ -28,22 +28,22 @@ SparseIdsMergedSlice::SparseIdsMergedSlice(
SparseIdsMergedSlice::SparseIdsMergedSlice(
Key key,
SparseUnsortedIdsSlice scheduled)
SparseUnsortedIdsSlice unsorted)
: _key(key)
, _scheduled(std::move(scheduled)) {
, _unsorted(std::move(unsorted)) {
}
std::optional<int> SparseIdsMergedSlice::fullCount() const {
return _scheduled
? _scheduled->fullCount()
return _unsorted
? _unsorted->fullCount()
: Add(
_part.fullCount(),
_migrated ? _migrated->fullCount() : 0);
}
std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
return _scheduled
? _scheduled->skippedBefore()
return _unsorted
? _unsorted->skippedBefore()
: Add(
isolatedInMigrated() ? 0 : _part.skippedBefore(),
_migrated
@ -55,8 +55,8 @@ std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
}
std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
return _scheduled
? _scheduled->skippedAfter()
return _unsorted
? _unsorted->skippedAfter()
: Add(
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
isolatedInPart() ? 0 : _migrated->skippedAfter()
@ -65,8 +65,8 @@ std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
std::optional<int> SparseIdsMergedSlice::indexOf(
FullMsgId fullId) const {
return _scheduled
? _scheduled->indexOf(fullId.msg)
return _unsorted
? _unsorted->indexOf(fullId.msg)
: isFromPart(fullId)
? (_part.indexOf(fullId.msg) | func::add(migratedSize()))
: isolatedInPart()
@ -77,8 +77,8 @@ std::optional<int> SparseIdsMergedSlice::indexOf(
}
int SparseIdsMergedSlice::size() const {
return _scheduled
? _scheduled->size()
return _unsorted
? _unsorted->size()
: (isolatedInPart() ? 0 : migratedSize())
+ (isolatedInMigrated() ? 0 : _part.size());
}
@ -86,8 +86,8 @@ int SparseIdsMergedSlice::size() const {
FullMsgId SparseIdsMergedSlice::operator[](int index) const {
Expects(index >= 0 && index < size());
if (_scheduled) {
return ComputeId(_key.peerId, (*_scheduled)[index]);
if (_unsorted) {
return ComputeId(_key.peerId, (*_unsorted)[index]);
}
if (const auto size = migratedSize()) {
@ -112,10 +112,13 @@ std::optional<int> SparseIdsMergedSlice::distance(
auto SparseIdsMergedSlice::nearest(
UniversalMsgId id) const -> std::optional<FullMsgId> {
if (_scheduled) {
if (const auto nearestId = _scheduled->nearest(id)) {
return ComputeId(_key.peerId, *nearestId);
if (_unsorted) {
if (_unsorted->indexOf(id).has_value()) {
return ComputeId(_key.peerId, id);
} else if (const auto count = _unsorted->size()) {
return ComputeId(_key.peerId, (*_unsorted)[count / 2]);
}
return std::nullopt;
}
const auto convertFromPartNearest = [&](MsgId result) {
return ComputeId(_key.peerId, result);

View file

@ -62,7 +62,7 @@ public:
std::optional<SparseIdsSlice> migrated);
SparseIdsMergedSlice(
Key key,
SparseUnsortedIdsSlice scheduled);
SparseUnsortedIdsSlice unsorted);
std::optional<int> fullCount() const;
std::optional<int> skippedBefore() const;
@ -139,7 +139,7 @@ private:
Key _key;
SparseIdsSlice _part;
std::optional<SparseIdsSlice> _migrated;
std::optional<SparseUnsortedIdsSlice> _scheduled;
std::optional<SparseUnsortedIdsSlice> _unsorted;
};

View file

@ -27,6 +27,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/file_upload.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "media/audio/media_audio.h"
@ -1017,6 +1019,10 @@ FullMsgId HistoryItem::fullId() const {
return FullMsgId(_history->peer->id, id);
}
GlobalMsgId HistoryItem::globalId() const {
return { fullId(), _history->session().uniqueId() };
}
Data::MessagePosition HistoryItem::position() const {
return { .fullId = fullId(), .date = date() };
}
@ -1301,6 +1307,20 @@ HistoryItem::~HistoryItem() {
applyTTL(0);
}
HistoryItem *MessageByGlobalId(GlobalMsgId globalId) {
if (!globalId.sessionUniqueId || !globalId.itemId) {
return nullptr;
}
for (const auto &[index, account] : Core::App().domain().accounts()) {
if (const auto session = account->maybeSession()) {
if (session->uniqueId() == globalId.sessionUniqueId) {
return session->data().message(globalId.itemId);
}
}
}
return nullptr;
}
QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
return base::unixtime::parse(item->date());
}

View file

@ -380,6 +380,7 @@ public:
[[nodiscard]] bool hasDirectLink() const;
[[nodiscard]] FullMsgId fullId() const;
[[nodiscard]] GlobalMsgId globalId() const;
[[nodiscard]] Data::MessagePosition position() const;
[[nodiscard]] TimeId date() const;
@ -494,9 +495,14 @@ private:
};
QDateTime ItemDateTime(not_null<const HistoryItem*> item);
QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline);
bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item);
[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId);
[[nodiscard]] QDateTime ItemDateTime(not_null<const HistoryItem*> item);
[[nodiscard]] QString ItemDateText(
not_null<const HistoryItem*> item,
bool isUntilOnline);
[[nodiscard]] bool IsItemScheduledUntilOnline(
not_null<const HistoryItem*> item);
ClickHandlerPtr goToMessageClickHandler(
not_null<PeerData*> peer,

View file

@ -282,4 +282,3 @@ void InnerWidget::peerListSetDescription(
} // namespace CommonGroups
} // namespace Info

View file

@ -0,0 +1,195 @@
/*
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/downloads/info_downloads_inner_widget.h"
#include "info/downloads/info_downloads_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::Downloads {
class EmptyWidget : public Ui::RpWidget {
public:
EmptyWidget(QWidget *parent);
void setFullHeight(rpl::producer<int> fullHeightValue);
void setSearchQuery(const QString &query);
protected:
int resizeGetHeight(int newWidth) 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;
auto iconHeight = st::infoEmptyFile.height();
auto iconTop = iconCenter - iconHeight / 2;
_height = iconTop + st::infoEmptyIconTop;
resizeToWidth(width());
}, lifetime());
}
void EmptyWidget::setSearchQuery(const QString &query) {
_text->setText(query.isEmpty()
? tr::lng_media_file_empty(tr::now)
: tr::lng_media_file_empty_search(tr::now));
resizeToWidth(width());
}
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;
}
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();
}
void InnerWidget::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
}
bool InnerWidget::showInternal(not_null<Memento*> memento) {
if (memento->section().type() == Section::Type::Downloads) {
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());
_controller->mediaSourceQueryValue(
) | rpl::start_with_next([this](const QString &query) {
_empty->setSearchQuery(query);
}, result->lifetime());
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::cancelSelection() {
_list->cancelSelection();
}
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::Downloads

View file

@ -0,0 +1,79 @@
/*
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 SettingsSlider;
class VerticalLayout;
class SearchFieldController;
} // namespace Ui
namespace Info {
class Controller;
struct SelectedItems;
namespace Media {
class ListWidget;
} // namespace Media
namespace Downloads {
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 cancelSelection();
~InnerWidget();
protected:
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override;
private:
int recountHeight();
void refreshHeight();
object_ptr<Media::ListWidget> setupList();
const not_null<Controller*> _controller;
object_ptr<Media::ListWidget> _list = { nullptr };
object_ptr<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 Downloads
} // namespace Info

View file

@ -0,0 +1,123 @@
/*
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/downloads/info_downloads_provider.h"
#include "info/media/info_media_widget.h"
#include "info/media/info_media_list_section.h"
#include "info/info_controller.h"
#include "storage/storage_shared_media.h"
#include "layout/layout_selection.h"
namespace Info::Downloads {
namespace {
using namespace Media;
} // namespace
Provider::Provider(not_null<AbstractController*> controller)
: _controller(controller) {
//_controller->session().data().itemRemoved(
//) | rpl::start_with_next([this](auto item) {
// itemRemoved(item);
//}, _lifetime);
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &layout : _layouts) {
layout.second.item->invalidateCache();
}
}, _lifetime);
}
Type Provider::type() {
return Type::File;
}
bool Provider::hasSelectRestriction() {
return false;
}
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
return rpl::never<bool>();
}
bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
return true;
}
std::optional<int> Provider::fullCount() {
return 0;
}
void Provider::restart() {
}
void Provider::checkPreload(
QSize viewport,
not_null<BaseLayout*> topLayout,
not_null<BaseLayout*> bottomLayout,
bool preloadTop,
bool preloadBottom) {
}
void Provider::refreshViewer() {
}
rpl::producer<> Provider::refreshed() {
return _refreshed.events();
}
std::vector<ListSection> Provider::fillSections(
not_null<Overview::Layout::Delegate*> delegate) {
return {};
}
rpl::producer<not_null<BaseLayout*>> Provider::layoutRemoved() {
return _layoutRemoved.events();
}
BaseLayout *Provider::lookupLayout(const HistoryItem *item) {
return nullptr;
}
bool Provider::isMyItem(not_null<const HistoryItem*> item) {
return false;
}
bool Provider::isAfter(
not_null<const HistoryItem*> a,
not_null<const HistoryItem*> b) {
return a < b;
}
void Provider::applyDragSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> fromItem,
bool skipFrom,
not_null<const HistoryItem*> tillItem,
bool skipTill) {
}
void Provider::saveState(
not_null<Media::Memento*> memento,
ListScrollTopState scrollState) {
}
void Provider::restoreState(
not_null<Media::Memento*> memento,
Fn<void(ListScrollTopState)> restoreScrollState) {
}
} // namespace Info::Downloads

View file

@ -0,0 +1,79 @@
/*
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/media/info_media_common.h"
namespace Info {
class AbstractController;
} // namespace Info
namespace Info::Downloads {
class Provider final : public Media::ListProvider {
public:
explicit Provider(not_null<AbstractController*> controller);
Media::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<Media::BaseLayout*> topLayout,
not_null<Media::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<Media::BaseLayout*>> layoutRemoved() override;
Media::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 applyDragSelection(
Media::ListSelectedMap &selected,
not_null<const HistoryItem*> fromItem,
bool skipFrom,
not_null<const HistoryItem*> tillItem,
bool skipTill) 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:
struct Element {
not_null<HistoryItem*> item;
uint64 started = 0;
};
const not_null<AbstractController*> _controller;
std::vector<Element> _elements;
std::unordered_map<not_null<HistoryItem*>, Media::CachedItem> _layouts;
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
rpl::event_stream<> _refreshed;
rpl::lifetime _lifetime;
};
} // namespace Info::Downloads

View file

@ -0,0 +1,99 @@
/*
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/downloads/info_downloads_widget.h"
#include "info/downloads/info_downloads_inner_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "ui/search_field_controller.h"
#include "ui/widgets/scroll_area.h"
#include "data/data_download_manager.h"
#include "data/data_user.h"
#include "core/application.h"
#include "styles/style_info.h"
namespace Info::Downloads {
Memento::Memento(not_null<Controller*> controller)
: ContentMemento(Tag{})
, _media(controller) {
}
Memento::Memento(not_null<UserData*> self)
: ContentMemento(Downloads::Tag{})
, _media(self, 0, Media::Type::File) {
}
Memento::~Memento() = default;
Section Memento::section() const {
return Section(Section::Type::Downloads);
}
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));
}
bool Widget::showInternal(not_null<ContentMemento*> memento) {
if (!controller()->validateMementoPeer(memento)) {
return false;
}
if (auto downloadsMemento = dynamic_cast<Memento*>(memento.get())) {
restoreState(downloadsMemento);
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());
}
std::shared_ptr<Info::Memento> Make(not_null<UserData*> self) {
return std::make_shared<Info::Memento>(
std::vector<std::shared_ptr<ContentMemento>>(
1,
std::make_shared<Memento>(self)));
}
} // namespace Info::Downloads

View file

@ -0,0 +1,69 @@
/*
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 Ui {
class SearchFieldController;
} // namespace Ui
namespace Info::Downloads {
class InnerWidget;
class Memento final : public ContentMemento {
public:
Memento(not_null<Controller*> controller);
Memento(not_null<UserData*> self);
~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);
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);
} // namespace Info::Downloads

View file

@ -262,8 +262,10 @@ Key ContentMemento::key() const {
return Key(peer);
} else if (const auto poll = this->poll()) {
return Key(poll, pollContextId());
} else if (const auto self = settingsSelf()) {
return Settings::Tag{ self };
} else {
return Settings::Tag{ settingsSelf() };
return Downloads::Tag();
}
}
@ -271,4 +273,7 @@ ContentMemento::ContentMemento(Settings::Tag settings)
: _settingsSelf(settings.self.get()) {
}
ContentMemento::ContentMemento(Downloads::Tag downloads) {
}
} // namespace Info

View file

@ -28,6 +28,10 @@ namespace Settings {
struct Tag;
} // namespace Settings
namespace Downloads {
struct Tag;
} // namespace Downloads
class ContentMemento;
class Controller;
@ -120,6 +124,7 @@ public:
, _migratedPeerId(migratedPeerId) {
}
explicit ContentMemento(Settings::Tag settings);
explicit ContentMemento(Downloads::Tag downloads);
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
: _poll(poll)
, _pollContextId(contextId) {

View file

@ -14,13 +14,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/info_content_widget.h"
#include "info/info_memento.h"
#include "info/media/info_media_widget.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_peer.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_download_manager.h"
#include "history/history_item.h"
#include "history/history.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
@ -32,6 +35,9 @@ Key::Key(not_null<PeerData*> peer) : _value(peer) {
Key::Key(Settings::Tag settings) : _value(settings) {
}
Key::Key(Downloads::Tag downloads) : _value(downloads) {
}
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
: _value(PollKey{ poll, contextId }) {
}
@ -50,6 +56,10 @@ UserData *Key::settingsSelf() const {
return nullptr;
}
bool Key::isDownloads() const {
return v::is<Downloads::Tag>(_value);
}
PollData *Key::poll() const {
if (const auto data = std::get_if<PollKey>(&_value)) {
return data->poll;
@ -98,6 +108,28 @@ rpl::producer<QString> AbstractController::mediaSourceQueryValue() const {
return rpl::single(QString());
}
rpl::producer<DownloadsSlice> AbstractController::downloadsSource() const {
const auto manager = &Core::App().downloadManager();
return rpl::single(
rpl::empty_value()
) | rpl::then(
manager->loadingListChanges()
) | rpl::map([=] {
auto result = DownloadsSlice();
for (const auto &id : manager->loadingList()) {
result.entries.push_back(DownloadsEntry{
.item = id.object.item,
.started = id.started,
});
}
ranges::sort(
result.entries,
ranges::less(),
&DownloadsEntry::started);
return result;
});
}
AbstractController::AbstractController(
not_null<Window::SessionController*> parent)
: SessionNavigation(&parent->session())
@ -204,17 +236,19 @@ void Controller::setSection(not_null<ContentMemento*> memento) {
void Controller::updateSearchControllers(
not_null<ContentMemento*> memento) {
using Type = Section::Type;
auto type = _section.type();
auto isMedia = (type == Type::Media);
auto mediaType = isMedia
const auto type = _section.type();
const auto isMedia = (type == Type::Media);
const auto mediaType = isMedia
? _section.mediaType()
: Section::MediaType::kCount;
auto hasMediaSearch = isMedia
const auto hasMediaSearch = isMedia
&& SharedMediaAllowSearch(mediaType);
auto hasCommonGroupsSearch
const auto hasCommonGroupsSearch
= (type == Type::CommonGroups);
auto hasMembersSearch = (type == Type::Members || type == Type::Profile);
auto searchQuery = memento->searchFieldQuery();
const auto hasMembersSearch = (type == Type::Members)
|| (type == Type::Profile);
const auto searchQuery = memento->searchFieldQuery();
const auto isDownloads = (type == Type::Downloads);
if (isMedia) {
_searchController
= std::make_unique<Api::DelayedSearchController>(&session());
@ -289,7 +323,9 @@ rpl::producer<bool> Controller::searchEnabledByContent() const {
}
rpl::producer<QString> Controller::mediaSourceQueryValue() const {
return _searchController->currentQueryValue();
return _searchController
? _searchController->currentQueryValue()
: rpl::never<QString>(); AssertIsDebug() // #TODO downloads
}
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(

View file

@ -28,14 +28,23 @@ struct Tag {
} // namespace Settings
namespace Downloads {
struct Tag {
};
} // namespace Downloads
class Key {
public:
Key(not_null<PeerData*> peer);
Key(Settings::Tag settings);
Key(Downloads::Tag downloads);
Key(not_null<PollData*> poll, FullMsgId contextId);
PeerData *peer() const;
UserData *settingsSelf() const;
bool isDownloads() const;
PollData *poll() const;
FullMsgId pollContextId() const;
@ -47,6 +56,7 @@ private:
std::variant<
not_null<PeerData*>,
Settings::Tag,
Downloads::Tag,
PollKey> _value;
};
@ -64,6 +74,7 @@ public:
CommonGroups,
Members,
Settings,
Downloads,
PollResults,
};
using SettingsType = ::Settings::Type;
@ -102,6 +113,14 @@ private:
};
struct DownloadsEntry {
not_null<HistoryItem*> item;
int64 started = 0; // unixtime * 1000.
};
struct DownloadsSlice {
std::vector<DownloadsEntry> entries;
};
class AbstractController : public Window::SessionNavigation {
public:
AbstractController(not_null<Window::SessionController*> parent);
@ -115,6 +134,9 @@ public:
UserData *settingsSelf() const {
return key().settingsSelf();
}
bool isDownloads() const {
return key().isDownloads();
}
PollData *poll() const;
FullMsgId pollContextId() const {
return key().pollContextId();
@ -128,6 +150,8 @@ public:
int limitAfter) const;
virtual rpl::producer<QString> mediaSourceQueryValue() const;
[[nodiscard]] rpl::producer<DownloadsSlice> downloadsSource() const;
void showSection(
std::shared_ptr<Window::SectionMemento> memento,
const Window::SectionShow &params = Window::SectionShow()) override;

View file

@ -27,6 +27,10 @@ namespace Settings {
struct Tag;
} // namespace Settings
namespace Downloads {
struct Tag;
} // namespace Downloads
class ContentMemento;
class WrapWidget;

View file

@ -545,9 +545,13 @@ MessageIdsList TopBar::collectItems() const {
return ranges::views::all(
_selectedItems.list
) | ranges::views::transform([](auto &&item) {
return item.msgId;
}) | ranges::views::filter([&](FullMsgId msgId) {
return _navigation->session().data().message(msgId) != nullptr;
return item.globalId;
}) | ranges::views::filter([&](const GlobalMsgId &globalId) {
const auto session = &_navigation->session();
return (globalId.sessionUniqueId == session->uniqueId())
&& (session->data().message(globalId.itemId) != nullptr);
}) | ranges::views::transform([](const GlobalMsgId &globalId) {
return globalId.itemId;
}) | ranges::to_vector;
}
@ -568,6 +572,7 @@ void TopBar::performForward() {
}
void TopBar::performDelete() {
// #TODO downloads
auto items = collectItems();
if (items.empty()) {
_cancelSelectionClicks.fire({});
@ -667,6 +672,10 @@ rpl::producer<QString> TitleValue(
return key.poll()->quiz()
? tr::lng_polls_quiz_results_title()
: tr::lng_polls_poll_results_title();
case Section::Type::Downloads:
return rpl::single(u"Downloads"_q);
}
Unexpected("Bad section type in Info::TitleValue()");
}

View file

@ -871,13 +871,19 @@ void WrapWidget::showNewContent(
_historyStack.clear();
}
_controller = std::move(newController);
if (newContent) {
setupTop();
showContent(std::move(newContent));
} else {
showNewContent(memento);
{
// Let old controller outlive old content widget.
const auto oldController = std::exchange(
_controller,
std::move(newController));
if (newContent) {
setupTop();
showContent(std::move(newContent));
} else {
showNewContent(memento);
}
}
if (animationParams) {
if (Ui::InFocusChain(this)) {
setFocus();

View file

@ -51,10 +51,10 @@ enum class Wrap {
};
struct SelectedItem {
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) {
explicit SelectedItem(GlobalMsgId globalId) : globalId(globalId) {
}
FullMsgId msgId;
GlobalMsgId globalId;
bool canDelete = false;
bool canForward = false;
};

View file

@ -23,8 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "styles/style_info.h"
namespace Info {
namespace Media {
namespace Info::Media {
using Type = Storage::SharedMediaType;
@ -118,5 +117,4 @@ inline auto AddCommonGroupsButton(
return result;
};
} // namespace Media
} // namespace Info
} // namespace Info::Media

View file

@ -0,0 +1,54 @@
/*
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/media/info_media_common.h"
#include "history/history_item.h"
namespace Info::Media {
UniversalMsgId GetUniversalId(FullMsgId itemId) {
return peerIsChannel(itemId.peer)
? UniversalMsgId(itemId.msg)
: UniversalMsgId(itemId.msg - ServerMaxMsgId);
}
UniversalMsgId GetUniversalId(not_null<const HistoryItem*> item) {
return GetUniversalId(item->fullId());
}
UniversalMsgId GetUniversalId(not_null<const BaseLayout*> layout) {
return GetUniversalId(layout->getItem()->fullId());
}
bool ChangeItemSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> item,
TextSelection selection) {
const auto changeExisting = [&](auto it) {
if (it == selected.cend()) {
return false;
} else if (it->second.text != selection) {
it->second.text = selection;
return true;
}
return false;
};
if (selected.size() < MaxSelectedItems) {
const auto [i, ok] = selected.try_emplace(item, selection);
if (ok) {
// #TODO downloads
i->second.canDelete = item->canDelete();
i->second.canForward = item->allowsForward();
return true;
}
return changeExisting(i);
}
return changeExisting(selected.find(item));
}
} // namespace Info::Media

View file

@ -0,0 +1,139 @@
/*
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 "overview/overview_layout.h"
namespace Storage {
enum class SharedMediaType : signed char;
} // namespace Storage
namespace Info::Media {
using Type = Storage::SharedMediaType;
using BaseLayout = Overview::Layout::ItemBase;
class Memento;
class ListSection;
inline constexpr auto kPreloadIfLessThanScreens = 2;
struct ListItemSelectionData {
explicit ListItemSelectionData(TextSelection text) : text(text) {
}
TextSelection text;
bool canDelete = false;
bool canForward = false;
};
using ListSelectedMap = base::flat_map<
not_null<const HistoryItem*>,
ListItemSelectionData,
std::less<>>;
enum class ListDragSelectAction {
None,
Selecting,
Deselecting,
};
struct ListContext {
Overview::Layout::PaintContext layoutContext;
not_null<ListSelectedMap*> selected;
not_null<ListSelectedMap*> dragSelected;
ListDragSelectAction dragSelectAction = ListDragSelectAction::None;
};
struct ListScrollTopState {
HistoryItem *item = nullptr;
int shift = 0;
};
struct ListFoundItem {
not_null<BaseLayout*> layout;
QRect geometry;
bool exact = false;
};
struct CachedItem {
CachedItem(std::unique_ptr<BaseLayout> item) : item(std::move(item)) {
};
CachedItem(CachedItem &&other) = default;
CachedItem &operator=(CachedItem &&other) = default;
~CachedItem() = default;
std::unique_ptr<BaseLayout> item;
bool stale = false;
};
using UniversalMsgId = MsgId;
[[nodiscard]] UniversalMsgId GetUniversalId(FullMsgId itemId);
[[nodiscard]] UniversalMsgId GetUniversalId(
not_null<const HistoryItem*> item);
[[nodiscard]] UniversalMsgId GetUniversalId(
not_null<const BaseLayout*> layout);
bool ChangeItemSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> item,
TextSelection selection);
class ListProvider {
public:
[[nodiscard]] virtual Type type() = 0;
[[nodiscard]] virtual bool hasSelectRestriction() = 0;
[[nodiscard]] virtual auto hasSelectRestrictionChanges()
->rpl::producer<bool> = 0;
[[nodiscard]] virtual bool isPossiblyMyItem(
not_null<const HistoryItem*> item) = 0;
[[nodiscard]] virtual std::optional<int> fullCount() = 0;
virtual void restart() = 0;
virtual void checkPreload(
QSize viewport,
not_null<BaseLayout*> topLayout,
not_null<BaseLayout*> bottomLayout,
bool preloadTop,
bool preloadBottom) = 0;
virtual void refreshViewer() = 0;
[[nodiscard]] virtual rpl::producer<> refreshed() = 0;
[[nodiscard]] virtual std::vector<ListSection> fillSections(
not_null<Overview::Layout::Delegate*> delegate) = 0;
[[nodiscard]] virtual auto layoutRemoved()
-> rpl::producer<not_null<BaseLayout*>> = 0;
[[nodiscard]] virtual BaseLayout *lookupLayout(
const HistoryItem *item) = 0;
[[nodiscard]] virtual bool isMyItem(
not_null<const HistoryItem*> item) = 0;
[[nodiscard]] virtual bool isAfter(
not_null<const HistoryItem*> a,
not_null<const HistoryItem*> b) = 0;
virtual void applyDragSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> fromItem,
bool skipFrom,
not_null<const HistoryItem*> tillItem,
bool skipTill) = 0;
virtual void saveState(
not_null<Memento*> memento,
ListScrollTopState scrollState) = 0;
virtual void restoreState(
not_null<Memento*> memento,
Fn<void(ListScrollTopState)> restoreScrollState) = 0;
virtual ~ListProvider() = default;
};
} // namespace Info::Media

View file

@ -0,0 +1,486 @@
/*
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/media/info_media_list_section.h"
#include "storage/storage_shared_media.h"
#include "lang/lang_keys.h"
#include "layout/layout_selection.h"
#include "styles/style_info.h"
namespace Info::Media {
namespace {
constexpr auto kFloatingHeaderAlpha = 0.9;
[[nodiscard]] bool HasFloatingHeader(Type type) {
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()");
}
} // namespace
ListSection::ListSection(Type type)
: _type(type)
, _hasFloatingHeader(HasFloatingHeader(type))
, _mosaic(st::emojiPanWidth - st::inlineResultsLeft) {
}
bool ListSection::empty() const {
return _items.empty();
}
UniversalMsgId ListSection::minId() const {
Expects(!empty());
return GetUniversalId(_items.back()->getItem());
}
void ListSection::setTop(int top) {
_top = top;
}
int ListSection::top() const {
return _top;
}
int ListSection::height() const {
return _height;
}
int ListSection::bottom() const {
return top() + height();
}
bool ListSection::addItem(not_null<BaseLayout*> item) {
if (_items.empty() || belongsHere(item)) {
if (_items.empty()) {
setHeader(item);
}
appendItem(item);
return true;
}
return false;
}
void ListSection::finishSection() {
if (_type == Type::GIF) {
_mosaic.setOffset(st::infoMediaSkip, headerHeight());
_mosaic.setRightSkip(st::infoMediaSkip);
_mosaic.addItems(_items);
}
}
void ListSection::setHeader(not_null<BaseLayout*> item) {
auto text = [&] {
auto date = item->dateTime().date();
switch (_type) {
case Type::Photo:
case Type::GIF:
case Type::Video:
case Type::RoundFile:
case Type::RoundVoiceFile:
case Type::File:
return langMonthFull(date);
case Type::Link:
return langDayOfMonthFull(date);
case Type::MusicFile:
return QString();
}
Unexpected("Type in ListSection::setHeader()");
}();
_header.setText(st::infoMediaHeaderStyle, text);
}
bool ListSection::belongsHere(
not_null<BaseLayout*> item) const {
Expects(!_items.empty());
auto date = item->dateTime().date();
auto myDate = _items.back()->dateTime().date();
switch (_type) {
case Type::Photo:
case Type::GIF:
case Type::Video:
case Type::RoundFile:
case Type::RoundVoiceFile:
case Type::File:
return date.year() == myDate.year()
&& date.month() == myDate.month();
case Type::Link:
return date.year() == myDate.year()
&& date.month() == myDate.month()
&& date.day() == myDate.day();
case Type::MusicFile:
return true;
}
Unexpected("Type in ListSection::belongsHere()");
}
void ListSection::appendItem(not_null<BaseLayout*> item) {
_items.push_back(item);
_byItem.emplace(item->getItem(), item);
}
bool ListSection::removeItem(not_null<const HistoryItem*> item) {
if (const auto i = _byItem.find(item); i != end(_byItem)) {
_items.erase(ranges::remove(_items, i->second), end(_items));
_byItem.erase(i);
refreshHeight();
return true;
}
return false;
}
QRect ListSection::findItemRect(
not_null<const BaseLayout*> item) const {
auto position = item->position();
if (!_mosaic.empty()) {
return _mosaic.findRect(position);
}
auto top = position / _itemsInRow;
auto indexInRow = position % _itemsInRow;
auto left = _itemsLeft
+ indexInRow * (_itemWidth + st::infoMediaSkip);
return QRect(left, top, _itemWidth, item->height());
}
ListFoundItem ListSection::completeResult(
not_null<BaseLayout*> item,
bool exact) const {
return { item, findItemRect(item), exact };
}
ListFoundItem ListSection::findItemByPoint(QPoint point) const {
Expects(!_items.empty());
if (!_mosaic.empty()) {
const auto found = _mosaic.findByPoint(point);
Assert(found.index != -1);
const auto item = _mosaic.itemAt(found.index);
const auto rect = findItemRect(item);
return { item, rect, found.exact };
}
auto itemIt = findItemAfterTop(point.y());
if (itemIt == end(_items)) {
--itemIt;
}
auto item = *itemIt;
auto rect = findItemRect(item);
if (point.y() >= rect.top()) {
auto shift = floorclamp(
point.x(),
(_itemWidth + st::infoMediaSkip),
0,
_itemsInRow);
while (shift-- && itemIt != _items.end()) {
++itemIt;
}
if (itemIt == _items.end()) {
--itemIt;
}
item = *itemIt;
rect = findItemRect(item);
}
return { item, rect, rect.contains(point) };
}
std::optional<ListFoundItem> ListSection::findItemByItem(
not_null<const HistoryItem*> item) const {
const auto i = _byItem.find(item);
if (i != end(_byItem)) {
return ListFoundItem{ i->second, findItemRect(i->second), true };
}
return std::nullopt;
}
ListFoundItem ListSection::findItemNearId(UniversalMsgId universalId) const {
Expects(!_items.empty());
// #TODO downloads
auto itemIt = ranges::lower_bound(
_items,
universalId,
std::greater<>(),
[](const auto &item) { return GetUniversalId(item); });
if (itemIt == _items.end()) {
--itemIt;
}
const auto item = *itemIt;
const auto exact = (GetUniversalId(item) == universalId);
return { item, findItemRect(item), exact };
}
ListFoundItem ListSection::findItemDetails(
not_null<BaseLayout*> item) const {
return { item, findItemRect(item), true };
}
auto ListSection::findItemAfterTop(
int top) -> Items::iterator {
Expects(_mosaic.empty());
return ranges::lower_bound(
_items,
top,
std::less_equal<>(),
[this](const auto &item) {
const auto itemTop = item->position() / _itemsInRow;
return itemTop + item->height();
});
}
auto ListSection::findItemAfterTop(
int top) const -> Items::const_iterator {
Expects(_mosaic.empty());
return ranges::lower_bound(
_items,
top,
std::less_equal<>(),
[this](const auto &item) {
const auto itemTop = item->position() / _itemsInRow;
return itemTop + item->height();
});
}
auto ListSection::findItemAfterBottom(
Items::const_iterator from,
int bottom) const -> Items::const_iterator {
Expects(_mosaic.empty());
return ranges::lower_bound(
from,
_items.end(),
bottom,
std::less<>(),
[this](const auto &item) {
const auto itemTop = item->position() / _itemsInRow;
return itemTop;
});
}
void ListSection::paint(
Painter &p,
const ListContext &context,
QRect clip,
int outerWidth) const {
auto header = headerHeight();
if (QRect(0, 0, outerWidth, header).intersects(clip)) {
p.setPen(st::infoMediaHeaderFg);
_header.drawLeftElided(
p,
st::infoMediaHeaderPosition.x(),
st::infoMediaHeaderPosition.y(),
outerWidth - 2 * st::infoMediaHeaderPosition.x(),
outerWidth);
}
auto localContext = context.layoutContext;
localContext.isAfterDate = (header > 0);
if (!_mosaic.empty()) {
auto paintItem = [&](not_null<BaseLayout*> item, QPoint point) {
p.translate(point.x(), point.y());
item->paint(
p,
clip.translated(-point),
itemSelection(item, context),
&localContext);
p.translate(-point.x(), -point.y());
};
_mosaic.paint(std::move(paintItem), clip);
return;
}
auto fromIt = findItemAfterTop(clip.y());
auto tillIt = findItemAfterBottom(
fromIt,
clip.y() + clip.height());
for (auto it = fromIt; it != tillIt; ++it) {
auto item = *it;
auto rect = findItemRect(item);
localContext.isAfterDate = (header > 0)
&& (rect.y() <= header + _itemsTop);
if (rect.intersects(clip)) {
p.translate(rect.topLeft());
item->paint(
p,
clip.translated(-rect.topLeft()),
itemSelection(item, context),
&localContext);
p.translate(-rect.topLeft());
}
}
}
void ListSection::paintFloatingHeader(
Painter &p,
int visibleTop,
int outerWidth) {
if (!_hasFloatingHeader) {
return;
}
const auto headerTop = st::infoMediaHeaderPosition.y() / 2;
if (visibleTop <= (_top + headerTop)) {
return;
}
const auto header = headerHeight();
const auto headerLeft = st::infoMediaHeaderPosition.x();
const auto floatingTop = std::min(
visibleTop,
bottom() - header + headerTop);
p.save();
p.resetTransform();
p.setOpacity(kFloatingHeaderAlpha);
p.fillRect(QRect(0, floatingTop, outerWidth, header), st::boxBg);
p.setOpacity(1.0);
p.setPen(st::infoMediaHeaderFg);
_header.drawLeftElided(
p,
headerLeft,
floatingTop + headerTop,
outerWidth - 2 * headerLeft,
outerWidth);
p.restore();
}
TextSelection ListSection::itemSelection(
not_null<const BaseLayout*> item,
const ListContext &context) const {
const auto parent = item->getItem();
auto dragSelectAction = context.dragSelectAction;
if (dragSelectAction != ListDragSelectAction::None) {
auto i = context.dragSelected->find(parent);
if (i != context.dragSelected->end()) {
return (dragSelectAction == ListDragSelectAction::Selecting)
? FullSelection
: TextSelection();
}
}
auto i = context.selected->find(parent);
return (i == context.selected->cend())
? TextSelection()
: i->second.text;
}
int ListSection::headerHeight() const {
return _header.isEmpty() ? 0 : st::infoMediaHeaderHeight;
}
void ListSection::resizeToWidth(int newWidth) {
auto minWidth = st::infoMediaMinGridSize + st::infoMediaSkip * 2;
if (newWidth < minWidth) {
return;
}
auto resizeOneColumn = [&](int itemsLeft, int itemWidth) {
_itemsLeft = itemsLeft;
_itemsTop = 0;
_itemsInRow = 1;
_itemWidth = itemWidth;
for (auto &item : _items) {
item->resizeGetHeight(_itemWidth);
}
};
switch (_type) {
case Type::Photo:
case Type::Video:
case Type::RoundFile: {
_itemsLeft = st::infoMediaSkip;
_itemsTop = st::infoMediaSkip;
_itemsInRow = (newWidth - _itemsLeft)
/ (st::infoMediaMinGridSize + st::infoMediaSkip);
_itemWidth = ((newWidth - _itemsLeft) / _itemsInRow)
- st::infoMediaSkip;
for (auto &item : _items) {
item->resizeGetHeight(_itemWidth);
}
} break;
case Type::GIF: {
_mosaic.setFullWidth(newWidth - st::infoMediaSkip);
} break;
case Type::RoundVoiceFile:
case Type::MusicFile:
resizeOneColumn(0, newWidth);
break;
case Type::File:
case Type::Link: {
auto itemsLeft = st::infoMediaHeaderPosition.x();
auto itemWidth = newWidth - 2 * itemsLeft;
resizeOneColumn(itemsLeft, itemWidth);
} break;
}
refreshHeight();
}
int ListSection::recountHeight() {
auto result = headerHeight();
switch (_type) {
case Type::Photo:
case Type::Video:
case Type::RoundFile: {
auto itemHeight = _itemWidth + st::infoMediaSkip;
auto index = 0;
result += _itemsTop;
for (auto &item : _items) {
item->setPosition(_itemsInRow * result + index);
if (++index == _itemsInRow) {
result += itemHeight;
index = 0;
}
}
if (_items.size() % _itemsInRow) {
_rowsCount = int(_items.size()) / _itemsInRow + 1;
result += itemHeight;
} else {
_rowsCount = int(_items.size()) / _itemsInRow;
}
} break;
case Type::GIF: {
return _mosaic.countDesiredHeight(0) + result;
} break;
case Type::RoundVoiceFile:
case Type::File:
case Type::MusicFile:
case Type::Link:
for (auto &item : _items) {
item->setPosition(result);
result += item->height();
}
_rowsCount = _items.size();
break;
}
return result;
}
void ListSection::refreshHeight() {
_height = recountHeight();
}
} // namespace Info::Media

View file

@ -0,0 +1,93 @@
/*
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/media/info_media_common.h"
#include "layout/layout_mosaic.h"
#include "ui/text/text.h"
namespace Info::Media {
class ListSection {
public:
ListSection(Type type);
bool addItem(not_null<BaseLayout*> item);
void finishSection();
[[nodiscard]] bool empty() const;
[[nodiscard]] UniversalMsgId minId() const;
void setTop(int top);
[[nodiscard]] int top() const;
void resizeToWidth(int newWidth);
[[nodiscard]] int height() const;
[[nodiscard]] int bottom() const;
bool removeItem(not_null<const HistoryItem*> item);
[[nodiscard]] std::optional<ListFoundItem> findItemByItem(
not_null<const HistoryItem*> item) const;
[[nodiscard]] ListFoundItem findItemNearId(
UniversalMsgId universalId) const;
[[nodiscard]] ListFoundItem findItemDetails(
not_null<BaseLayout*> item) const;
[[nodiscard]] ListFoundItem findItemByPoint(QPoint point) const;
void paint(
Painter &p,
const ListContext &context,
QRect clip,
int outerWidth) const;
void paintFloatingHeader(Painter &p, int visibleTop, int outerWidth);
private:
using Items = std::vector<not_null<BaseLayout*>>;
[[nodiscard]] int headerHeight() const;
void appendItem(not_null<BaseLayout*> item);
void setHeader(not_null<BaseLayout*> item);
[[nodiscard]] bool belongsHere(not_null<BaseLayout*> item) const;
[[nodiscard]] Items::iterator findItemAfterTop(int top);
[[nodiscard]] Items::const_iterator findItemAfterTop(int top) const;
[[nodiscard]] Items::const_iterator findItemAfterBottom(
Items::const_iterator from,
int bottom) const;
[[nodiscard]] QRect findItemRect(not_null<const BaseLayout*> item) const;
[[nodiscard]] ListFoundItem completeResult(
not_null<BaseLayout*> item,
bool exact) const;
[[nodiscard]] TextSelection itemSelection(
not_null<const BaseLayout*> item,
const ListContext &context) const;
int recountHeight();
void refreshHeight();
Type _type = Type{};
bool _hasFloatingHeader = false;
Ui::Text::String _header;
Items _items;
base::flat_map<
not_null<const HistoryItem*>,
not_null<BaseLayout*>> _byItem;
int _itemsLeft = 0;
int _itemsTop = 0;
int _itemWidth = 0;
int _itemsInRow = 1;
mutable int _rowsCount = 0;
int _top = 0;
int _height = 0;
Mosaic::Layout::MosaicLayout<BaseLayout> _mosaic;
};
} // namespace Info::Media

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "info/media/info_media_widget.h"
#include "data/data_shared_media.h"
#include "info/media/info_media_common.h"
#include "overview/overview_layout_delegate.h"
class DeleteMessagesBox;
@ -45,8 +45,10 @@ class AbstractController;
namespace Media {
using BaseLayout = Overview::Layout::ItemBase;
using UniversalMsgId = MsgId;
struct ListFoundItem;
struct ListContext;
class ListSection;
class ListProvider;
class ListWidget final
: public Ui::RpWidget
@ -89,12 +91,15 @@ public:
bool showInMediaView = false) override;
private:
struct Context;
struct DateBadge;
class Section;
using Section = ListSection;
using FoundItem = ListFoundItem;
using CursorState = HistoryView::CursorState;
using TextState = HistoryView::TextState;
using StateRequest = HistoryView::StateRequest;
using SelectionData = ListItemSelectionData;
using SelectedMap = ListSelectedMap;
using DragSelectAction = ListDragSelectAction;
enum class MouseAction {
None,
PrepareDrag,
@ -102,45 +107,14 @@ private:
PrepareSelect,
Selecting,
};
struct CachedItem {
CachedItem(std::unique_ptr<BaseLayout> item);
CachedItem(CachedItem &&other);
CachedItem &operator=(CachedItem &&other);
~CachedItem();
std::unique_ptr<BaseLayout> item;
bool stale = false;
};
struct FoundItem {
not_null<BaseLayout*> layout;
QRect geometry;
bool exact = false;
};
struct SelectionData {
explicit SelectionData(TextSelection text) : text(text) {
}
TextSelection text;
bool canDelete = false;
bool canForward = false;
};
using SelectedMap = base::flat_map<
UniversalMsgId,
SelectionData,
std::less<>>;
enum class DragSelectAction {
None,
Selecting,
Deselecting,
};
struct MouseState {
UniversalMsgId itemId = 0;
HistoryItem *item = nullptr;
QSize size;
QPoint cursor;
bool inside = false;
inline bool operator==(const MouseState &other) const {
return (itemId == other.itemId)
return (item == other.item)
&& (cursor == other.cursor);
}
inline bool operator!=(const MouseState &other) const {
@ -153,10 +127,6 @@ private:
Touch,
Other,
};
struct ScrollTopState {
UniversalMsgId item = 0;
int shift = 0;
};
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
@ -175,97 +145,83 @@ private:
void start();
int recountHeight();
void refreshHeight();
void subscribeToSession(not_null<Main::Session*> session);
void setupSelectRestriction();
[[nodiscard]] bool hasSelectRestriction() const;
QMargins padding() const;
bool isMyItem(not_null<const HistoryItem*> item) const;
bool isItemLayout(
not_null<const HistoryItem*> item,
BaseLayout *layout) const;
bool isPossiblyMyId(FullMsgId fullId) const;
void repaintItem(const HistoryItem *item);
void repaintItem(UniversalMsgId msgId);
void repaintItem(const BaseLayout *item);
void repaintItem(QRect itemGeometry);
void itemRemoved(not_null<const HistoryItem*> item);
void itemLayoutChanged(not_null<const HistoryItem*> item);
void refreshViewer();
void invalidatePaletteCache();
void refreshRows();
SparseIdsMergedSlice::Key sliceKey(
UniversalMsgId universalId) const;
BaseLayout *getLayout(UniversalMsgId universalId);
BaseLayout *getExistingLayout(UniversalMsgId universalId) const;
std::unique_ptr<BaseLayout> createLayout(
UniversalMsgId universalId,
Type type);
SelectedItems collectSelectedItems() const;
MessageIdsList collectSelectedIds() const;
[[nodiscard]] SelectedItems collectSelectedItems() const;
[[nodiscard]] MessageIdsList collectSelectedIds() const;
void pushSelectedItems();
FullMsgId computeFullId(UniversalMsgId universalId) const;
bool hasSelected() const;
bool isSelectedItem(
[[nodiscard]] bool hasSelected() const;
[[nodiscard]] bool isSelectedItem(
const SelectedMap::const_iterator &i) const;
void removeItemSelection(
const SelectedMap::const_iterator &i);
bool hasSelectedText() const;
bool hasSelectedItems() const;
[[nodiscard]] bool hasSelectedText() const;
[[nodiscard]] bool hasSelectedItems() const;
void clearSelected();
void forwardSelected();
void forwardItem(UniversalMsgId universalId);
void forwardItem(GlobalMsgId globalId);
void forwardItems(MessageIdsList &&items);
void deleteSelected();
void deleteItem(UniversalMsgId universalId);
void deleteItem(GlobalMsgId globalId);
DeleteMessagesBox *deleteItems(MessageIdsList &&items);
void applyItemSelection(
UniversalMsgId universalId,
HistoryItem *item,
TextSelection selection);
void toggleItemSelection(
UniversalMsgId universalId);
SelectedMap::iterator itemUnderPressSelection();
SelectedMap::const_iterator itemUnderPressSelection() const;
void toggleItemSelection(not_null<HistoryItem*> item);
[[nodiscard]] SelectedMap::iterator itemUnderPressSelection();
[[nodiscard]] auto itemUnderPressSelection() const
-> SelectedMap::const_iterator;
bool isItemUnderPressSelected() const;
bool requiredToStartDragging(not_null<BaseLayout*> layout) const;
bool isPressInSelectedText(TextState state) const;
[[nodiscard]] bool requiredToStartDragging(
not_null<BaseLayout*> layout) const;
[[nodiscard]] bool isPressInSelectedText(TextState state) const;
void applyDragSelection();
void applyDragSelection(SelectedMap &applyTo) const;
bool changeItemSelection(
SelectedMap &selected,
UniversalMsgId universalId,
TextSelection selection) const;
static bool IsAfter(
[[nodiscard]] bool isAfter(
const MouseState &a,
const MouseState &b);
static bool SkipSelectFromItem(const MouseState &state);
static bool SkipSelectTillItem(const MouseState &state);
const MouseState &b) const;
[[nodiscard]] static bool SkipSelectFromItem(const MouseState &state);
[[nodiscard]] static bool SkipSelectTillItem(const MouseState &state);
void markLayoutsStale();
void clearStaleLayouts();
std::vector<Section>::iterator findSectionByItem(
UniversalMsgId universalId);
std::vector<Section>::iterator findSectionAfterTop(int top);
std::vector<Section>::const_iterator findSectionAfterTop(
[[nodiscard]] std::vector<Section>::iterator findSectionByItem(
not_null<const HistoryItem*> item);
[[nodiscard]] std::vector<Section>::iterator findSectionAfterTop(
int top);
[[nodiscard]] std::vector<Section>::const_iterator findSectionAfterTop(
int top) const;
std::vector<Section>::const_iterator findSectionAfterBottom(
[[nodiscard]] auto findSectionAfterBottom(
std::vector<Section>::const_iterator from,
int bottom) const;
FoundItem findItemByPoint(QPoint point) const;
std::optional<FoundItem> findItemById(UniversalMsgId universalId);
FoundItem findItemDetails(not_null<BaseLayout*> item);
FoundItem foundItemInSection(
int bottom) const -> std::vector<Section>::const_iterator;
[[nodiscard]] FoundItem findItemByPoint(QPoint point) const;
[[nodiscard]] std::optional<FoundItem> findItemByItem(
const HistoryItem *item);
[[nodiscard]] FoundItem findItemDetails(not_null<BaseLayout*> item);
[[nodiscard]] FoundItem foundItemInSection(
const FoundItem &item,
const Section &section) const;
ScrollTopState countScrollState() const;
[[nodiscard]] ListScrollTopState countScrollState() const;
void saveScrollState();
void restoreScrollState();
QPoint clampMousePosition(QPoint position) const;
[[nodiscard]] QPoint clampMousePosition(QPoint position) const;
void mouseActionStart(
const QPoint &globalPosition,
Qt::MouseButton button);
@ -276,7 +232,7 @@ private:
Qt::MouseButton button);
void mouseActionCancel();
void performDrag();
style::cursor computeMouseCursor() const;
[[nodiscard]] style::cursor computeMouseCursor() const;
void showContextMenu(
QContextMenuEvent *e,
ContextMenuSource source);
@ -298,24 +254,15 @@ private:
void setActionBoxWeak(QPointer<Ui::RpWidget> box);
const not_null<AbstractController*> _controller;
const not_null<PeerData*> _peer;
PeerData * const _migrated = nullptr;
const Type _type = Type::Photo;
const std::unique_ptr<ListProvider> _provider;
static constexpr auto kMinimalIdsLimit = 16;
static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1);
UniversalMsgId _universalAroundId = kDefaultAroundId;
int _idsLimit = kMinimalIdsLimit;
SparseIdsMergedSlice _slice;
std::unordered_map<UniversalMsgId, CachedItem> _layouts;
base::flat_set<not_null<const BaseLayout*>> _heavyLayouts;
bool _heavyLayoutsInvalidated = false;
std::vector<Section> _sections;
int _visibleTop = 0;
int _visibleBottom = 0;
ScrollTopState _scrollTopState;
ListScrollTopState _scrollTopState;
rpl::event_stream<int> _scrollToRequests;
MouseAction _mouseAction = MouseAction::None;
@ -324,7 +271,7 @@ private:
MouseState _overState;
MouseState _pressState;
BaseLayout *_overLayout = nullptr;
UniversalMsgId _contextUniversalId = 0;
HistoryItem *_contextItem = nullptr;
CursorState _mouseCursorState = CursorState();
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;
@ -345,8 +292,6 @@ private:
QPoint _trippleClickPoint;
crl::time _trippleClickStartTime = 0;
rpl::lifetime _viewerLifetime;
};
} // namespace Media

View file

@ -0,0 +1,450 @@
/*
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/media/info_media_provider.h"
#include "info/media/info_media_widget.h"
#include "info/media/info_media_list_section.h"
#include "info/info_controller.h"
#include "layout/layout_selection.h"
#include "main/main_session.h"
#include "history/history.h"
#include "history/history_item.h"
#include "data/data_session.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_peer_values.h"
#include "styles/style_info.h"
namespace Info::Media {
namespace {
constexpr auto kPreloadedScreensCount = 4;
constexpr auto kPreloadedScreensCountFull
= kPreloadedScreensCount + 1 + kPreloadedScreensCount;
[[nodiscard]] int MinItemHeight(Type type, int width) {
auto &songSt = st::overviewFileLayout;
switch (type) {
case Type::Photo:
case Type::GIF:
case Type::Video:
case Type::RoundFile: {
auto itemsLeft = st::infoMediaSkip;
auto itemsInRow = (width - itemsLeft)
/ (st::infoMediaMinGridSize + st::infoMediaSkip);
return (st::infoMediaMinGridSize + st::infoMediaSkip) / itemsInRow;
} break;
case Type::RoundVoiceFile:
return songSt.songPadding.top() + songSt.songThumbSize + songSt.songPadding.bottom() + st::lineWidth;
case Type::File:
return songSt.filePadding.top() + songSt.fileThumbSize + songSt.filePadding.bottom() + st::lineWidth;
case Type::MusicFile:
return songSt.songPadding.top() + songSt.songThumbSize + songSt.songPadding.bottom();
case Type::Link:
return st::linksPhotoSize + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder;
}
Unexpected("Type in MinItemHeight()");
}
} // namespace
Provider::Provider(not_null<AbstractController*> controller)
: _controller(controller)
, _peer(_controller->key().peer())
, _migrated(_controller->migrated())
, _type(_controller->section().mediaType())
, _slice(sliceKey(_universalAroundId)) {
_controller->session().data().itemRemoved(
) | rpl::start_with_next([this](auto item) {
itemRemoved(item);
}, _lifetime);
style::PaletteChanged(
) | rpl::start_with_next([=] {
for (auto &layout : _layouts) {
layout.second.item->invalidateCache();
}
}, _lifetime);
}
Type Provider::type() {
return _type;
}
bool Provider::hasSelectRestriction() {
if (_peer->allowsForwarding()) {
return false;
} else if (const auto chat = _peer->asChat()) {
return !chat->canDeleteMessages();
} else if (const auto channel = _peer->asChannel()) {
return !channel->canDeleteMessages();
}
return true;
}
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
if (_peer->isUser()) {
return rpl::never<bool>();
}
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
auto noForwards = chat
? Data::PeerFlagValue(chat, ChatDataFlag::NoForwards)
: Data::PeerFlagValue(
channel,
ChannelDataFlag::NoForwards
) | rpl::type_erased();
auto rights = chat
? chat->adminRightsValue()
: channel->adminRightsValue();
auto canDelete = std::move(
rights
) | rpl::map([=] {
return chat
? chat->canDeleteMessages()
: channel->canDeleteMessages();
});
return rpl::combine(
std::move(noForwards),
std::move(canDelete)
) | rpl::map([=] {
return hasSelectRestriction();
}) | rpl::distinct_until_changed() | rpl::skip(1);
}
bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
return isPossiblyMyPeerId(item->history()->peer->id);
}
bool Provider::isPossiblyMyPeerId(PeerId peerId) const {
return (peerId == _peer->id) || (_migrated && peerId == _migrated->id);
}
std::optional<int> Provider::fullCount() {
return _slice.fullCount();
}
void Provider::restart() {
_layouts.clear();
_universalAroundId = kDefaultAroundId;
_idsLimit = kMinimalIdsLimit;
_slice = SparseIdsMergedSlice(sliceKey(_universalAroundId));
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 = 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
- kPreloadIfLessThanScreens;
const auto minUniversalIdDelta = (minScreenDelta * visibleHeight)
/ minItemHeight;
const auto preloadAroundItem = [&](not_null<BaseLayout*> layout) {
auto preloadRequired = false;
auto universalId = GetUniversalId(layout);
if (!preloadRequired) {
preloadRequired = (_idsLimit < preloadIdsLimitMin);
}
if (!preloadRequired) {
auto delta = _slice.distance(
sliceKey(_universalAroundId),
sliceKey(universalId));
Assert(delta != std::nullopt);
preloadRequired = (qAbs(*delta) >= minUniversalIdDelta);
}
if (preloadRequired) {
_idsLimit = preloadIdsLimit;
_universalAroundId = universalId;
refreshViewer();
}
};
if (preloadTop && !topLoaded) {
preloadAroundItem(topLayout);
} else if (preloadBottom && !bottomLoaded) {
preloadAroundItem(bottomLayout);
}
}
void Provider::refreshViewer() {
_viewerLifetime.destroy();
const auto idForViewer = sliceKey(_universalAroundId).universalId;
_controller->mediaSource(
idForViewer,
_idsLimit,
_idsLimit
) | rpl::start_with_next([=](SparseIdsMergedSlice &&slice) {
if (!slice.fullCount()) {
// Don't display anything while full count is unknown.
return;
}
_slice = std::move(slice);
if (auto nearest = _slice.nearest(idForViewer)) {
_universalAroundId = GetUniversalId(*nearest);
}
_refreshed.fire({});
}, _viewerLifetime);
}
rpl::producer<> Provider::refreshed() {
return _refreshed.events();
}
std::vector<ListSection> Provider::fillSections(
not_null<Overview::Layout::Delegate*> delegate) {
markLayoutsStale();
const auto guard = gsl::finally([&] { clearStaleLayouts(); });
auto result = std::vector<ListSection>();
auto section = ListSection(_type);
auto count = _slice.size();
for (auto i = count; i != 0;) {
auto universalId = GetUniversalId(_slice[--i]);
if (auto layout = getLayout(universalId, delegate)) {
if (!section.addItem(layout)) {
section.finishSection();
result.push_back(std::move(section));
section = ListSection(_type);
section.addItem(layout);
}
}
}
if (!section.empty()) {
section.finishSection();
result.push_back(std::move(section));
}
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;
}
}
}
rpl::producer<not_null<BaseLayout*>> Provider::layoutRemoved() {
return _layoutRemoved.events();
}
BaseLayout *Provider::lookupLayout(
const HistoryItem *item) {
const auto i = _layouts.find(GetUniversalId(item));
return (i != _layouts.end()) ? i->second.item.get() : nullptr;
}
bool Provider::isMyItem(not_null<const HistoryItem*> item) {
const auto peer = item->history()->peer;
return (_peer == peer) || (_migrated == peer);
}
bool Provider::isAfter(
not_null<const HistoryItem*> a,
not_null<const HistoryItem*> b) {
return (GetUniversalId(a) < GetUniversalId(b));
}
SparseIdsMergedSlice::Key Provider::sliceKey(
UniversalMsgId universalId) const {
using Key = SparseIdsMergedSlice::Key;
if (_migrated) {
return Key(_peer->id, _migrated->id, universalId);
}
if (universalId < 0) {
// Convert back to plain id for non-migrated histories.
universalId = universalId + ServerMaxMsgId;
}
return Key(_peer->id, 0, universalId);
}
void Provider::itemRemoved(not_null<const HistoryItem*> item) {
const auto id = GetUniversalId(item);
if (const auto i = _layouts.find(id); i != end(_layouts)) {
_layoutRemoved.fire(i->second.item.get());
_layouts.erase(i);
}
}
FullMsgId Provider::computeFullId(
UniversalMsgId universalId) const {
Expects(universalId != 0);
return (universalId > 0)
? FullMsgId(_peer->id, universalId)
: FullMsgId(
(_migrated ? _migrated : _peer.get())->id,
ServerMaxMsgId + universalId);
}
BaseLayout *Provider::getLayout(
UniversalMsgId universalId,
not_null<Overview::Layout::Delegate*> delegate) {
auto it = _layouts.find(universalId);
if (it == _layouts.end()) {
if (auto layout = createLayout(universalId, delegate, _type)) {
layout->initDimensions();
it = _layouts.emplace(
universalId,
std::move(layout)).first;
} else {
return nullptr;
}
}
it->second.stale = false;
return it->second.item.get();
}
std::unique_ptr<BaseLayout> Provider::createLayout(
UniversalMsgId universalId,
not_null<Overview::Layout::Delegate*> delegate,
Type type) {
const auto item = _controller->session().data().message(
computeFullId(universalId));
if (!item) {
return nullptr;
}
auto getPhoto = [&]() -> PhotoData* {
if (const auto media = item->media()) {
return media->photo();
}
return nullptr;
};
auto getFile = [&]() -> DocumentData* {
if (auto media = item->media()) {
return media->document();
}
return nullptr;
};
auto &songSt = st::overviewFileLayout;
using namespace Overview::Layout;
switch (type) {
case Type::Photo:
if (const auto photo = getPhoto()) {
return std::make_unique<Photo>(delegate, item, photo);
}
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);
}
return nullptr;
case Type::File:
if (const auto file = getFile()) {
return std::make_unique<Document>(delegate, item, file, songSt);
}
return nullptr;
case Type::MusicFile:
if (const auto file = getFile()) {
return std::make_unique<Document>(delegate, item, 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()");
}
void Provider::applyDragSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> fromItem,
bool skipFrom,
not_null<const HistoryItem*> tillItem,
bool skipTill) {
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) {
ChangeItemSelection(
selected,
layoutItem.second.item->getItem(),
FullSelection);
}
}
}
void Provider::saveState(
not_null<Memento*> memento,
ListScrollTopState scrollState) {
if (_universalAroundId != kDefaultAroundId && scrollState.item) {
memento->setAroundId(computeFullId(_universalAroundId));
memento->setIdsLimit(_idsLimit);
memento->setScrollTopItem(scrollState.item->globalId());
memento->setScrollTopShift(scrollState.shift);
}
}
void Provider::restoreState(
not_null<Memento*> memento,
Fn<void(ListScrollTopState)> restoreScrollState) {
if (const auto limit = memento->idsLimit()) {
auto wasAroundId = memento->aroundId();
if (isPossiblyMyPeerId(wasAroundId.peer)) {
_idsLimit = limit;
_universalAroundId = GetUniversalId(wasAroundId);
restoreScrollState({
.item = MessageByGlobalId(memento->scrollTopItem()),
.shift = memento->scrollTopShift(),
});
refreshViewer();
}
}
}
} // namespace Info::Media

View file

@ -0,0 +1,103 @@
/*
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/media/info_media_common.h"
#include "data/data_shared_media.h"
namespace Info {
class AbstractController;
} // namespace Info
namespace Info::Media {
class Provider final : public ListProvider {
public:
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<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 applyDragSelection(
ListSelectedMap &selected,
not_null<const HistoryItem*> fromItem,
bool skipFrom,
not_null<const HistoryItem*> tillItem,
bool skipTill) override;
void saveState(
not_null<Memento*> memento,
ListScrollTopState scrollState) override;
void restoreState(
not_null<Memento*> memento,
Fn<void(ListScrollTopState)> restoreScrollState) override;
private:
static constexpr auto kMinimalIdsLimit = 16;
static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1);
[[nodiscard]] bool isPossiblyMyPeerId(PeerId peerId) const;
[[nodiscard]] FullMsgId computeFullId(UniversalMsgId universalId) const;
[[nodiscard]] BaseLayout *getLayout(
UniversalMsgId universalId,
not_null<Overview::Layout::Delegate*> delegate);
[[nodiscard]] std::unique_ptr<BaseLayout> createLayout(
UniversalMsgId universalId,
not_null<Overview::Layout::Delegate*> delegate,
Type type);
[[nodiscard]] SparseIdsMergedSlice::Key sliceKey(
UniversalMsgId universalId) const;
void itemRemoved(not_null<const HistoryItem*> item);
void markLayoutsStale();
void clearStaleLayouts();
const not_null<AbstractController*> _controller;
const not_null<PeerData*> _peer;
PeerData * const _migrated = nullptr;
const Type _type = Type::Photo;
UniversalMsgId _universalAroundId = kDefaultAroundId;
int _idsLimit = kMinimalIdsLimit;
SparseIdsMergedSlice _slice;
std::unordered_map<UniversalMsgId, CachedItem> _layouts;
rpl::event_stream<not_null<BaseLayout*>> _layoutRemoved;
rpl::event_stream<> _refreshed;
rpl::lifetime _lifetime;
rpl::lifetime _viewerLifetime;
};
} // namespace Info::Media

View file

@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/media/info_media_inner_widget.h"
#include "info/info_controller.h"
#include "main/main_session.h"
#include "ui/widgets/scroll_area.h"
#include "ui/search_field_controller.h"
#include "ui/ui_utility.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "styles/style_info.h"
namespace Info {
@ -38,9 +40,13 @@ Type TabIndexToType(int index) {
Memento::Memento(not_null<Controller*> controller)
: Memento(
controller->peer(),
(controller->peer()
? controller->peer()
: controller->parentController()->session().user()),
controller->migratedPeerId(),
controller->section().mediaType()) {
(controller->section().type() == Section::Type::Downloads
? Type::File
: controller->section().mediaType())) {
}
Memento::Memento(not_null<PeerData*> peer, PeerId migratedPeerId, Type type)

View file

@ -40,6 +40,7 @@ public:
return _type;
}
// Only for media, not for downloads.
void setAroundId(FullMsgId aroundId) {
_aroundId = aroundId;
}
@ -52,10 +53,11 @@ public:
int idsLimit() const {
return _idsLimit;
}
void setScrollTopItem(FullMsgId item) {
void setScrollTopItem(GlobalMsgId item) {
_scrollTopItem = item;
}
FullMsgId scrollTopItem() const {
GlobalMsgId scrollTopItem() const {
return _scrollTopItem;
}
void setScrollTopShift(int shift) {
@ -75,7 +77,7 @@ private:
Type _type = Type::Photo;
FullMsgId _aroundId;
int _idsLimit = 0;
FullMsgId _scrollTopItem;
GlobalMsgId _scrollTopItem;
int _scrollTopShift = 0;
SearchState _searchState;

View file

@ -301,7 +301,7 @@ void Panel::refreshList() {
section().mediaType());
memento.setAroundId(contextId);
memento.setIdsLimit(kPlaylistIdsLimit);
memento.setScrollTopItem(contextId);
memento.setScrollTopItem({ contextId, peer->session().uniqueId() });
memento.setScrollTopShift(-st::infoMediaMargin.top());
weak->restoreState(&memento);
}

View file

@ -53,9 +53,9 @@ public:
TextSelection selection,
const PaintContext *context) = 0;
QDateTime dateTime() const;
[[nodiscard]] QDateTime dateTime() const;
HistoryItem *getItem() const {
[[nodiscard]] not_null<HistoryItem*> getItem() const {
return _parent;
}
@ -79,7 +79,7 @@ protected:
QPoint position,
bool selected,
const PaintContext *context);
virtual const style::RoundCheckbox &checkboxStyle() const;
[[nodiscard]] virtual const style::RoundCheckbox &checkboxStyle() const;
private:
void ensureCheckboxCreated();

View file

@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_adaptive.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "info/downloads/info_downloads_widget.h"
#include "info/info_memento.h"
#include "storage/localstorage.h"
#include "core/file_utilities.h"
#include "core/application.h"
@ -900,6 +902,8 @@ void SetupDataStorage(
st::settingsButton,
{ &st::settingsIconDownload, kIconPurple }
)->setClickedCallback([=] {
controller->showSection(
Info::Downloads::Make(controller->session().user()));
});
const auto ask = AddButton(