/* 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/saved/info_saved_music_provider.h" #include "base/unixtime.h" #include "core/application.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_media_types.h" #include "data/data_saved_music.h" #include "data/data_session.h" #include "history/history_item.h" #include "history/history_item_helpers.h" #include "history/history.h" #include "info/media/info_media_widget.h" #include "info/media/info_media_list_section.h" #include "info/info_controller.h" #include "main/main_account.h" #include "main/main_session.h" #include "layout/layout_selection.h" #include "storage/storage_shared_media.h" #include "styles/style_info.h" #include "styles/style_overview.h" namespace Info::Saved { namespace { using namespace Media; constexpr auto kPreloadedScreensCount = 4; constexpr auto kPreloadedScreensCountFull = kPreloadedScreensCount + 1 + kPreloadedScreensCount; } // namespace MusicProvider::MusicProvider(not_null controller) : _controller(controller) , _peer(controller->key().musicPeer()) , _history(_peer->owner().history(_peer)) { _controller->session().data().itemRemoved( ) | rpl::on_next([this](auto item) { itemRemoved(item); }, _lifetime); style::PaletteChanged( ) | rpl::on_next([=] { for (auto &layout : _layouts) { layout.second.item->invalidateCache(); } }, _lifetime); } MusicProvider::~MusicProvider() { clear(); } Type MusicProvider::type() { return Type::MusicFile; } bool MusicProvider::hasSelectRestriction() { //if (_peer->session().frozen()) { // return true; //} return true; } rpl::producer MusicProvider::hasSelectRestrictionChanges() { return rpl::never(); } bool MusicProvider::sectionHasFloatingHeader() { return false; } QString MusicProvider::sectionTitle(not_null item) { return QString(); } bool MusicProvider::sectionItemBelongsHere( not_null item, not_null previous) { return true; } bool MusicProvider::isPossiblyMyItem(not_null item) { return true; } std::optional MusicProvider::fullCount() { return _slice.fullCount(); } void MusicProvider::clear() { _layouts.clear(); _aroundId = nullptr; _idsLimit = kMinimalIdsLimit; _slice = Data::SavedMusicSlice(); } void MusicProvider::restart() { clear(); refreshViewer(); } void MusicProvider::checkPreload( QSize viewport, not_null topLayout, not_null 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::MusicFile, 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 minIdDelta = (minScreenDelta * visibleHeight) / minItemHeight; const auto preloadAroundItem = [&](not_null layout) { auto preloadRequired = false; const auto item = layout->getItem(); if (!preloadRequired) { preloadRequired = (_idsLimit < preloadIdsLimitMin); } if (!preloadRequired) { auto delta = _slice.distance(_aroundId, item); Assert(delta != std::nullopt); preloadRequired = (qAbs(*delta) >= minIdDelta); } if (preloadRequired) { _idsLimit = preloadIdsLimit; _aroundId = item; refreshViewer(); } }; if (preloadTop && !topLoaded) { preloadAroundItem(topLayout); } else if (preloadBottom && !bottomLoaded) { preloadAroundItem(bottomLayout); } } void MusicProvider::setSearchQuery(QString query) { } void MusicProvider::refreshViewer() { _viewerLifetime.destroy(); const auto aroundId = _aroundId; auto ids = Data::SavedMusicList(_peer, aroundId, _idsLimit); std::move( ids ) | rpl::on_next([=](Data::SavedMusicSlice &&slice) { if (!slice.fullCount()) { // Don't display anything while full count is unknown. return; } _slice = std::move(slice); auto nearestId = (HistoryItem*)nullptr; for (auto i = 0; i != _slice.size(); ++i) { if (_slice[i] == aroundId) { nearestId = aroundId; break; } } if (!nearestId && _slice.size() > 0) { _aroundId = _slice[_slice.size() / 2]; } _refreshed.fire({}); }, _viewerLifetime); } rpl::producer<> MusicProvider::refreshed() { return _refreshed.events(); } std::vector MusicProvider::fillSections( not_null delegate) { markLayoutsStale(); const auto guard = gsl::finally([&] { clearStaleLayouts(); }); auto result = std::vector(); auto section = ListSection(Type::MusicFile, sectionDelegate()); auto count = _slice.size(); for (auto i = 0; i != count; ++i) { const auto item = _slice[i]; if (const auto layout = getLayout(item, delegate)) { if (!section.addItem(layout)) { section.finishSection(); result.push_back(std::move(section)); section = ListSection(Type::MusicFile, sectionDelegate()); section.addItem(layout); } } } if (!section.empty()) { section.finishSection(); result.push_back(std::move(section)); } return result; } void MusicProvider::itemRemoved(not_null item) { if (const auto i = _layouts.find(item); i != end(_layouts)) { _layoutRemoved.fire(i->second.item.get()); _layouts.erase(i); } } void MusicProvider::markLayoutsStale() { for (auto &layout : _layouts) { layout.second.stale = true; } } void MusicProvider::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> MusicProvider::layoutRemoved() { return _layoutRemoved.events(); } BaseLayout *MusicProvider::lookupLayout(const HistoryItem *item) { return nullptr; } bool MusicProvider::isMyItem(not_null item) { return item->isSavedMusicItem() && (item->history()->peer == _peer); } bool MusicProvider::isAfter( not_null a, not_null b) { return (a->id < b->id); } BaseLayout *MusicProvider::getLayout( not_null item, not_null delegate) { auto it = _layouts.find(item); if (it == _layouts.end()) { if (auto layout = createLayout(item, delegate)) { layout->initDimensions(); it = _layouts.emplace(item, std::move(layout)).first; } else { return nullptr; } } it->second.stale = false; return it->second.item.get(); } std::unique_ptr MusicProvider::createLayout( not_null item, not_null delegate) { using namespace Overview::Layout; if (const auto media = item->media()) { if (const auto file = media->document()) { return std::make_unique( delegate, item, DocumentFields{ file }, st::overviewFileLayout); } } return nullptr; } ListItemSelectionData MusicProvider::computeSelectionData( not_null item, TextSelection selection) { auto result = ListItemSelectionData(selection); result.canDelete = item->history()->peer->isSelf(); result.canForward = true;// item->allowsForward(); return result; } void MusicProvider::applyDragSelection( ListSelectedMap &selected, not_null fromItem, bool skipFrom, not_null tillItem, bool skipTill) { const auto fromId = fromItem->id - (skipFrom ? 1 : 0); const auto tillId = tillItem->id - (skipTill ? 0 : 1); for (auto i = selected.begin(); i != selected.end();) { const auto itemId = i->first->id; if (itemId > fromId || itemId <= tillId) { i = selected.erase(i); } else { ++i; } } for (auto &layoutItem : _layouts) { const auto item = layoutItem.first; if (item->id <= fromId && item->id > tillId) { ChangeItemSelection( selected, item, computeSelectionData(item, FullSelection)); } } } bool MusicProvider::allowSaveFileAs( not_null item, not_null document) { return true; } QString MusicProvider::showInFolderPath( not_null item, not_null document) { return document->filepath(true); } int64 MusicProvider::scrollTopStatePosition(not_null item) { return item->id.bare; } HistoryItem *MusicProvider::scrollTopStateItem(ListScrollTopState state) { if (state.item && _slice.indexOf(state.item)) { return state.item; //} else if (const auto id = _slice.nearest(state.position)) { // const auto full = FullMsgId(_peer->id, StoryIdToMsgId(*id)); // if (const auto item = _controller->session().data().message(full)) { // return item; // } } auto nearestId = (HistoryItem*)nullptr; //AssertIsDebug(); //for (auto i = 0; i != _slice.size(); ++i) { // if (!nearestId // || std::abs(*nearestId - state.position) // > std::abs(_slice[i] - state.position)) { // nearestId = _slice[i]; // } //} if (nearestId) { const auto full = nearestId->fullId(); if (const auto item = _controller->session().data().message(full)) { return item; } } return state.item; } void MusicProvider::saveState( not_null memento, ListScrollTopState scrollState) { if (_aroundId != nullptr && scrollState.item) { //AssertIsDebug(); //memento->setAroundId({ _peer->id, StoryIdToMsgId(_aroundId) }); //memento->setIdsLimit(_idsLimit); //memento->setScrollTopItem(scrollState.item->globalId()); //memento->setScrollTopItemPosition(scrollState.position); //memento->setScrollTopShift(scrollState.shift); } } void MusicProvider::restoreState( not_null memento, Fn restoreScrollState) { if (const auto limit = memento->idsLimit()) { const auto wasAroundId = memento->aroundId(); if (wasAroundId.peer == _peer->id) { _idsLimit = limit; //AssertIsDebug(); //_aroundId = StoryIdFromMsgId(wasAroundId.msg); restoreScrollState({ .position = memento->scrollTopItemPosition(), .item = MessageByGlobalId(memento->scrollTopItem()), .shift = memento->scrollTopShift(), }); refreshViewer(); } } } } // namespace Info::Saved