mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-07 15:43:55 +02:00
Allow Info::Media::ListWidget use different data providers.
This commit is contained in:
parent
eefb9823e1
commit
91c46dbc85
36 changed files with 2491 additions and 1237 deletions
|
@ -712,13 +712,25 @@ PRIVATE
|
||||||
info/common_groups/info_common_groups_inner_widget.h
|
info/common_groups/info_common_groups_inner_widget.h
|
||||||
info/common_groups/info_common_groups_widget.cpp
|
info/common_groups/info_common_groups_widget.cpp
|
||||||
info/common_groups/info_common_groups_widget.h
|
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_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.cpp
|
||||||
info/media/info_media_empty_widget.h
|
info/media/info_media_empty_widget.h
|
||||||
info/media/info_media_inner_widget.cpp
|
info/media/info_media_inner_widget.cpp
|
||||||
info/media/info_media_inner_widget.h
|
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.cpp
|
||||||
info/media/info_media_list_widget.h
|
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.cpp
|
||||||
info/media/info_media_widget.h
|
info/media/info_media_widget.h
|
||||||
info/members/info_members_widget.cpp
|
info/members/info_members_widget.cpp
|
||||||
|
|
|
@ -24,31 +24,31 @@ public:
|
||||||
, _skippedAfter(skippedAfter) {
|
, _skippedAfter(skippedAfter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int> fullCount() const {
|
[[nodiscard]] std::optional<int> fullCount() const {
|
||||||
return _fullCount;
|
return _fullCount;
|
||||||
}
|
}
|
||||||
std::optional<int> skippedBefore() const {
|
[[nodiscard]] std::optional<int> skippedBefore() const {
|
||||||
return _skippedBefore;
|
return _skippedBefore;
|
||||||
}
|
}
|
||||||
std::optional<int> skippedAfter() const {
|
[[nodiscard]] std::optional<int> skippedAfter() const {
|
||||||
return _skippedAfter;
|
return _skippedAfter;
|
||||||
}
|
}
|
||||||
std::optional<int> indexOf(Id id) const {
|
[[nodiscard]] std::optional<int> indexOf(Id id) const {
|
||||||
const auto it = ranges::find(_ids, id);
|
const auto it = ranges::find(_ids, id);
|
||||||
if (it != _ids.end()) {
|
if (it != _ids.end()) {
|
||||||
return (it - _ids.begin());
|
return (it - _ids.begin());
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
int size() const {
|
[[nodiscard]] int size() const {
|
||||||
return _ids.size();
|
return _ids.size();
|
||||||
}
|
}
|
||||||
Id operator[](int index) const {
|
[[nodiscard]] Id operator[](int index) const {
|
||||||
Expects(index >= 0 && index < size());
|
Expects(index >= 0 && index < size());
|
||||||
|
|
||||||
return *(_ids.begin() + index);
|
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 i = indexOf(a)) {
|
||||||
if (const auto j = indexOf(b)) {
|
if (const auto j = indexOf(b)) {
|
||||||
return *j - *i;
|
return *j - *i;
|
||||||
|
@ -56,7 +56,8 @@ public:
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
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()) {
|
if (const auto it = ranges::lower_bound(_ids, id); it != _ids.end()) {
|
||||||
return *it;
|
return *it;
|
||||||
} else if (_ids.empty()) {
|
} else if (_ids.empty()) {
|
||||||
|
|
|
@ -181,6 +181,60 @@ struct FullMsgId {
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(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 {
|
namespace std {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|
|
@ -28,22 +28,22 @@ SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||||
|
|
||||||
SparseIdsMergedSlice::SparseIdsMergedSlice(
|
SparseIdsMergedSlice::SparseIdsMergedSlice(
|
||||||
Key key,
|
Key key,
|
||||||
SparseUnsortedIdsSlice scheduled)
|
SparseUnsortedIdsSlice unsorted)
|
||||||
: _key(key)
|
: _key(key)
|
||||||
, _scheduled(std::move(scheduled)) {
|
, _unsorted(std::move(unsorted)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int> SparseIdsMergedSlice::fullCount() const {
|
std::optional<int> SparseIdsMergedSlice::fullCount() const {
|
||||||
return _scheduled
|
return _unsorted
|
||||||
? _scheduled->fullCount()
|
? _unsorted->fullCount()
|
||||||
: Add(
|
: Add(
|
||||||
_part.fullCount(),
|
_part.fullCount(),
|
||||||
_migrated ? _migrated->fullCount() : 0);
|
_migrated ? _migrated->fullCount() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
|
std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
|
||||||
return _scheduled
|
return _unsorted
|
||||||
? _scheduled->skippedBefore()
|
? _unsorted->skippedBefore()
|
||||||
: Add(
|
: Add(
|
||||||
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
isolatedInMigrated() ? 0 : _part.skippedBefore(),
|
||||||
_migrated
|
_migrated
|
||||||
|
@ -55,8 +55,8 @@ std::optional<int> SparseIdsMergedSlice::skippedBefore() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
|
std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
|
||||||
return _scheduled
|
return _unsorted
|
||||||
? _scheduled->skippedAfter()
|
? _unsorted->skippedAfter()
|
||||||
: Add(
|
: Add(
|
||||||
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
isolatedInMigrated() ? _part.fullCount() : _part.skippedAfter(),
|
||||||
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
isolatedInPart() ? 0 : _migrated->skippedAfter()
|
||||||
|
@ -65,8 +65,8 @@ std::optional<int> SparseIdsMergedSlice::skippedAfter() const {
|
||||||
|
|
||||||
std::optional<int> SparseIdsMergedSlice::indexOf(
|
std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||||
FullMsgId fullId) const {
|
FullMsgId fullId) const {
|
||||||
return _scheduled
|
return _unsorted
|
||||||
? _scheduled->indexOf(fullId.msg)
|
? _unsorted->indexOf(fullId.msg)
|
||||||
: isFromPart(fullId)
|
: isFromPart(fullId)
|
||||||
? (_part.indexOf(fullId.msg) | func::add(migratedSize()))
|
? (_part.indexOf(fullId.msg) | func::add(migratedSize()))
|
||||||
: isolatedInPart()
|
: isolatedInPart()
|
||||||
|
@ -77,8 +77,8 @@ std::optional<int> SparseIdsMergedSlice::indexOf(
|
||||||
}
|
}
|
||||||
|
|
||||||
int SparseIdsMergedSlice::size() const {
|
int SparseIdsMergedSlice::size() const {
|
||||||
return _scheduled
|
return _unsorted
|
||||||
? _scheduled->size()
|
? _unsorted->size()
|
||||||
: (isolatedInPart() ? 0 : migratedSize())
|
: (isolatedInPart() ? 0 : migratedSize())
|
||||||
+ (isolatedInMigrated() ? 0 : _part.size());
|
+ (isolatedInMigrated() ? 0 : _part.size());
|
||||||
}
|
}
|
||||||
|
@ -86,8 +86,8 @@ int SparseIdsMergedSlice::size() const {
|
||||||
FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
FullMsgId SparseIdsMergedSlice::operator[](int index) const {
|
||||||
Expects(index >= 0 && index < size());
|
Expects(index >= 0 && index < size());
|
||||||
|
|
||||||
if (_scheduled) {
|
if (_unsorted) {
|
||||||
return ComputeId(_key.peerId, (*_scheduled)[index]);
|
return ComputeId(_key.peerId, (*_unsorted)[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto size = migratedSize()) {
|
if (const auto size = migratedSize()) {
|
||||||
|
@ -112,10 +112,13 @@ std::optional<int> SparseIdsMergedSlice::distance(
|
||||||
|
|
||||||
auto SparseIdsMergedSlice::nearest(
|
auto SparseIdsMergedSlice::nearest(
|
||||||
UniversalMsgId id) const -> std::optional<FullMsgId> {
|
UniversalMsgId id) const -> std::optional<FullMsgId> {
|
||||||
if (_scheduled) {
|
if (_unsorted) {
|
||||||
if (const auto nearestId = _scheduled->nearest(id)) {
|
if (_unsorted->indexOf(id).has_value()) {
|
||||||
return ComputeId(_key.peerId, *nearestId);
|
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) {
|
const auto convertFromPartNearest = [&](MsgId result) {
|
||||||
return ComputeId(_key.peerId, result);
|
return ComputeId(_key.peerId, result);
|
||||||
|
|
|
@ -62,7 +62,7 @@ public:
|
||||||
std::optional<SparseIdsSlice> migrated);
|
std::optional<SparseIdsSlice> migrated);
|
||||||
SparseIdsMergedSlice(
|
SparseIdsMergedSlice(
|
||||||
Key key,
|
Key key,
|
||||||
SparseUnsortedIdsSlice scheduled);
|
SparseUnsortedIdsSlice unsorted);
|
||||||
|
|
||||||
std::optional<int> fullCount() const;
|
std::optional<int> fullCount() const;
|
||||||
std::optional<int> skippedBefore() const;
|
std::optional<int> skippedBefore() const;
|
||||||
|
@ -139,7 +139,7 @@ private:
|
||||||
Key _key;
|
Key _key;
|
||||||
SparseIdsSlice _part;
|
SparseIdsSlice _part;
|
||||||
std::optional<SparseIdsSlice> _migrated;
|
std::optional<SparseIdsSlice> _migrated;
|
||||||
std::optional<SparseUnsortedIdsSlice> _scheduled;
|
std::optional<SparseUnsortedIdsSlice> _unsorted;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "storage/file_upload.h"
|
#include "storage/file_upload.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
#include "storage/storage_shared_media.h"
|
#include "storage/storage_shared_media.h"
|
||||||
|
#include "main/main_account.h"
|
||||||
|
#include "main/main_domain.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
|
@ -1017,6 +1019,10 @@ FullMsgId HistoryItem::fullId() const {
|
||||||
return FullMsgId(_history->peer->id, id);
|
return FullMsgId(_history->peer->id, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GlobalMsgId HistoryItem::globalId() const {
|
||||||
|
return { fullId(), _history->session().uniqueId() };
|
||||||
|
}
|
||||||
|
|
||||||
Data::MessagePosition HistoryItem::position() const {
|
Data::MessagePosition HistoryItem::position() const {
|
||||||
return { .fullId = fullId(), .date = date() };
|
return { .fullId = fullId(), .date = date() };
|
||||||
}
|
}
|
||||||
|
@ -1301,6 +1307,20 @@ HistoryItem::~HistoryItem() {
|
||||||
applyTTL(0);
|
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) {
|
QDateTime ItemDateTime(not_null<const HistoryItem*> item) {
|
||||||
return base::unixtime::parse(item->date());
|
return base::unixtime::parse(item->date());
|
||||||
}
|
}
|
||||||
|
|
|
@ -380,6 +380,7 @@ public:
|
||||||
[[nodiscard]] bool hasDirectLink() const;
|
[[nodiscard]] bool hasDirectLink() const;
|
||||||
|
|
||||||
[[nodiscard]] FullMsgId fullId() const;
|
[[nodiscard]] FullMsgId fullId() const;
|
||||||
|
[[nodiscard]] GlobalMsgId globalId() const;
|
||||||
[[nodiscard]] Data::MessagePosition position() const;
|
[[nodiscard]] Data::MessagePosition position() const;
|
||||||
[[nodiscard]] TimeId date() const;
|
[[nodiscard]] TimeId date() const;
|
||||||
|
|
||||||
|
@ -494,9 +495,14 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QDateTime ItemDateTime(not_null<const HistoryItem*> item);
|
[[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId);
|
||||||
QString ItemDateText(not_null<const HistoryItem*> item, bool isUntilOnline);
|
|
||||||
bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item);
|
[[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(
|
ClickHandlerPtr goToMessageClickHandler(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
|
|
|
@ -282,4 +282,3 @@ void InnerWidget::peerListSetDescription(
|
||||||
|
|
||||||
} // namespace CommonGroups
|
} // namespace CommonGroups
|
||||||
} // namespace Info
|
} // namespace Info
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
123
Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
Normal file
123
Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
Normal 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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
69
Telegram/SourceFiles/info/downloads/info_downloads_widget.h
Normal file
69
Telegram/SourceFiles/info/downloads/info_downloads_widget.h
Normal 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
|
|
@ -262,8 +262,10 @@ Key ContentMemento::key() const {
|
||||||
return Key(peer);
|
return Key(peer);
|
||||||
} else if (const auto poll = this->poll()) {
|
} else if (const auto poll = this->poll()) {
|
||||||
return Key(poll, pollContextId());
|
return Key(poll, pollContextId());
|
||||||
|
} else if (const auto self = settingsSelf()) {
|
||||||
|
return Settings::Tag{ self };
|
||||||
} else {
|
} else {
|
||||||
return Settings::Tag{ settingsSelf() };
|
return Downloads::Tag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,4 +273,7 @@ ContentMemento::ContentMemento(Settings::Tag settings)
|
||||||
: _settingsSelf(settings.self.get()) {
|
: _settingsSelf(settings.self.get()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentMemento::ContentMemento(Downloads::Tag downloads) {
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Info
|
} // namespace Info
|
||||||
|
|
|
@ -28,6 +28,10 @@ namespace Settings {
|
||||||
struct Tag;
|
struct Tag;
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
||||||
|
namespace Downloads {
|
||||||
|
struct Tag;
|
||||||
|
} // namespace Downloads
|
||||||
|
|
||||||
class ContentMemento;
|
class ContentMemento;
|
||||||
class Controller;
|
class Controller;
|
||||||
|
|
||||||
|
@ -120,6 +124,7 @@ public:
|
||||||
, _migratedPeerId(migratedPeerId) {
|
, _migratedPeerId(migratedPeerId) {
|
||||||
}
|
}
|
||||||
explicit ContentMemento(Settings::Tag settings);
|
explicit ContentMemento(Settings::Tag settings);
|
||||||
|
explicit ContentMemento(Downloads::Tag downloads);
|
||||||
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
: _poll(poll)
|
: _poll(poll)
|
||||||
, _pollContextId(contextId) {
|
, _pollContextId(contextId) {
|
||||||
|
|
|
@ -14,13 +14,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/info_content_widget.h"
|
#include "info/info_content_widget.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
#include "info/media/info_media_widget.h"
|
#include "info/media/info_media_widget.h"
|
||||||
|
#include "core/application.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
|
#include "data/data_download_manager.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "history/history.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "window/window_session_controller.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(Settings::Tag settings) : _value(settings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Key::Key(Downloads::Tag downloads) : _value(downloads) {
|
||||||
|
}
|
||||||
|
|
||||||
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
: _value(PollKey{ poll, contextId }) {
|
: _value(PollKey{ poll, contextId }) {
|
||||||
}
|
}
|
||||||
|
@ -50,6 +56,10 @@ UserData *Key::settingsSelf() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Key::isDownloads() const {
|
||||||
|
return v::is<Downloads::Tag>(_value);
|
||||||
|
}
|
||||||
|
|
||||||
PollData *Key::poll() const {
|
PollData *Key::poll() const {
|
||||||
if (const auto data = std::get_if<PollKey>(&_value)) {
|
if (const auto data = std::get_if<PollKey>(&_value)) {
|
||||||
return data->poll;
|
return data->poll;
|
||||||
|
@ -98,6 +108,28 @@ rpl::producer<QString> AbstractController::mediaSourceQueryValue() const {
|
||||||
return rpl::single(QString());
|
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(
|
AbstractController::AbstractController(
|
||||||
not_null<Window::SessionController*> parent)
|
not_null<Window::SessionController*> parent)
|
||||||
: SessionNavigation(&parent->session())
|
: SessionNavigation(&parent->session())
|
||||||
|
@ -204,17 +236,19 @@ void Controller::setSection(not_null<ContentMemento*> memento) {
|
||||||
void Controller::updateSearchControllers(
|
void Controller::updateSearchControllers(
|
||||||
not_null<ContentMemento*> memento) {
|
not_null<ContentMemento*> memento) {
|
||||||
using Type = Section::Type;
|
using Type = Section::Type;
|
||||||
auto type = _section.type();
|
const auto type = _section.type();
|
||||||
auto isMedia = (type == Type::Media);
|
const auto isMedia = (type == Type::Media);
|
||||||
auto mediaType = isMedia
|
const auto mediaType = isMedia
|
||||||
? _section.mediaType()
|
? _section.mediaType()
|
||||||
: Section::MediaType::kCount;
|
: Section::MediaType::kCount;
|
||||||
auto hasMediaSearch = isMedia
|
const auto hasMediaSearch = isMedia
|
||||||
&& SharedMediaAllowSearch(mediaType);
|
&& SharedMediaAllowSearch(mediaType);
|
||||||
auto hasCommonGroupsSearch
|
const auto hasCommonGroupsSearch
|
||||||
= (type == Type::CommonGroups);
|
= (type == Type::CommonGroups);
|
||||||
auto hasMembersSearch = (type == Type::Members || type == Type::Profile);
|
const auto hasMembersSearch = (type == Type::Members)
|
||||||
auto searchQuery = memento->searchFieldQuery();
|
|| (type == Type::Profile);
|
||||||
|
const auto searchQuery = memento->searchFieldQuery();
|
||||||
|
const auto isDownloads = (type == Type::Downloads);
|
||||||
if (isMedia) {
|
if (isMedia) {
|
||||||
_searchController
|
_searchController
|
||||||
= std::make_unique<Api::DelayedSearchController>(&session());
|
= std::make_unique<Api::DelayedSearchController>(&session());
|
||||||
|
@ -289,7 +323,9 @@ rpl::producer<bool> Controller::searchEnabledByContent() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<QString> Controller::mediaSourceQueryValue() 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(
|
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(
|
||||||
|
|
|
@ -28,14 +28,23 @@ struct Tag {
|
||||||
|
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
||||||
|
namespace Downloads {
|
||||||
|
|
||||||
|
struct Tag {
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Downloads
|
||||||
|
|
||||||
class Key {
|
class Key {
|
||||||
public:
|
public:
|
||||||
Key(not_null<PeerData*> peer);
|
Key(not_null<PeerData*> peer);
|
||||||
Key(Settings::Tag settings);
|
Key(Settings::Tag settings);
|
||||||
|
Key(Downloads::Tag downloads);
|
||||||
Key(not_null<PollData*> poll, FullMsgId contextId);
|
Key(not_null<PollData*> poll, FullMsgId contextId);
|
||||||
|
|
||||||
PeerData *peer() const;
|
PeerData *peer() const;
|
||||||
UserData *settingsSelf() const;
|
UserData *settingsSelf() const;
|
||||||
|
bool isDownloads() const;
|
||||||
PollData *poll() const;
|
PollData *poll() const;
|
||||||
FullMsgId pollContextId() const;
|
FullMsgId pollContextId() const;
|
||||||
|
|
||||||
|
@ -47,6 +56,7 @@ private:
|
||||||
std::variant<
|
std::variant<
|
||||||
not_null<PeerData*>,
|
not_null<PeerData*>,
|
||||||
Settings::Tag,
|
Settings::Tag,
|
||||||
|
Downloads::Tag,
|
||||||
PollKey> _value;
|
PollKey> _value;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -64,6 +74,7 @@ public:
|
||||||
CommonGroups,
|
CommonGroups,
|
||||||
Members,
|
Members,
|
||||||
Settings,
|
Settings,
|
||||||
|
Downloads,
|
||||||
PollResults,
|
PollResults,
|
||||||
};
|
};
|
||||||
using SettingsType = ::Settings::Type;
|
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 {
|
class AbstractController : public Window::SessionNavigation {
|
||||||
public:
|
public:
|
||||||
AbstractController(not_null<Window::SessionController*> parent);
|
AbstractController(not_null<Window::SessionController*> parent);
|
||||||
|
@ -115,6 +134,9 @@ public:
|
||||||
UserData *settingsSelf() const {
|
UserData *settingsSelf() const {
|
||||||
return key().settingsSelf();
|
return key().settingsSelf();
|
||||||
}
|
}
|
||||||
|
bool isDownloads() const {
|
||||||
|
return key().isDownloads();
|
||||||
|
}
|
||||||
PollData *poll() const;
|
PollData *poll() const;
|
||||||
FullMsgId pollContextId() const {
|
FullMsgId pollContextId() const {
|
||||||
return key().pollContextId();
|
return key().pollContextId();
|
||||||
|
@ -128,6 +150,8 @@ public:
|
||||||
int limitAfter) const;
|
int limitAfter) const;
|
||||||
virtual rpl::producer<QString> mediaSourceQueryValue() const;
|
virtual rpl::producer<QString> mediaSourceQueryValue() const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<DownloadsSlice> downloadsSource() const;
|
||||||
|
|
||||||
void showSection(
|
void showSection(
|
||||||
std::shared_ptr<Window::SectionMemento> memento,
|
std::shared_ptr<Window::SectionMemento> memento,
|
||||||
const Window::SectionShow ¶ms = Window::SectionShow()) override;
|
const Window::SectionShow ¶ms = Window::SectionShow()) override;
|
||||||
|
|
|
@ -27,6 +27,10 @@ namespace Settings {
|
||||||
struct Tag;
|
struct Tag;
|
||||||
} // namespace Settings
|
} // namespace Settings
|
||||||
|
|
||||||
|
namespace Downloads {
|
||||||
|
struct Tag;
|
||||||
|
} // namespace Downloads
|
||||||
|
|
||||||
class ContentMemento;
|
class ContentMemento;
|
||||||
class WrapWidget;
|
class WrapWidget;
|
||||||
|
|
||||||
|
|
|
@ -545,9 +545,13 @@ MessageIdsList TopBar::collectItems() const {
|
||||||
return ranges::views::all(
|
return ranges::views::all(
|
||||||
_selectedItems.list
|
_selectedItems.list
|
||||||
) | ranges::views::transform([](auto &&item) {
|
) | ranges::views::transform([](auto &&item) {
|
||||||
return item.msgId;
|
return item.globalId;
|
||||||
}) | ranges::views::filter([&](FullMsgId msgId) {
|
}) | ranges::views::filter([&](const GlobalMsgId &globalId) {
|
||||||
return _navigation->session().data().message(msgId) != nullptr;
|
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;
|
}) | ranges::to_vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,6 +572,7 @@ void TopBar::performForward() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TopBar::performDelete() {
|
void TopBar::performDelete() {
|
||||||
|
// #TODO downloads
|
||||||
auto items = collectItems();
|
auto items = collectItems();
|
||||||
if (items.empty()) {
|
if (items.empty()) {
|
||||||
_cancelSelectionClicks.fire({});
|
_cancelSelectionClicks.fire({});
|
||||||
|
@ -667,6 +672,10 @@ rpl::producer<QString> TitleValue(
|
||||||
return key.poll()->quiz()
|
return key.poll()->quiz()
|
||||||
? tr::lng_polls_quiz_results_title()
|
? tr::lng_polls_quiz_results_title()
|
||||||
: tr::lng_polls_poll_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()");
|
Unexpected("Bad section type in Info::TitleValue()");
|
||||||
}
|
}
|
||||||
|
|
|
@ -871,13 +871,19 @@ void WrapWidget::showNewContent(
|
||||||
_historyStack.clear();
|
_historyStack.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
_controller = std::move(newController);
|
{
|
||||||
if (newContent) {
|
// Let old controller outlive old content widget.
|
||||||
setupTop();
|
const auto oldController = std::exchange(
|
||||||
showContent(std::move(newContent));
|
_controller,
|
||||||
} else {
|
std::move(newController));
|
||||||
showNewContent(memento);
|
if (newContent) {
|
||||||
|
setupTop();
|
||||||
|
showContent(std::move(newContent));
|
||||||
|
} else {
|
||||||
|
showNewContent(memento);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (animationParams) {
|
if (animationParams) {
|
||||||
if (Ui::InFocusChain(this)) {
|
if (Ui::InFocusChain(this)) {
|
||||||
setFocus();
|
setFocus();
|
||||||
|
|
|
@ -51,10 +51,10 @@ enum class Wrap {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SelectedItem {
|
struct SelectedItem {
|
||||||
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) {
|
explicit SelectedItem(GlobalMsgId globalId) : globalId(globalId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
FullMsgId msgId;
|
GlobalMsgId globalId;
|
||||||
bool canDelete = false;
|
bool canDelete = false;
|
||||||
bool canForward = false;
|
bool canForward = false;
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,8 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
namespace Info {
|
namespace Info::Media {
|
||||||
namespace Media {
|
|
||||||
|
|
||||||
using Type = Storage::SharedMediaType;
|
using Type = Storage::SharedMediaType;
|
||||||
|
|
||||||
|
@ -118,5 +117,4 @@ inline auto AddCommonGroupsButton(
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Media
|
} // namespace Info::Media
|
||||||
} // namespace Info
|
|
||||||
|
|
54
Telegram/SourceFiles/info/media/info_media_common.cpp
Normal file
54
Telegram/SourceFiles/info/media/info_media_common.cpp
Normal 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
|
139
Telegram/SourceFiles/info/media/info_media_common.h
Normal file
139
Telegram/SourceFiles/info/media/info_media_common.h
Normal 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
|
486
Telegram/SourceFiles/info/media/info_media_list_section.cpp
Normal file
486
Telegram/SourceFiles/info/media/info_media_list_section.cpp
Normal 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
|
93
Telegram/SourceFiles/info/media/info_media_list_section.h
Normal file
93
Telegram/SourceFiles/info/media/info_media_list_section.h
Normal 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
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "info/media/info_media_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"
|
#include "overview/overview_layout_delegate.h"
|
||||||
|
|
||||||
class DeleteMessagesBox;
|
class DeleteMessagesBox;
|
||||||
|
@ -45,8 +45,10 @@ class AbstractController;
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
|
|
||||||
using BaseLayout = Overview::Layout::ItemBase;
|
struct ListFoundItem;
|
||||||
using UniversalMsgId = MsgId;
|
struct ListContext;
|
||||||
|
class ListSection;
|
||||||
|
class ListProvider;
|
||||||
|
|
||||||
class ListWidget final
|
class ListWidget final
|
||||||
: public Ui::RpWidget
|
: public Ui::RpWidget
|
||||||
|
@ -89,12 +91,15 @@ public:
|
||||||
bool showInMediaView = false) override;
|
bool showInMediaView = false) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Context;
|
|
||||||
struct DateBadge;
|
struct DateBadge;
|
||||||
class Section;
|
using Section = ListSection;
|
||||||
|
using FoundItem = ListFoundItem;
|
||||||
using CursorState = HistoryView::CursorState;
|
using CursorState = HistoryView::CursorState;
|
||||||
using TextState = HistoryView::TextState;
|
using TextState = HistoryView::TextState;
|
||||||
using StateRequest = HistoryView::StateRequest;
|
using StateRequest = HistoryView::StateRequest;
|
||||||
|
using SelectionData = ListItemSelectionData;
|
||||||
|
using SelectedMap = ListSelectedMap;
|
||||||
|
using DragSelectAction = ListDragSelectAction;
|
||||||
enum class MouseAction {
|
enum class MouseAction {
|
||||||
None,
|
None,
|
||||||
PrepareDrag,
|
PrepareDrag,
|
||||||
|
@ -102,45 +107,14 @@ private:
|
||||||
PrepareSelect,
|
PrepareSelect,
|
||||||
Selecting,
|
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 {
|
struct MouseState {
|
||||||
UniversalMsgId itemId = 0;
|
HistoryItem *item = nullptr;
|
||||||
QSize size;
|
QSize size;
|
||||||
QPoint cursor;
|
QPoint cursor;
|
||||||
bool inside = false;
|
bool inside = false;
|
||||||
|
|
||||||
inline bool operator==(const MouseState &other) const {
|
inline bool operator==(const MouseState &other) const {
|
||||||
return (itemId == other.itemId)
|
return (item == other.item)
|
||||||
&& (cursor == other.cursor);
|
&& (cursor == other.cursor);
|
||||||
}
|
}
|
||||||
inline bool operator!=(const MouseState &other) const {
|
inline bool operator!=(const MouseState &other) const {
|
||||||
|
@ -153,10 +127,6 @@ private:
|
||||||
Touch,
|
Touch,
|
||||||
Other,
|
Other,
|
||||||
};
|
};
|
||||||
struct ScrollTopState {
|
|
||||||
UniversalMsgId item = 0;
|
|
||||||
int shift = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
void visibleTopBottomUpdated(
|
void visibleTopBottomUpdated(
|
||||||
|
@ -175,97 +145,83 @@ private:
|
||||||
void start();
|
void start();
|
||||||
int recountHeight();
|
int recountHeight();
|
||||||
void refreshHeight();
|
void refreshHeight();
|
||||||
|
void subscribeToSession(not_null<Main::Session*> session);
|
||||||
|
|
||||||
void setupSelectRestriction();
|
void setupSelectRestriction();
|
||||||
[[nodiscard]] bool hasSelectRestriction() const;
|
|
||||||
|
|
||||||
QMargins padding() const;
|
QMargins padding() const;
|
||||||
bool isMyItem(not_null<const HistoryItem*> item) const;
|
bool isMyItem(not_null<const HistoryItem*> item) const;
|
||||||
bool isItemLayout(
|
bool isItemLayout(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const HistoryItem*> item,
|
||||||
BaseLayout *layout) const;
|
BaseLayout *layout) const;
|
||||||
bool isPossiblyMyId(FullMsgId fullId) const;
|
|
||||||
void repaintItem(const HistoryItem *item);
|
void repaintItem(const HistoryItem *item);
|
||||||
void repaintItem(UniversalMsgId msgId);
|
|
||||||
void repaintItem(const BaseLayout *item);
|
void repaintItem(const BaseLayout *item);
|
||||||
void repaintItem(QRect itemGeometry);
|
void repaintItem(QRect itemGeometry);
|
||||||
void itemRemoved(not_null<const HistoryItem*> item);
|
void itemRemoved(not_null<const HistoryItem*> item);
|
||||||
void itemLayoutChanged(not_null<const HistoryItem*> item);
|
void itemLayoutChanged(not_null<const HistoryItem*> item);
|
||||||
|
|
||||||
void refreshViewer();
|
void refreshViewer();
|
||||||
void invalidatePaletteCache();
|
|
||||||
void refreshRows();
|
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;
|
[[nodiscard]] SelectedItems collectSelectedItems() const;
|
||||||
MessageIdsList collectSelectedIds() const;
|
[[nodiscard]] MessageIdsList collectSelectedIds() const;
|
||||||
void pushSelectedItems();
|
void pushSelectedItems();
|
||||||
FullMsgId computeFullId(UniversalMsgId universalId) const;
|
[[nodiscard]] bool hasSelected() const;
|
||||||
bool hasSelected() const;
|
[[nodiscard]] bool isSelectedItem(
|
||||||
bool isSelectedItem(
|
|
||||||
const SelectedMap::const_iterator &i) const;
|
const SelectedMap::const_iterator &i) const;
|
||||||
void removeItemSelection(
|
void removeItemSelection(
|
||||||
const SelectedMap::const_iterator &i);
|
const SelectedMap::const_iterator &i);
|
||||||
bool hasSelectedText() const;
|
[[nodiscard]] bool hasSelectedText() const;
|
||||||
bool hasSelectedItems() const;
|
[[nodiscard]] bool hasSelectedItems() const;
|
||||||
void clearSelected();
|
void clearSelected();
|
||||||
void forwardSelected();
|
void forwardSelected();
|
||||||
void forwardItem(UniversalMsgId universalId);
|
void forwardItem(GlobalMsgId globalId);
|
||||||
void forwardItems(MessageIdsList &&items);
|
void forwardItems(MessageIdsList &&items);
|
||||||
void deleteSelected();
|
void deleteSelected();
|
||||||
void deleteItem(UniversalMsgId universalId);
|
void deleteItem(GlobalMsgId globalId);
|
||||||
DeleteMessagesBox *deleteItems(MessageIdsList &&items);
|
DeleteMessagesBox *deleteItems(MessageIdsList &&items);
|
||||||
void applyItemSelection(
|
void applyItemSelection(
|
||||||
UniversalMsgId universalId,
|
HistoryItem *item,
|
||||||
TextSelection selection);
|
TextSelection selection);
|
||||||
void toggleItemSelection(
|
void toggleItemSelection(not_null<HistoryItem*> item);
|
||||||
UniversalMsgId universalId);
|
[[nodiscard]] SelectedMap::iterator itemUnderPressSelection();
|
||||||
SelectedMap::iterator itemUnderPressSelection();
|
[[nodiscard]] auto itemUnderPressSelection() const
|
||||||
SelectedMap::const_iterator itemUnderPressSelection() const;
|
-> SelectedMap::const_iterator;
|
||||||
bool isItemUnderPressSelected() const;
|
bool isItemUnderPressSelected() const;
|
||||||
bool requiredToStartDragging(not_null<BaseLayout*> layout) const;
|
[[nodiscard]] bool requiredToStartDragging(
|
||||||
bool isPressInSelectedText(TextState state) const;
|
not_null<BaseLayout*> layout) const;
|
||||||
|
[[nodiscard]] bool isPressInSelectedText(TextState state) const;
|
||||||
void applyDragSelection();
|
void applyDragSelection();
|
||||||
void applyDragSelection(SelectedMap &applyTo) const;
|
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 &a,
|
||||||
const MouseState &b);
|
const MouseState &b) const;
|
||||||
static bool SkipSelectFromItem(const MouseState &state);
|
[[nodiscard]] static bool SkipSelectFromItem(const MouseState &state);
|
||||||
static bool SkipSelectTillItem(const MouseState &state);
|
[[nodiscard]] static bool SkipSelectTillItem(const MouseState &state);
|
||||||
|
|
||||||
void markLayoutsStale();
|
[[nodiscard]] std::vector<Section>::iterator findSectionByItem(
|
||||||
void clearStaleLayouts();
|
not_null<const HistoryItem*> item);
|
||||||
std::vector<Section>::iterator findSectionByItem(
|
[[nodiscard]] std::vector<Section>::iterator findSectionAfterTop(
|
||||||
UniversalMsgId universalId);
|
int top);
|
||||||
std::vector<Section>::iterator findSectionAfterTop(int top);
|
[[nodiscard]] std::vector<Section>::const_iterator findSectionAfterTop(
|
||||||
std::vector<Section>::const_iterator findSectionAfterTop(
|
|
||||||
int top) const;
|
int top) const;
|
||||||
std::vector<Section>::const_iterator findSectionAfterBottom(
|
[[nodiscard]] auto findSectionAfterBottom(
|
||||||
std::vector<Section>::const_iterator from,
|
std::vector<Section>::const_iterator from,
|
||||||
int bottom) const;
|
int bottom) const -> std::vector<Section>::const_iterator;
|
||||||
FoundItem findItemByPoint(QPoint point) const;
|
[[nodiscard]] FoundItem findItemByPoint(QPoint point) const;
|
||||||
std::optional<FoundItem> findItemById(UniversalMsgId universalId);
|
[[nodiscard]] std::optional<FoundItem> findItemByItem(
|
||||||
FoundItem findItemDetails(not_null<BaseLayout*> item);
|
const HistoryItem *item);
|
||||||
FoundItem foundItemInSection(
|
[[nodiscard]] FoundItem findItemDetails(not_null<BaseLayout*> item);
|
||||||
|
[[nodiscard]] FoundItem foundItemInSection(
|
||||||
const FoundItem &item,
|
const FoundItem &item,
|
||||||
const Section §ion) const;
|
const Section §ion) const;
|
||||||
|
|
||||||
ScrollTopState countScrollState() const;
|
[[nodiscard]] ListScrollTopState countScrollState() const;
|
||||||
void saveScrollState();
|
void saveScrollState();
|
||||||
void restoreScrollState();
|
void restoreScrollState();
|
||||||
|
|
||||||
QPoint clampMousePosition(QPoint position) const;
|
[[nodiscard]] QPoint clampMousePosition(QPoint position) const;
|
||||||
void mouseActionStart(
|
void mouseActionStart(
|
||||||
const QPoint &globalPosition,
|
const QPoint &globalPosition,
|
||||||
Qt::MouseButton button);
|
Qt::MouseButton button);
|
||||||
|
@ -276,7 +232,7 @@ private:
|
||||||
Qt::MouseButton button);
|
Qt::MouseButton button);
|
||||||
void mouseActionCancel();
|
void mouseActionCancel();
|
||||||
void performDrag();
|
void performDrag();
|
||||||
style::cursor computeMouseCursor() const;
|
[[nodiscard]] style::cursor computeMouseCursor() const;
|
||||||
void showContextMenu(
|
void showContextMenu(
|
||||||
QContextMenuEvent *e,
|
QContextMenuEvent *e,
|
||||||
ContextMenuSource source);
|
ContextMenuSource source);
|
||||||
|
@ -298,24 +254,15 @@ private:
|
||||||
void setActionBoxWeak(QPointer<Ui::RpWidget> box);
|
void setActionBoxWeak(QPointer<Ui::RpWidget> box);
|
||||||
|
|
||||||
const not_null<AbstractController*> _controller;
|
const not_null<AbstractController*> _controller;
|
||||||
const not_null<PeerData*> _peer;
|
const std::unique_ptr<ListProvider> _provider;
|
||||||
PeerData * const _migrated = nullptr;
|
|
||||||
const Type _type = Type::Photo;
|
|
||||||
|
|
||||||
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;
|
base::flat_set<not_null<const BaseLayout*>> _heavyLayouts;
|
||||||
bool _heavyLayoutsInvalidated = false;
|
bool _heavyLayoutsInvalidated = false;
|
||||||
std::vector<Section> _sections;
|
std::vector<Section> _sections;
|
||||||
|
|
||||||
int _visibleTop = 0;
|
int _visibleTop = 0;
|
||||||
int _visibleBottom = 0;
|
int _visibleBottom = 0;
|
||||||
ScrollTopState _scrollTopState;
|
ListScrollTopState _scrollTopState;
|
||||||
rpl::event_stream<int> _scrollToRequests;
|
rpl::event_stream<int> _scrollToRequests;
|
||||||
|
|
||||||
MouseAction _mouseAction = MouseAction::None;
|
MouseAction _mouseAction = MouseAction::None;
|
||||||
|
@ -324,7 +271,7 @@ private:
|
||||||
MouseState _overState;
|
MouseState _overState;
|
||||||
MouseState _pressState;
|
MouseState _pressState;
|
||||||
BaseLayout *_overLayout = nullptr;
|
BaseLayout *_overLayout = nullptr;
|
||||||
UniversalMsgId _contextUniversalId = 0;
|
HistoryItem *_contextItem = nullptr;
|
||||||
CursorState _mouseCursorState = CursorState();
|
CursorState _mouseCursorState = CursorState();
|
||||||
uint16 _mouseTextSymbol = 0;
|
uint16 _mouseTextSymbol = 0;
|
||||||
bool _pressWasInactive = false;
|
bool _pressWasInactive = false;
|
||||||
|
@ -345,8 +292,6 @@ private:
|
||||||
QPoint _trippleClickPoint;
|
QPoint _trippleClickPoint;
|
||||||
crl::time _trippleClickStartTime = 0;
|
crl::time _trippleClickStartTime = 0;
|
||||||
|
|
||||||
rpl::lifetime _viewerLifetime;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Media
|
} // namespace Media
|
||||||
|
|
450
Telegram/SourceFiles/info/media/info_media_provider.cpp
Normal file
450
Telegram/SourceFiles/info/media/info_media_provider.cpp
Normal 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
|
103
Telegram/SourceFiles/info/media/info_media_provider.h
Normal file
103
Telegram/SourceFiles/info/media/info_media_provider.h
Normal 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
|
|
@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "info/media/info_media_inner_widget.h"
|
#include "info/media/info_media_inner_widget.h"
|
||||||
#include "info/info_controller.h"
|
#include "info/info_controller.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "ui/search_field_controller.h"
|
#include "ui/search_field_controller.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
namespace Info {
|
namespace Info {
|
||||||
|
@ -38,9 +40,13 @@ Type TabIndexToType(int index) {
|
||||||
|
|
||||||
Memento::Memento(not_null<Controller*> controller)
|
Memento::Memento(not_null<Controller*> controller)
|
||||||
: Memento(
|
: Memento(
|
||||||
controller->peer(),
|
(controller->peer()
|
||||||
|
? controller->peer()
|
||||||
|
: controller->parentController()->session().user()),
|
||||||
controller->migratedPeerId(),
|
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)
|
Memento::Memento(not_null<PeerData*> peer, PeerId migratedPeerId, Type type)
|
||||||
|
|
|
@ -40,6 +40,7 @@ public:
|
||||||
return _type;
|
return _type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only for media, not for downloads.
|
||||||
void setAroundId(FullMsgId aroundId) {
|
void setAroundId(FullMsgId aroundId) {
|
||||||
_aroundId = aroundId;
|
_aroundId = aroundId;
|
||||||
}
|
}
|
||||||
|
@ -52,10 +53,11 @@ public:
|
||||||
int idsLimit() const {
|
int idsLimit() const {
|
||||||
return _idsLimit;
|
return _idsLimit;
|
||||||
}
|
}
|
||||||
void setScrollTopItem(FullMsgId item) {
|
|
||||||
|
void setScrollTopItem(GlobalMsgId item) {
|
||||||
_scrollTopItem = item;
|
_scrollTopItem = item;
|
||||||
}
|
}
|
||||||
FullMsgId scrollTopItem() const {
|
GlobalMsgId scrollTopItem() const {
|
||||||
return _scrollTopItem;
|
return _scrollTopItem;
|
||||||
}
|
}
|
||||||
void setScrollTopShift(int shift) {
|
void setScrollTopShift(int shift) {
|
||||||
|
@ -75,7 +77,7 @@ private:
|
||||||
Type _type = Type::Photo;
|
Type _type = Type::Photo;
|
||||||
FullMsgId _aroundId;
|
FullMsgId _aroundId;
|
||||||
int _idsLimit = 0;
|
int _idsLimit = 0;
|
||||||
FullMsgId _scrollTopItem;
|
GlobalMsgId _scrollTopItem;
|
||||||
int _scrollTopShift = 0;
|
int _scrollTopShift = 0;
|
||||||
SearchState _searchState;
|
SearchState _searchState;
|
||||||
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ void Panel::refreshList() {
|
||||||
section().mediaType());
|
section().mediaType());
|
||||||
memento.setAroundId(contextId);
|
memento.setAroundId(contextId);
|
||||||
memento.setIdsLimit(kPlaylistIdsLimit);
|
memento.setIdsLimit(kPlaylistIdsLimit);
|
||||||
memento.setScrollTopItem(contextId);
|
memento.setScrollTopItem({ contextId, peer->session().uniqueId() });
|
||||||
memento.setScrollTopShift(-st::infoMediaMargin.top());
|
memento.setScrollTopShift(-st::infoMediaMargin.top());
|
||||||
weak->restoreState(&memento);
|
weak->restoreState(&memento);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,9 @@ public:
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
const PaintContext *context) = 0;
|
const PaintContext *context) = 0;
|
||||||
|
|
||||||
QDateTime dateTime() const;
|
[[nodiscard]] QDateTime dateTime() const;
|
||||||
|
|
||||||
HistoryItem *getItem() const {
|
[[nodiscard]] not_null<HistoryItem*> getItem() const {
|
||||||
return _parent;
|
return _parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ protected:
|
||||||
QPoint position,
|
QPoint position,
|
||||||
bool selected,
|
bool selected,
|
||||||
const PaintContext *context);
|
const PaintContext *context);
|
||||||
virtual const style::RoundCheckbox &checkboxStyle() const;
|
[[nodiscard]] virtual const style::RoundCheckbox &checkboxStyle() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ensureCheckboxCreated();
|
void ensureCheckboxCreated();
|
||||||
|
|
|
@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_adaptive.h"
|
#include "window/window_adaptive.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
|
#include "info/downloads/info_downloads_widget.h"
|
||||||
|
#include "info/info_memento.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "core/file_utilities.h"
|
#include "core/file_utilities.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
@ -900,6 +902,8 @@ void SetupDataStorage(
|
||||||
st::settingsButton,
|
st::settingsButton,
|
||||||
{ &st::settingsIconDownload, kIconPurple }
|
{ &st::settingsIconDownload, kIconPurple }
|
||||||
)->setClickedCallback([=] {
|
)->setClickedCallback([=] {
|
||||||
|
controller->showSection(
|
||||||
|
Info::Downloads::Make(controller->session().user()));
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto ask = AddButton(
|
const auto ask = AddButton(
|
||||||
|
|
Loading…
Add table
Reference in a new issue