init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
object_ptr<Ui::FlatLabel> _text;
|
||||
int _height = 0;
|
||||
|
||||
};
|
||||
|
||||
EmptyWidget::EmptyWidget(QWidget *parent)
|
||||
: RpWidget(parent)
|
||||
, _text(this, st::infoEmptyLabel) {
|
||||
}
|
||||
|
||||
void EmptyWidget::setFullHeight(rpl::producer<int> fullHeightValue) {
|
||||
std::move(
|
||||
fullHeightValue
|
||||
) | rpl::on_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;
|
||||
}
|
||||
|
||||
void EmptyWidget::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
|
||||
const auto iconLeft = (width() - st::infoEmptyFile.width()) / 2;
|
||||
const auto iconTop = height() - st::infoEmptyIconTop;
|
||||
st::infoEmptyFile.paint(p, iconLeft, iconTop, width());
|
||||
}
|
||||
|
||||
InnerWidget::InnerWidget(
|
||||
QWidget *parent,
|
||||
not_null<Controller*> controller)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _empty(this) {
|
||||
_empty->heightValue(
|
||||
) | rpl::on_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::on_next(
|
||||
[this] { refreshHeight(); },
|
||||
result->lifetime());
|
||||
using namespace rpl::mappers;
|
||||
result->scrollToRequests(
|
||||
) | rpl::map([widget = result.data()](int to) {
|
||||
return Ui::ScrollToRequest {
|
||||
widget->y() + to,
|
||||
-1
|
||||
};
|
||||
}) | rpl::start_to_stream(
|
||||
_scrollToRequests,
|
||||
result->lifetime());
|
||||
_selectedLists.fire(result->selectedListValue());
|
||||
_listTops.fire(result->topValue());
|
||||
_controller->searchQueryValue(
|
||||
) | rpl::on_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::selectionAction(SelectionAction action) {
|
||||
_list->selectionAction(action);
|
||||
}
|
||||
|
||||
InnerWidget::~InnerWidget() = default;
|
||||
|
||||
int InnerWidget::resizeGetHeight(int newWidth) {
|
||||
_inResize = true;
|
||||
auto guard = gsl::finally([this] { _inResize = false; });
|
||||
|
||||
_list->resizeToWidth(newWidth);
|
||||
_empty->resizeToWidth(newWidth);
|
||||
return recountHeight();
|
||||
}
|
||||
|
||||
void InnerWidget::refreshHeight() {
|
||||
if (_inResize) {
|
||||
return;
|
||||
}
|
||||
resize(width(), recountHeight());
|
||||
}
|
||||
|
||||
int InnerWidget::recountHeight() {
|
||||
auto top = 0;
|
||||
auto listHeight = 0;
|
||||
if (_list) {
|
||||
_list->moveToLeft(0, top);
|
||||
listHeight = _list->heightNoMargins();
|
||||
top += listHeight;
|
||||
}
|
||||
if (listHeight > 0) {
|
||||
_empty->hide();
|
||||
} else {
|
||||
_empty->show();
|
||||
_empty->moveToLeft(0, top);
|
||||
top += _empty->heightNoMargins();
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
void InnerWidget::setScrollHeightValue(rpl::producer<int> value) {
|
||||
using namespace rpl::mappers;
|
||||
_empty->setFullHeight(rpl::combine(
|
||||
std::move(value),
|
||||
_listTops.events_starting_with(
|
||||
_list->topValue()
|
||||
) | rpl::flatten_latest(),
|
||||
_1 - _2));
|
||||
}
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
} // namespace Info::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 VerticalLayout;
|
||||
class SearchFieldController;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
|
||||
class Controller;
|
||||
struct SelectedItems;
|
||||
enum class SelectionAction;
|
||||
|
||||
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 selectionAction(SelectionAction action);
|
||||
|
||||
~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
|
||||
576
Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
Normal file
576
Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp
Normal file
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
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 "ui/text/format_song_document_name.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "data/data_download_manager.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_media_types.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "history/history.h"
|
||||
#include "core/application.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "layout/layout_selection.h"
|
||||
#include "styles/style_overview.h"
|
||||
|
||||
namespace Info::Downloads {
|
||||
namespace {
|
||||
|
||||
using namespace Media;
|
||||
|
||||
} // namespace
|
||||
|
||||
Provider::Provider(not_null<AbstractController*> controller)
|
||||
: _controller(controller)
|
||||
, _storiesAddToAlbumId(_controller->storiesAddToAlbumId()) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::on_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::sectionHasFloatingHeader() {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Provider::sectionTitle(not_null<const BaseLayout*> item) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool Provider::sectionItemBelongsHere(
|
||||
not_null<const BaseLayout*> item,
|
||||
not_null<const BaseLayout*> previous) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<int> Provider::fullCount() {
|
||||
return _queryWords.empty()
|
||||
? _fullCount
|
||||
: (_foundCount || _fullCount.has_value())
|
||||
? _foundCount
|
||||
: std::optional<int>();
|
||||
}
|
||||
|
||||
void Provider::restart() {
|
||||
}
|
||||
|
||||
void Provider::checkPreload(
|
||||
QSize viewport,
|
||||
not_null<BaseLayout*> topLayout,
|
||||
not_null<BaseLayout*> bottomLayout,
|
||||
bool preloadTop,
|
||||
bool preloadBottom) {
|
||||
}
|
||||
|
||||
void Provider::setSearchQuery(QString query) {
|
||||
if (_query == query) {
|
||||
return;
|
||||
}
|
||||
_query = query;
|
||||
auto words = TextUtilities::PrepareSearchWords(_query);
|
||||
if (!_started || _queryWords == words) {
|
||||
return;
|
||||
}
|
||||
_queryWords = std::move(words);
|
||||
if (searchMode()) {
|
||||
_foundCount = 0;
|
||||
for (auto &element : _elements) {
|
||||
if ((element.found = computeIsFound(element))) {
|
||||
++_foundCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
_refreshed.fire({});
|
||||
}
|
||||
|
||||
void Provider::refreshViewer() {
|
||||
if (_started) {
|
||||
return;
|
||||
}
|
||||
_started = true;
|
||||
auto &manager = Core::App().downloadManager();
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
manager.loadingListChanges() | rpl::to_empty
|
||||
) | rpl::on_next([=, &manager] {
|
||||
auto copy = _downloading;
|
||||
for (const auto id : manager.loadingList()) {
|
||||
if (!id->done) {
|
||||
const auto item = id->object.item;
|
||||
if (!copy.remove(item) && !_downloaded.contains(item)) {
|
||||
_downloading.emplace(item);
|
||||
addElementNow({
|
||||
.item = item,
|
||||
.started = id->started,
|
||||
.path = id->path,
|
||||
});
|
||||
trackItemSession(item);
|
||||
refreshPostponed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &item : copy) {
|
||||
Assert(!_downloaded.contains(item));
|
||||
remove(item);
|
||||
}
|
||||
if (!_fullCount.has_value()) {
|
||||
refreshPostponed(false);
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
for (const auto id : manager.loadedList()) {
|
||||
addPostponed(id);
|
||||
}
|
||||
|
||||
manager.loadedAdded(
|
||||
) | rpl::on_next([=](not_null<const Data::DownloadedId*> entry) {
|
||||
addPostponed(entry);
|
||||
}, _lifetime);
|
||||
|
||||
manager.loadedRemoved(
|
||||
) | rpl::on_next([=](not_null<const HistoryItem*> item) {
|
||||
if (!_downloading.contains(item)) {
|
||||
remove(item);
|
||||
} else {
|
||||
_downloaded.remove(item);
|
||||
_addPostponed.erase(
|
||||
ranges::remove(_addPostponed, item, &Element::item),
|
||||
end(_addPostponed));
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
manager.loadedResolveDone(
|
||||
) | rpl::on_next([=] {
|
||||
if (!_fullCount.has_value()) {
|
||||
_fullCount = 0;
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
performAdd();
|
||||
performRefresh();
|
||||
}
|
||||
|
||||
void Provider::addPostponed(not_null<const Data::DownloadedId*> entry) {
|
||||
Expects(entry->object != nullptr);
|
||||
|
||||
const auto item = entry->object->item;
|
||||
trackItemSession(item);
|
||||
const auto i = ranges::find(_addPostponed, item, &Element::item);
|
||||
if (i != end(_addPostponed)) {
|
||||
i->path = entry->path;
|
||||
i->started = entry->started;
|
||||
} else {
|
||||
_addPostponed.push_back({
|
||||
.item = item,
|
||||
.started = entry->started,
|
||||
.path = entry->path,
|
||||
});
|
||||
if (_addPostponed.size() == 1) {
|
||||
Ui::PostponeCall(this, [=] {
|
||||
performAdd();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::performAdd() {
|
||||
if (_addPostponed.empty()) {
|
||||
return;
|
||||
}
|
||||
for (auto &element : base::take(_addPostponed)) {
|
||||
_downloaded.emplace(element.item);
|
||||
if (!_downloading.remove(element.item)) {
|
||||
addElementNow(std::move(element));
|
||||
}
|
||||
}
|
||||
refreshPostponed(true);
|
||||
}
|
||||
|
||||
void Provider::addElementNow(Element &&element) {
|
||||
_elements.push_back(std::move(element));
|
||||
auto &added = _elements.back();
|
||||
fillSearchIndex(added);
|
||||
added.found = searchMode() && computeIsFound(added);
|
||||
if (added.found) {
|
||||
++_foundCount;
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::remove(not_null<const HistoryItem*> item) {
|
||||
_addPostponed.erase(
|
||||
ranges::remove(_addPostponed, item, &Element::item),
|
||||
end(_addPostponed));
|
||||
_downloading.remove(item);
|
||||
_downloaded.remove(item);
|
||||
const auto proj = [&](const Element &element) {
|
||||
if (element.item != item) {
|
||||
return false;
|
||||
} else if (element.found && searchMode()) {
|
||||
--_foundCount;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
_elements.erase(ranges::remove_if(_elements, proj), end(_elements));
|
||||
if (const auto i = _layouts.find(item); i != end(_layouts)) {
|
||||
_layoutRemoved.fire(i->second.item.get());
|
||||
_layouts.erase(i);
|
||||
}
|
||||
refreshPostponed(false);
|
||||
}
|
||||
|
||||
void Provider::refreshPostponed(bool added) {
|
||||
if (added) {
|
||||
_postponedRefreshSort = true;
|
||||
}
|
||||
if (!_postponedRefresh) {
|
||||
_postponedRefresh = true;
|
||||
Ui::PostponeCall(this, [=] {
|
||||
performRefresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::performRefresh() {
|
||||
if (!_postponedRefresh) {
|
||||
return;
|
||||
}
|
||||
_postponedRefresh = false;
|
||||
if (!_elements.empty() || _fullCount.has_value()) {
|
||||
_fullCount = _elements.size();
|
||||
}
|
||||
if (base::take(_postponedRefreshSort)) {
|
||||
ranges::sort(_elements, ranges::less(), &Element::started);
|
||||
}
|
||||
_refreshed.fire({});
|
||||
}
|
||||
|
||||
void Provider::trackItemSession(not_null<const HistoryItem*> item) {
|
||||
const auto session = &item->history()->session();
|
||||
if (_trackedSessions.contains(session)) {
|
||||
return;
|
||||
}
|
||||
auto &lifetime = _trackedSessions.emplace(session).first->second;
|
||||
|
||||
session->data().itemRemoved(
|
||||
) | rpl::on_next([this](auto item) {
|
||||
itemRemoved(item);
|
||||
}, lifetime);
|
||||
|
||||
session->account().sessionChanges(
|
||||
) | rpl::take(1) | rpl::on_next([=] {
|
||||
_trackedSessions.remove(session);
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
rpl::producer<> Provider::refreshed() {
|
||||
return _refreshed.events();
|
||||
}
|
||||
|
||||
std::vector<ListSection> Provider::fillSections(
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
const auto search = searchMode();
|
||||
|
||||
if (!search) {
|
||||
markLayoutsStale();
|
||||
}
|
||||
const auto guard = gsl::finally([&] { clearStaleLayouts(); });
|
||||
|
||||
if (_elements.empty() || (search && !_foundCount)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto result = std::vector<ListSection>();
|
||||
result.emplace_back(Type::File, sectionDelegate());
|
||||
auto §ion = result.back();
|
||||
for (const auto &element : ranges::views::reverse(_elements)) {
|
||||
if (search && !element.found) {
|
||||
continue;
|
||||
} else if (auto layout = getLayout(element, delegate)) {
|
||||
section.addItem(layout);
|
||||
}
|
||||
}
|
||||
section.finishSection();
|
||||
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) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Provider::isMyItem(not_null<const HistoryItem*> item) {
|
||||
return _downloading.contains(item) || _downloaded.contains(item);
|
||||
}
|
||||
|
||||
bool Provider::isAfter(
|
||||
not_null<const HistoryItem*> a,
|
||||
not_null<const HistoryItem*> b) {
|
||||
if (a != b) {
|
||||
for (const auto &element : _elements) {
|
||||
if (element.item == a) {
|
||||
return false;
|
||||
} else if (element.item == b) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Provider::searchMode() const {
|
||||
return !_queryWords.empty();
|
||||
}
|
||||
|
||||
void Provider::fillSearchIndex(Element &element) {
|
||||
auto strings = QStringList(QFileInfo(element.path).fileName());
|
||||
if (const auto media = element.item->media()) {
|
||||
if (const auto document = media->document()) {
|
||||
strings.append(document->filename());
|
||||
strings.append(Ui::Text::FormatDownloadsName(document).text);
|
||||
}
|
||||
}
|
||||
element.words = TextUtilities::PrepareSearchWords(strings.join(' '));
|
||||
element.letters.clear();
|
||||
for (const auto &word : element.words) {
|
||||
element.letters.emplace(word.front());
|
||||
}
|
||||
}
|
||||
|
||||
bool Provider::computeIsFound(const Element &element) const {
|
||||
Expects(!_queryWords.empty());
|
||||
|
||||
const auto has = [&](const QString &queryWord) {
|
||||
if (!element.letters.contains(queryWord.front())) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &word : element.words) {
|
||||
if (word.startsWith(queryWord)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (const auto &queryWord : _queryWords) {
|
||||
if (!has(queryWord)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Provider::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
remove(item);
|
||||
}
|
||||
|
||||
BaseLayout *Provider::getLayout(
|
||||
Element element,
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
auto it = _layouts.find(element.item);
|
||||
if (it == _layouts.end()) {
|
||||
if (auto layout = createLayout(element, delegate)) {
|
||||
layout->initDimensions();
|
||||
it = _layouts.emplace(element.item, std::move(layout)).first;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
it->second.stale = false;
|
||||
return it->second.item.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<BaseLayout> Provider::createLayout(
|
||||
Element element,
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
const auto getFile = [&]() -> DocumentData* {
|
||||
if (auto media = element.item->media()) {
|
||||
return media->document();
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
using namespace Overview::Layout;
|
||||
auto &songSt = st::overviewFileLayout;
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Document>(
|
||||
delegate,
|
||||
element.item,
|
||||
DocumentFields{
|
||||
.document = file,
|
||||
.dateOverride = Data::DateFromDownloadDate(element.started),
|
||||
.forceFileLayout = true,
|
||||
},
|
||||
songSt);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ListItemSelectionData Provider::computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) {
|
||||
auto result = ListItemSelectionData(selection);
|
||||
result.canDelete = true;
|
||||
result.canForward = item->allowsForward()
|
||||
&& (&item->history()->session() == &_controller->session());
|
||||
return result;
|
||||
}
|
||||
|
||||
void Provider::applyDragSelection(
|
||||
ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
bool skipFrom,
|
||||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) {
|
||||
auto from = ranges::find(_elements, fromItem, &Element::item);
|
||||
auto till = ranges::find(_elements, tillItem, &Element::item);
|
||||
if (from == end(_elements) || till == end(_elements)) {
|
||||
return;
|
||||
}
|
||||
if (skipFrom) {
|
||||
++from;
|
||||
}
|
||||
if (!skipTill) {
|
||||
++till;
|
||||
}
|
||||
if (from >= till) {
|
||||
selected.clear();
|
||||
return;
|
||||
}
|
||||
const auto search = !_queryWords.isEmpty();
|
||||
const auto selectLimit = _storiesAddToAlbumId
|
||||
? _controller->session().appConfig().storiesAlbumLimit()
|
||||
: MaxSelectedItems;
|
||||
auto chosen = base::flat_set<not_null<const HistoryItem*>>();
|
||||
chosen.reserve(till - from);
|
||||
for (auto i = from; i != till; ++i) {
|
||||
if (search && !i->found) {
|
||||
continue;
|
||||
}
|
||||
const auto item = i->item;
|
||||
chosen.emplace(item);
|
||||
ChangeItemSelection(
|
||||
selected,
|
||||
item,
|
||||
computeSelectionData(item, FullSelection),
|
||||
selectLimit);
|
||||
}
|
||||
if (selected.size() != chosen.size()) {
|
||||
for (auto i = begin(selected); i != end(selected);) {
|
||||
if (selected.contains(i->first)) {
|
||||
++i;
|
||||
} else {
|
||||
i = selected.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Provider::allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Provider::showInFolderPath(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
const auto i = ranges::find(_elements, item, &Element::item);
|
||||
return (i != end(_elements)) ? i->path : QString();
|
||||
}
|
||||
|
||||
int64 Provider::scrollTopStatePosition(not_null<HistoryItem*> item) {
|
||||
const auto i = ranges::find(_elements, item, &Element::item);
|
||||
return (i != end(_elements)) ? i->started : 0;
|
||||
}
|
||||
|
||||
HistoryItem *Provider::scrollTopStateItem(ListScrollTopState state) {
|
||||
if (!state.position) {
|
||||
return _elements.empty() ? nullptr : _elements.back().item.get();
|
||||
}
|
||||
const auto i = ranges::lower_bound(
|
||||
_elements,
|
||||
state.position,
|
||||
ranges::less(),
|
||||
&Element::started);
|
||||
return (i != end(_elements))
|
||||
? i->item.get()
|
||||
: _elements.empty()
|
||||
? nullptr
|
||||
: _elements.back().item.get();
|
||||
}
|
||||
|
||||
void Provider::saveState(
|
||||
not_null<Media::Memento*> memento,
|
||||
ListScrollTopState scrollState) {
|
||||
if (!_elements.empty() && scrollState.item) {
|
||||
memento->setAroundId({ PeerId(), 1 });
|
||||
memento->setScrollTopItem(scrollState.item->globalId());
|
||||
memento->setScrollTopItemPosition(scrollState.position);
|
||||
memento->setScrollTopShift(scrollState.shift);
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::restoreState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Fn<void(ListScrollTopState)> restoreScrollState) {
|
||||
if (memento->aroundId() == FullMsgId(PeerId(), 1)) {
|
||||
restoreScrollState({
|
||||
.position = memento->scrollTopItemPosition(),
|
||||
.item = MessageByGlobalId(memento->scrollTopItem()),
|
||||
.shift = memento->scrollTopShift(),
|
||||
});
|
||||
refreshViewer();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Info::Downloads
|
||||
155
Telegram/SourceFiles/info/downloads/info_downloads_provider.h
Normal file
155
Telegram/SourceFiles/info/downloads/info_downloads_provider.h
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "info/media/info_media_common.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
namespace Data {
|
||||
struct DownloadedId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Info {
|
||||
class AbstractController;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info::Downloads {
|
||||
|
||||
class Provider final
|
||||
: public Media::ListProvider
|
||||
, private Media::ListSectionDelegate
|
||||
, public base::has_weak_ptr {
|
||||
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;
|
||||
|
||||
void setSearchQuery(QString query) 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;
|
||||
|
||||
Media::ListItemSelectionData computeSelectionData(
|
||||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) override;
|
||||
void applyDragSelection(
|
||||
Media::ListSelectedMap &selected,
|
||||
not_null<const HistoryItem*> fromItem,
|
||||
bool skipFrom,
|
||||
not_null<const HistoryItem*> tillItem,
|
||||
bool skipTill) override;
|
||||
|
||||
bool allowSaveFileAs(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) override;
|
||||
QString showInFolderPath(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) override;
|
||||
|
||||
|
||||
int64 scrollTopStatePosition(not_null<HistoryItem*> item) override;
|
||||
HistoryItem *scrollTopStateItem(
|
||||
Media::ListScrollTopState state) override;
|
||||
void saveState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Media::ListScrollTopState scrollState) override;
|
||||
void restoreState(
|
||||
not_null<Media::Memento*> memento,
|
||||
Fn<void(Media::ListScrollTopState)> restoreScrollState) override;
|
||||
|
||||
private:
|
||||
struct Element {
|
||||
not_null<HistoryItem*> item;
|
||||
int64 started = 0; // unixtime * 1000
|
||||
QString path;
|
||||
|
||||
QStringList words;
|
||||
base::flat_set<QChar> letters;
|
||||
bool found = false;
|
||||
};
|
||||
|
||||
bool sectionHasFloatingHeader() override;
|
||||
QString sectionTitle(not_null<const Media::BaseLayout*> item) override;
|
||||
bool sectionItemBelongsHere(
|
||||
not_null<const Media::BaseLayout*> item,
|
||||
not_null<const Media::BaseLayout*> previous) override;
|
||||
|
||||
[[nodiscard]] bool searchMode() const;
|
||||
void fillSearchIndex(Element &element);
|
||||
[[nodiscard]] bool computeIsFound(const Element &element) const;
|
||||
|
||||
void itemRemoved(not_null<const HistoryItem*> item);
|
||||
void markLayoutsStale();
|
||||
void clearStaleLayouts();
|
||||
|
||||
void refreshPostponed(bool added);
|
||||
void addPostponed(not_null<const Data::DownloadedId*> entry);
|
||||
void performRefresh();
|
||||
void performAdd();
|
||||
void addElementNow(Element &&element);
|
||||
void remove(not_null<const HistoryItem*> item);
|
||||
void trackItemSession(not_null<const HistoryItem*> item);
|
||||
|
||||
[[nodiscard]] Media::BaseLayout *getLayout(
|
||||
Element element,
|
||||
not_null<Overview::Layout::Delegate*> delegate);
|
||||
[[nodiscard]] std::unique_ptr<Media::BaseLayout> createLayout(
|
||||
Element element,
|
||||
not_null<Overview::Layout::Delegate*> delegate);
|
||||
|
||||
const not_null<AbstractController*> _controller;
|
||||
|
||||
std::vector<Element> _elements;
|
||||
std::optional<int> _fullCount;
|
||||
base::flat_set<not_null<const HistoryItem*>> _downloading;
|
||||
base::flat_set<not_null<const HistoryItem*>> _downloaded;
|
||||
int _storiesAddToAlbumId = 0;
|
||||
|
||||
std::vector<Element> _addPostponed;
|
||||
|
||||
std::unordered_map<
|
||||
not_null<const HistoryItem*>,
|
||||
Media::CachedItem> _layouts;
|
||||
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
|
||||
rpl::event_stream<> _refreshed;
|
||||
|
||||
QString _query;
|
||||
QStringList _queryWords;
|
||||
int _foundCount = 0;
|
||||
|
||||
base::flat_map<not_null<Main::Session*>, rpl::lifetime> _trackedSessions;
|
||||
bool _postponedRefreshSort = false;
|
||||
bool _postponedRefresh = false;
|
||||
bool _started = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Downloads
|
||||
144
Telegram/SourceFiles/info/downloads/info_downloads_widget.cpp
Normal file
144
Telegram/SourceFiles/info/downloads/info_downloads_widget.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/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/boxes/confirm_box.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "data/data_download_manager.h"
|
||||
#include "data/data_user.h"
|
||||
#include "core/application.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace Info::Downloads {
|
||||
|
||||
Memento::Memento(not_null<Controller*> controller)
|
||||
: ContentMemento(Tag{})
|
||||
, _media(controller) {
|
||||
}
|
||||
|
||||
Memento::Memento(not_null<UserData*> self)
|
||||
: ContentMemento(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));
|
||||
_inner->setScrollHeightValue(scrollHeightValue());
|
||||
_inner->scrollToRequests(
|
||||
) | rpl::on_next([this](Ui::ScrollToRequest request) {
|
||||
scrollTo(request);
|
||||
}, _inner->lifetime());
|
||||
}
|
||||
|
||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||
if (auto 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());
|
||||
}
|
||||
|
||||
rpl::producer<SelectedItems> Widget::selectedListValue() const {
|
||||
return _inner->selectedListValue();
|
||||
}
|
||||
|
||||
void Widget::selectionAction(SelectionAction action) {
|
||||
_inner->selectionAction(action);
|
||||
}
|
||||
|
||||
void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
|
||||
const auto window = controller()->parentController();
|
||||
const auto deleteAll = [=] {
|
||||
auto &manager = Core::App().downloadManager();
|
||||
const auto phrase = tr::lng_downloads_delete_sure_all(tr::now);
|
||||
const auto added = manager.loadedHasNonCloudFile()
|
||||
? QString()
|
||||
: tr::lng_downloads_delete_in_cloud(tr::now);
|
||||
const auto deleteSure = [=, &manager](Fn<void()> close) {
|
||||
Ui::PostponeCall(this, close);
|
||||
manager.deleteAll();
|
||||
};
|
||||
window->show(Ui::MakeConfirmBox({
|
||||
.text = phrase + (added.isEmpty() ? QString() : "\n\n" + added),
|
||||
.confirmed = deleteSure,
|
||||
.confirmText = tr::lng_box_delete(tr::now),
|
||||
.confirmStyle = &st::attentionBoxButton,
|
||||
}));
|
||||
};
|
||||
addAction(
|
||||
tr::lng_context_delete_all_files(tr::now),
|
||||
deleteAll,
|
||||
&st::menuIconDelete);
|
||||
}
|
||||
|
||||
rpl::producer<QString> Widget::title() {
|
||||
return tr::lng_downloads_section();
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
76
Telegram/SourceFiles/info/downloads/info_downloads_widget.h
Normal file
76
Telegram/SourceFiles/info/downloads/info_downloads_widget.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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);
|
||||
|
||||
rpl::producer<SelectedItems> selectedListValue() const override;
|
||||
void selectionAction(SelectionAction action) override;
|
||||
|
||||
void fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) override;
|
||||
|
||||
rpl::producer<QString> title() override;
|
||||
|
||||
private:
|
||||
void saveState(not_null<Memento*> memento);
|
||||
void restoreState(not_null<Memento*> memento);
|
||||
|
||||
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||
|
||||
InnerWidget *_inner = nullptr;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Info::Memento> Make(not_null<UserData*> self);
|
||||
|
||||
} // namespace Info::Downloads
|
||||
Reference in New Issue
Block a user