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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
/*
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_common.h"
#include "data/data_peer.h"
#include "data/data_saved_music.h"
#include "data/data_saved_sublist.h"
#include "history/history_item.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/profile/info_profile_music_button.h"
#include "info/saved/info_saved_music_widget.h"
#include "ui/text/format_song_document_name.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/vertical_list.h"
namespace Info::Saved {
namespace {
[[nodiscard]] Profile::MusicButtonData DocumentMusicButtonData(
not_null<DocumentData*> document) {
return { Ui::Text::FormatSongNameFor(document) };
}
} // namespace
void SetupSavedMusic(
not_null<Ui::VerticalLayout*> container,
not_null<Info::Controller*> controller,
not_null<PeerData*> peer,
rpl::producer<std::optional<QColor>> topBarColor) {
auto musicValue = Data::SavedMusic::Supported(peer->id)
? Data::SavedMusicList(
peer,
nullptr,
1
) | rpl::map([=](const Data::SavedMusicSlice &data) {
return data.size() ? data[0].get() : nullptr;
}) | rpl::type_erased
: rpl::single<HistoryItem*>((HistoryItem*)(nullptr));
const auto divider = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
rpl::combine(
std::move(musicValue),
std::move(topBarColor)
) | rpl::on_next([=](
HistoryItem *item,
std::optional<QColor> color) {
while (divider->entity()->count()) {
delete divider->entity()->widgetAt(0);
}
if (item) {
if (const auto document = item->media()
? item->media()->document()
: nullptr) {
const auto music = divider->entity()->add(
object_ptr<Profile::MusicButton>(
divider->entity(),
DocumentMusicButtonData(document),
[window = controller, peer] {
window->showSection(Info::Saved::MakeMusic(peer));
}));
music->setOverrideBg(color);
}
divider->toggle(true, anim::type::normal);
}
}, container->lifetime());
divider->finishAnimating();
}
} // namespace Info::Saved

View File

@@ -0,0 +1,36 @@
/*
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
class PeerData;
namespace Ui {
class VerticalLayout;
} // namespace Ui
namespace Info {
class Controller;
} // namespace Info
namespace Info::Saved {
struct MusicTag {
explicit MusicTag(not_null<PeerData*> peer)
: peer(peer) {
}
not_null<PeerData*> peer;
};
void SetupSavedMusic(
not_null<Ui::VerticalLayout*> container,
not_null<Info::Controller*> controller,
not_null<PeerData*> peer,
rpl::producer<std::optional<QColor>> topBarColor);
} // namespace Info::Saved

View File

@@ -0,0 +1,408 @@
/*
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<AbstractController*> 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<bool> MusicProvider::hasSelectRestrictionChanges() {
return rpl::never<bool>();
}
bool MusicProvider::sectionHasFloatingHeader() {
return false;
}
QString MusicProvider::sectionTitle(not_null<const BaseLayout*> item) {
return QString();
}
bool MusicProvider::sectionItemBelongsHere(
not_null<const BaseLayout*> item,
not_null<const BaseLayout*> previous) {
return true;
}
bool MusicProvider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
return true;
}
std::optional<int> 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<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::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<BaseLayout*> 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<ListSection> MusicProvider::fillSections(
not_null<Overview::Layout::Delegate*> delegate) {
markLayoutsStale();
const auto guard = gsl::finally([&] { clearStaleLayouts(); });
auto result = std::vector<ListSection>();
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<const HistoryItem*> 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<not_null<BaseLayout*>> MusicProvider::layoutRemoved() {
return _layoutRemoved.events();
}
BaseLayout *MusicProvider::lookupLayout(const HistoryItem *item) {
return nullptr;
}
bool MusicProvider::isMyItem(not_null<const HistoryItem*> item) {
return item->isSavedMusicItem() && (item->history()->peer == _peer);
}
bool MusicProvider::isAfter(
not_null<const HistoryItem*> a,
not_null<const HistoryItem*> b) {
return (a->id < b->id);
}
BaseLayout *MusicProvider::getLayout(
not_null<HistoryItem*> item,
not_null<Overview::Layout::Delegate*> 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<BaseLayout> MusicProvider::createLayout(
not_null<HistoryItem*> item,
not_null<Overview::Layout::Delegate*> delegate) {
using namespace Overview::Layout;
if (const auto media = item->media()) {
if (const auto file = media->document()) {
return std::make_unique<Document>(
delegate,
item,
DocumentFields{ file },
st::overviewFileLayout);
}
}
return nullptr;
}
ListItemSelectionData MusicProvider::computeSelectionData(
not_null<const HistoryItem*> 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<const HistoryItem*> fromItem,
bool skipFrom,
not_null<const HistoryItem*> 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<const HistoryItem*> item,
not_null<DocumentData*> document) {
return true;
}
QString MusicProvider::showInFolderPath(
not_null<const HistoryItem*> item,
not_null<DocumentData*> document) {
return document->filepath(true);
}
int64 MusicProvider::scrollTopStatePosition(not_null<HistoryItem*> 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<Media::Memento*> 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<Media::Memento*> memento,
Fn<void(ListScrollTopState)> 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

View File

@@ -0,0 +1,132 @@
/*
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 "base/weak_ptr.h"
#include "data/data_saved_music.h"
#include "info/media/info_media_common.h"
#include "info/saved/info_saved_music_common.h"
#include "history/history_item.h"
class DocumentData;
class HistoryItem;
class PeerData;
class History;
namespace Info {
class AbstractController;
} // namespace Info
namespace Info::Saved {
class MusicProvider final
: public Media::ListProvider
, private Media::ListSectionDelegate
, public base::has_weak_ptr {
public:
explicit MusicProvider(not_null<AbstractController*> controller);
~MusicProvider();
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:
static constexpr auto kMinimalIdsLimit = 16;
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;
void itemRemoved(not_null<const HistoryItem*> item);
void markLayoutsStale();
void clearStaleLayouts();
void clear();
[[nodiscard]] Media::BaseLayout *getLayout(
not_null<HistoryItem*> item,
not_null<Overview::Layout::Delegate*> delegate);
[[nodiscard]] std::unique_ptr<Media::BaseLayout> createLayout(
not_null<HistoryItem*> item,
not_null<Overview::Layout::Delegate*> delegate);
const not_null<AbstractController*> _controller;
const not_null<PeerData*> _peer;
const not_null<History*> _history;
HistoryItem *_aroundId = nullptr;
int _idsLimit = kMinimalIdsLimit;
Data::SavedMusicSlice _slice;
std::unordered_map<
not_null<const HistoryItem*>,
Media::CachedItem> _layouts;
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
rpl::event_stream<> _refreshed;
bool _started = false;
rpl::lifetime _lifetime;
rpl::lifetime _viewerLifetime;
};
} // namespace Info::Saved

View File

@@ -0,0 +1,354 @@
/*
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_widget.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_saved_music.h"
#include "data/data_session.h"
#include "info/media/info_media_list_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "main/main_session.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "lang/lang_keys.h"
#include "ui/ui_utility.h"
#include "styles/style_credits.h" // giftListAbout
#include "styles/style_info.h"
#include "styles/style_layers.h"
namespace Info::Saved {
class MusicInner final : public Ui::RpWidget {
public:
MusicInner(QWidget *parent, not_null<Controller*> controller);
~MusicInner();
bool showInternal(not_null<MusicMemento*> memento);
void setIsStackBottom(bool isStackBottom) {
_isStackBottom = isStackBottom;
}
void saveState(not_null<MusicMemento*> memento);
void restoreState(not_null<MusicMemento*> memento);
void setScrollHeightValue(rpl::producer<int> value);
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
rpl::producer<SelectedItems> selectedListValue() const;
void selectionAction(SelectionAction action);
protected:
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override;
private:
int recountHeight();
void refreshHeight();
void refreshEmpty();
void setupList();
void setupEmpty();
const not_null<Controller*> _controller;
const not_null<PeerData*> _peer;
base::unique_qptr<Ui::PopupMenu> _menu;
object_ptr<Media::ListWidget> _list = { nullptr };
object_ptr<Ui::RpWidget> _empty = { nullptr };
int _lastNonLoadingHeight = 0;
bool _emptyLoading = false;
bool _inResize = false;
bool _isStackBottom = false;
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
rpl::event_stream<rpl::producer<int>> _listTops;
rpl::variable<int> _topHeight;
rpl::variable<bool> _albumEmpty;
};
MusicInner::MusicInner(QWidget *parent, not_null<Controller*> controller)
: RpWidget(parent)
, _controller(controller)
, _peer(controller->key().musicPeer()) {
setupList();
setupEmpty();
}
MusicInner::~MusicInner() = default;
void MusicInner::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
}
bool MusicInner::showInternal(not_null<MusicMemento*> memento) {
if (memento->section().type() == Section::Type::SavedMusic) {
restoreState(memento);
return true;
}
return false;
}
void MusicInner::setupList() {
Expects(!_list);
_list = object_ptr<Media::ListWidget>(this, _controller);
if (_peer->isSelf()) {
_list->setReorderDescriptor({
.save = [=](
int oldPosition,
int newPosition,
Fn<void()> done,
Fn<void()> fail) {
_controller->session().data().savedMusic().reorder(
oldPosition,
newPosition);
done();
}
});
}
const auto raw = _list.data();
using namespace rpl::mappers;
raw->scrollToRequests(
) | rpl::map([=](int to) {
return Ui::ScrollToRequest {
raw->y() + to,
-1
};
}) | rpl::start_to_stream(_scrollToRequests, raw->lifetime());
_selectedLists.fire(raw->selectedListValue());
_listTops.fire(raw->topValue());
raw->show();
}
void MusicInner::setupEmpty() {
_list->resizeToWidth(width());
const auto savedMusic = &_controller->session().data().savedMusic();
rpl::combine(
rpl::single(
rpl::empty
) | rpl::then(
savedMusic->changed() | rpl::filter(
rpl::mappers::_1 == _peer->id
) | rpl::to_empty
),
_list->heightValue()
) | rpl::on_next([=](auto, int listHeight) {
const auto padding = st::infoMediaMargin;
if (const auto raw = _empty.release()) {
raw->hide();
raw->deleteLater();
}
_emptyLoading = false;
if (listHeight <= padding.bottom() + padding.top()) {
refreshEmpty();
} else {
_albumEmpty = false;
}
refreshHeight();
}, _list->lifetime());
}
void MusicInner::refreshEmpty() {
const auto savedMusic = &_controller->session().data().savedMusic();
const auto knownEmpty = savedMusic->countKnown(_peer->id);
_empty = object_ptr<Ui::FlatLabel>(
this,
(!knownEmpty
? tr::lng_contacts_loading(tr::marked)
: rpl::single(
tr::lng_media_song_empty(tr::now, tr::marked))),
st::giftListAbout);
_empty->show();
_emptyLoading = !knownEmpty;
resizeToWidth(width());
}
void MusicInner::saveState(not_null<MusicMemento*> memento) {
_list->saveState(&memento->media());
}
void MusicInner::restoreState(not_null<MusicMemento*> memento) {
_list->restoreState(&memento->media());
}
rpl::producer<SelectedItems> MusicInner::selectedListValue() const {
return _selectedLists.events_starting_with(
_list->selectedListValue()
) | rpl::flatten_latest();
}
void MusicInner::selectionAction(SelectionAction action) {
_list->selectionAction(action);
}
int MusicInner::resizeGetHeight(int newWidth) {
if (!newWidth) {
return 0;
}
_inResize = true;
auto guard = gsl::finally([this] { _inResize = false; });
if (_list) {
_list->resizeToWidth(newWidth);
}
if (const auto empty = _empty.get()) {
const auto margin = st::giftListAboutMargin;
empty->resizeToWidth(newWidth - margin.left() - margin.right());
}
return recountHeight();
}
void MusicInner::refreshHeight() {
if (_inResize) {
return;
}
resize(width(), recountHeight());
}
int MusicInner::recountHeight() {
auto top = 0;
auto listHeight = 0;
if (_list) {
_list->moveToLeft(0, top);
listHeight = _list->heightNoMargins();
top += listHeight;
}
if (const auto empty = _empty.get()) {
const auto margin = st::giftListAboutMargin;
empty->moveToLeft(margin.left(), top + margin.top());
top += margin.top() + empty->height() + margin.bottom();
}
if (_emptyLoading) {
top = std::max(top, _lastNonLoadingHeight);
} else {
_lastNonLoadingHeight = top;
}
return top;
}
void MusicInner::setScrollHeightValue(rpl::producer<int> value) {
}
rpl::producer<Ui::ScrollToRequest> MusicInner::scrollToRequests() const {
return _scrollToRequests.events();
}
MusicMemento::MusicMemento(not_null<Controller*> controller)
: ContentMemento(MusicTag{ controller->musicPeer() })
, _media(controller) {
}
MusicMemento::MusicMemento(not_null<PeerData*> peer)
: ContentMemento(MusicTag{ peer })
, _media(peer, 0, Media::Type::MusicFile) {
}
MusicMemento::~MusicMemento() = default;
Section MusicMemento::section() const {
return Section(Section::Type::SavedMusic);
}
object_ptr<ContentWidget> MusicMemento::createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) {
auto result = object_ptr<MusicWidget>(parent, controller);
result->setInternalState(geometry, this);
return result;
}
MusicWidget::MusicWidget(
QWidget *parent,
not_null<Controller*> controller)
: ContentWidget(parent, controller) {
_inner = setInnerWidget(object_ptr<MusicInner>(this, controller));
_inner->setScrollHeightValue(scrollHeightValue());
_inner->scrollToRequests(
) | rpl::on_next([this](Ui::ScrollToRequest request) {
scrollTo(request);
}, _inner->lifetime());
}
void MusicWidget::setIsStackBottom(bool isStackBottom) {
ContentWidget::setIsStackBottom(isStackBottom);
_inner->setIsStackBottom(isStackBottom);
}
bool MusicWidget::showInternal(not_null<ContentMemento*> memento) {
if (!controller()->validateMementoPeer(memento)) {
return false;
}
return true;
}
void MusicWidget::setInternalState(
const QRect &geometry,
not_null<MusicMemento*> memento) {
setGeometry(geometry);
Ui::SendPendingMoveResizeEvents(this);
restoreState(memento);
}
std::shared_ptr<ContentMemento> MusicWidget::doCreateMemento() {
auto result = std::make_shared<MusicMemento>(controller());
saveState(result.get());
return result;
}
void MusicWidget::saveState(not_null<MusicMemento*> memento) {
memento->setScrollTop(scrollTopSave());
_inner->saveState(memento);
}
void MusicWidget::restoreState(not_null<MusicMemento*> memento) {
_inner->restoreState(memento);
scrollTopRestore(memento->scrollTop());
}
rpl::producer<SelectedItems> MusicWidget::selectedListValue() const {
return _inner->selectedListValue();
}
void MusicWidget::selectionAction(SelectionAction action) {
_inner->selectionAction(action);
}
rpl::producer<QString> MusicWidget::title() {
return controller()->key().musicPeer()->isSelf()
? tr::lng_media_saved_music_your()
: tr::lng_media_saved_music_title();
}
std::shared_ptr<Info::Memento> MakeMusic(not_null<PeerData*> peer) {
return std::make_shared<Info::Memento>(
std::vector<std::shared_ptr<ContentMemento>>(
1,
std::make_shared<MusicMemento>(peer)));
}
} // namespace Info::Saved

View File

@@ -0,0 +1,81 @@
/*
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"
#include "info/stories/info_stories_common.h"
namespace Ui {
template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Info::Saved {
class MusicInner;
class MusicMemento final : public ContentMemento {
public:
MusicMemento(not_null<Controller*> controller);
MusicMemento(not_null<PeerData*> peer);
~MusicMemento();
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;
int _addingToAlbumId = 0;
};
class MusicWidget final : public ContentWidget {
public:
MusicWidget(QWidget *parent, not_null<Controller*> controller);
void setIsStackBottom(bool isStackBottom) override;
bool showInternal(
not_null<ContentMemento*> memento) override;
void setInternalState(
const QRect &geometry,
not_null<MusicMemento*> memento);
rpl::producer<SelectedItems> selectedListValue() const override;
void selectionAction(SelectionAction action) override;
rpl::producer<QString> title() override;
private:
void saveState(not_null<MusicMemento*> memento);
void restoreState(not_null<MusicMemento*> memento);
std::shared_ptr<ContentMemento> doCreateMemento() override;
MusicInner *_inner = nullptr;
bool _shown = false;
};
[[nodiscard]] std::shared_ptr<Info::Memento> MakeMusic(
not_null<PeerData*> peer);
} // namespace Info::Saved

View File

@@ -0,0 +1,194 @@
/*
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_sublists_widget.h"
//
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "dialogs/dialogs_inner_widget.h"
#include "history/view/history_view_chat_section.h"
#include "info/media/info_media_buttons.h"
#include "info/profile/info_profile_icon.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "main/main_session.h"
#include "lang/lang_keys.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/ui_utility.h"
#include "styles/style_info.h"
namespace Info::Saved {
SublistsMemento::SublistsMemento(not_null<Main::Session*> session)
: ContentMemento(session->user(), nullptr, nullptr, PeerId()) {
}
Section SublistsMemento::section() const {
return Section(Section::Type::SavedSublists);
}
object_ptr<ContentWidget> SublistsMemento::createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) {
auto result = object_ptr<SublistsWidget>(parent, controller);
result->setInternalState(geometry, this);
return result;
}
SublistsMemento::~SublistsMemento() = default;
SublistsWidget::SublistsWidget(
QWidget *parent,
not_null<Controller*> controller)
: ContentWidget(parent, controller)
, _layout(setInnerWidget(object_ptr<Ui::VerticalLayout>(this))) {
setupOtherTypes();
_list = _layout->add(object_ptr<Dialogs::InnerWidget>(
this,
controller->parentController(),
rpl::single(Dialogs::InnerWidget::ChildListShown())));
_list->showSavedSublists();
_list->setNarrowRatio(0.);
_list->chosenRow() | rpl::on_next([=](Dialogs::ChosenRow row) {
if (const auto sublist = row.key.sublist()) {
using namespace Window;
using namespace HistoryView;
auto params = SectionShow(SectionShow::Way::Forward);
params.dropSameFromStack = true;
controller->showSection(
std::make_shared<ChatMemento>(ChatViewId{
.history = sublist->owningHistory(),
.sublist = sublist,
}),
params);
}
}, _list->lifetime());
const auto saved = &controller->session().data().savedMessages();
_list->heightValue() | rpl::on_next([=] {
if (!saved->supported()) {
crl::on_main(controller, [=] {
controller->showSection(
Memento::Default(controller->session().user()),
Window::SectionShow::Way::Backward);
});
}
}, lifetime());
_list->setLoadMoreCallback([=] {
saved->loadMore();
});
}
void SublistsWidget::setupOtherTypes() {
auto wrap = _layout->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_layout,
object_ptr<Ui::VerticalLayout>(_layout)));
auto content = wrap->entity();
content->add(object_ptr<Ui::FixedHeightWidget>(
content,
st::infoProfileSkip));
using Type = Media::Type;
auto tracker = Ui::MultiSlideTracker();
const auto peer = controller()->session().user();
const auto addMediaButton = [&](
Type buttonType,
const style::icon &icon) {
auto result = Media::AddButton(
content,
controller(),
peer,
MsgId(), // topicRootId
PeerId(), // monoforumPeerId
nullptr, // migrated
buttonType,
tracker);
object_ptr<Profile::FloatingIcon>(
result,
icon,
st::infoSharedMediaButtonIconPosition)->show();
};
addMediaButton(Type::Photo, st::infoIconMediaPhoto);
addMediaButton(Type::Video, st::infoIconMediaVideo);
addMediaButton(Type::File, st::infoIconMediaFile);
addMediaButton(Type::MusicFile, st::infoIconMediaAudio);
addMediaButton(Type::Link, st::infoIconMediaLink);
addMediaButton(Type::RoundVoiceFile, st::infoIconMediaVoice);
addMediaButton(Type::GIF, st::infoIconMediaGif);
content->add(object_ptr<Ui::FixedHeightWidget>(
content,
st::infoProfileSkip));
wrap->toggleOn(tracker.atLeastOneShownValue());
wrap->finishAnimating();
_layout->add(object_ptr<Ui::BoxContentDivider>(_layout));
_layout->add(object_ptr<Ui::FixedHeightWidget>(
content,
st::infoProfileSkip));
}
rpl::producer<QString> SublistsWidget::title() {
return tr::lng_saved_messages();
}
rpl::producer<QString> SublistsWidget::subtitle() {
const auto saved = &controller()->session().data().savedMessages();
return saved->chatsList()->fullSize().value(
) | rpl::map([=](int value) {
return (value || saved->chatsList()->loaded())
? tr::lng_filters_chats_count(tr::now, lt_count, value)
: tr::lng_contacts_loading(tr::now);
});
}
bool SublistsWidget::showInternal(not_null<ContentMemento*> memento) {
if (!controller()->validateMementoPeer(memento)) {
return false;
}
if (auto my = dynamic_cast<SublistsMemento*>(memento.get())) {
restoreState(my);
return true;
}
return false;
}
void SublistsWidget::setInternalState(
const QRect &geometry,
not_null<SublistsMemento*> memento) {
setGeometry(geometry);
Ui::SendPendingMoveResizeEvents(this);
restoreState(memento);
}
std::shared_ptr<ContentMemento> SublistsWidget::doCreateMemento() {
auto result = std::make_shared<SublistsMemento>(
&controller()->session());
saveState(result.get());
return result;
}
void SublistsWidget::saveState(not_null<SublistsMemento*> memento) {
memento->setScrollTop(scrollTopSave());
}
void SublistsWidget::restoreState(not_null<SublistsMemento*> memento) {
scrollTopRestore(memento->scrollTop());
}
} // namespace Info::Saved

View File

@@ -0,0 +1,72 @@
/*
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"
namespace Dialogs {
class InnerWidget;
} // namespace Dialogs
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class VerticalLayout;
} // namespace Ui
namespace Info::Saved {
class SublistsMemento final : public ContentMemento {
public:
explicit SublistsMemento(not_null<Main::Session*> session);
object_ptr<ContentWidget> createWidget(
QWidget *parent,
not_null<Controller*> controller,
const QRect &geometry) override;
Section section() const override;
~SublistsMemento();
private:
};
class SublistsWidget final : public ContentWidget {
public:
SublistsWidget(
QWidget *parent,
not_null<Controller*> controller);
bool showInternal(
not_null<ContentMemento*> memento) override;
void setInternalState(
const QRect &geometry,
not_null<SublistsMemento*> memento);
rpl::producer<QString> title() override;
rpl::producer<QString> subtitle() override;
private:
void saveState(not_null<SublistsMemento*> memento);
void restoreState(not_null<SublistsMemento*> memento);
std::shared_ptr<ContentMemento> doCreateMemento() override;
void setupOtherTypes();
const not_null<Ui::VerticalLayout*> _layout;
Dialogs::InnerWidget *_list = nullptr;
};
} // namespace Info::Saved