mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Proof-of-concept saved music API support.
This commit is contained in:
@@ -636,6 +636,8 @@ PRIVATE
|
||||
data/data_report.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_music.cpp
|
||||
data/data_saved_music.h
|
||||
data/data_saved_sublist.cpp
|
||||
data/data_saved_sublist.h
|
||||
data/data_search_controller.cpp
|
||||
@@ -1050,6 +1052,11 @@ PRIVATE
|
||||
info/reactions_list/info_reactions_list_widget.h
|
||||
info/requests_list/info_requests_list_widget.cpp
|
||||
info/requests_list/info_requests_list_widget.h
|
||||
info/saved/info_saved_music_common.h
|
||||
info/saved/info_saved_music_provider.cpp
|
||||
info/saved/info_saved_music_provider.h
|
||||
info/saved/info_saved_music_widget.cpp
|
||||
info/saved/info_saved_music_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
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 "data/data_saved_music.h"
|
||||
|
||||
#include "api/api_hash.h"
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/ui_utility.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPerPage = 50;
|
||||
|
||||
} // namespace
|
||||
|
||||
SavedMusic::SavedMusic(not_null<Session*> owner)
|
||||
: _owner(owner) {
|
||||
}
|
||||
|
||||
bool SavedMusic::Supported(PeerId peerId) {
|
||||
return peerId && peerIsUser(peerId);
|
||||
}
|
||||
|
||||
bool SavedMusic::has(not_null<DocumentData*> document) const {
|
||||
const auto entry = lookupEntry(_owner->session().userPeerId());
|
||||
return entry && ranges::contains(entry->list, document);
|
||||
}
|
||||
|
||||
void SavedMusic::save(not_null<DocumentData*> document) {
|
||||
const auto peerId = _owner->session().userPeerId();
|
||||
auto &entry = _entries[peerId];
|
||||
if (entry.list.empty() && !entry.loaded) {
|
||||
loadMore(peerId);
|
||||
}
|
||||
if (ranges::contains(entry.list, document)) {
|
||||
return;
|
||||
}
|
||||
entry.list.insert(begin(entry.list), document);
|
||||
if (entry.total >= 0) {
|
||||
++entry.total;
|
||||
}
|
||||
_owner->session().api().request(MTPaccount_SaveMusic(
|
||||
MTP_flags(0),
|
||||
document->mtpInput(),
|
||||
MTPInputDocument()
|
||||
)).send();
|
||||
_changed.fire_copy(peerId);
|
||||
}
|
||||
|
||||
void SavedMusic::remove(not_null<DocumentData*> document) {
|
||||
const auto peerId = _owner->session().userPeerId();
|
||||
auto &entry = _entries[peerId];
|
||||
const auto i = ranges::remove(entry.list, document);
|
||||
if (const auto removed = int(end(entry.list) - i)) {
|
||||
entry.list.erase(i, end(entry.list));
|
||||
if (entry.total >= 0) {
|
||||
entry.total = std::max(entry.total - removed, 0);
|
||||
}
|
||||
}
|
||||
_owner->session().api().request(MTPaccount_SaveMusic(
|
||||
MTP_flags(MTPaccount_SaveMusic::Flag::f_unsave),
|
||||
document->mtpInput(),
|
||||
MTPInputDocument()
|
||||
)).send();
|
||||
_changed.fire_copy(peerId);
|
||||
}
|
||||
|
||||
bool SavedMusic::countKnown(PeerId peerId) const {
|
||||
if (!Supported(peerId)) {
|
||||
return true;
|
||||
}
|
||||
const auto entry = lookupEntry(peerId);
|
||||
return entry && entry->total >= 0;
|
||||
}
|
||||
|
||||
int SavedMusic::count(PeerId peerId) const {
|
||||
if (!Supported(peerId)) {
|
||||
return 0;
|
||||
}
|
||||
const auto entry = lookupEntry(peerId);
|
||||
return entry ? std::max(entry->total, 0) : 0;
|
||||
}
|
||||
|
||||
const std::vector<not_null<DocumentData*>> &SavedMusic::list(
|
||||
PeerId peerId) const {
|
||||
static const auto empty = std::vector<not_null<DocumentData*>>();
|
||||
if (!Supported(peerId)) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
const auto entry = lookupEntry(peerId);
|
||||
return entry ? entry->list : empty;
|
||||
}
|
||||
|
||||
void SavedMusic::loadMore(PeerId peerId) {
|
||||
loadMore(peerId, false);
|
||||
}
|
||||
|
||||
void SavedMusic::loadMore(PeerId peerId, bool reload) {
|
||||
if (!Supported(peerId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &entry = _entries[peerId];
|
||||
if (!entry.reloading && reload) {
|
||||
_owner->session().api().request(
|
||||
base::take(entry.requestId)).cancel();
|
||||
}
|
||||
if ((!reload && entry.loaded) || entry.requestId) {
|
||||
return;
|
||||
}
|
||||
const auto user = _owner->peer(peerId)->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
entry.reloading = reload;
|
||||
entry.requestId = _owner->session().api().request(MTPusers_GetSavedMusic(
|
||||
user->inputUser,
|
||||
MTP_int(reload ? 0 : entry.list.size()),
|
||||
MTP_int(kPerPage),
|
||||
MTP_long(reload ? firstPageHash(entry) : 0)
|
||||
)).done([=](const MTPusers_SavedMusic &result) {
|
||||
auto &entry = _entries[peerId];
|
||||
entry.requestId = 0;
|
||||
const auto reloaded = base::take(entry.reloading);
|
||||
result.match([&](const MTPDusers_savedMusicNotModified &) {
|
||||
}, [&](const MTPDusers_savedMusic &data) {
|
||||
const auto list = data.vdocuments().v;
|
||||
const auto count = int(list.size());
|
||||
entry.total = std::max(count, data.vcount().v);
|
||||
if (reloaded) {
|
||||
entry.list.clear();
|
||||
}
|
||||
for (const auto &item : list) {
|
||||
const auto document = _owner->processDocument(item);
|
||||
if (!ranges::contains(entry.list, document)) {
|
||||
entry.list.push_back(document);
|
||||
}
|
||||
}
|
||||
entry.loaded = list.empty() || (count == entry.list.size());
|
||||
});
|
||||
_changed.fire_copy(peerId);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
auto &entry = _entries[peerId];
|
||||
entry.requestId = 0;
|
||||
entry.total = int(entry.list.size());
|
||||
entry.loaded = true;
|
||||
_changed.fire_copy(peerId);
|
||||
}).send();
|
||||
}
|
||||
|
||||
uint64 SavedMusic::firstPageHash(const Entry &entry) const {
|
||||
return Api::CountHash(entry.list
|
||||
| ranges::views::transform(&DocumentData::id)
|
||||
| ranges::views::take(kPerPage));
|
||||
}
|
||||
|
||||
rpl::producer<PeerId> SavedMusic::changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
|
||||
SavedMusic::Entry *SavedMusic::lookupEntry(PeerId peerId) {
|
||||
if (!Supported(peerId)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = _entries.find(peerId);
|
||||
if (it == end(_entries)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
const SavedMusic::Entry *SavedMusic::lookupEntry(PeerId peerId) const {
|
||||
return const_cast<SavedMusic*>(this)->lookupEntry(peerId);
|
||||
}
|
||||
|
||||
rpl::producer<SavedMusicSlice> SavedMusicList(
|
||||
not_null<PeerData*> peer,
|
||||
DocumentData *aroundId,
|
||||
int limit) {
|
||||
if (!peer->isUser()) {
|
||||
return rpl::single(SavedMusicSlice({}, 0, 0, 0));
|
||||
}
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
struct State {
|
||||
SavedMusicSlice slice;
|
||||
base::has_weak_ptr guard;
|
||||
bool scheduled = false;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto push = [=] {
|
||||
state->scheduled = false;
|
||||
|
||||
const auto peerId = peer->id;
|
||||
const auto savedMusic = &peer->owner().savedMusic();
|
||||
if (!savedMusic->countKnown(peerId)) {
|
||||
return;
|
||||
}
|
||||
const auto &loaded = savedMusic->list(peerId);
|
||||
const auto count = savedMusic->count(peerId);
|
||||
auto i = aroundId
|
||||
? ranges::find(loaded, not_null(aroundId))
|
||||
: begin(loaded);
|
||||
if (i == end(loaded)) {
|
||||
i = begin(loaded);
|
||||
}
|
||||
const auto hasBefore = int(i - begin(loaded));
|
||||
const auto hasAfter = int(end(loaded) - i);
|
||||
if (hasAfter < limit) {
|
||||
savedMusic->loadMore(peerId);
|
||||
}
|
||||
const auto takeBefore = std::min(hasBefore, limit);
|
||||
const auto takeAfter = std::min(hasAfter, limit);
|
||||
auto ids = std::vector<not_null<DocumentData*>>();
|
||||
ids.reserve(takeBefore + takeAfter);
|
||||
for (auto j = i - takeBefore; j != i + takeAfter; ++j) {
|
||||
ids.push_back(*j);
|
||||
}
|
||||
const auto added = int(ids.size());
|
||||
state->slice = SavedMusicSlice(
|
||||
std::move(ids),
|
||||
count,
|
||||
(hasBefore - takeBefore),
|
||||
count - hasBefore - added);
|
||||
consumer.put_next_copy(state->slice);
|
||||
};
|
||||
const auto schedule = [=] {
|
||||
if (state->scheduled) {
|
||||
return;
|
||||
}
|
||||
state->scheduled = true;
|
||||
Ui::PostponeCall(&state->guard, [=] {
|
||||
if (state->scheduled) {
|
||||
push();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto peerId = peer->id;
|
||||
const auto savedMusic = &peer->owner().savedMusic();
|
||||
savedMusic->changed(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == peerId
|
||||
) | rpl::start_with_next(schedule, lifetime);
|
||||
|
||||
if (!savedMusic->countKnown(peerId)) {
|
||||
savedMusic->loadMore(peerId);
|
||||
}
|
||||
|
||||
push();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
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 "data/data_abstract_sparse_ids.h"
|
||||
|
||||
class PeerData;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Session;
|
||||
|
||||
class SavedMusic final {
|
||||
public:
|
||||
explicit SavedMusic(not_null<Session*> owner);
|
||||
|
||||
[[nodiscard]] static bool Supported(PeerId peerId);
|
||||
|
||||
[[nodiscard]] bool countKnown(PeerId peerId) const;
|
||||
[[nodiscard]] int count(PeerId peerId) const;
|
||||
[[nodiscard]] const std::vector<not_null<DocumentData*>> &list(
|
||||
PeerId peerId) const;
|
||||
void loadMore(PeerId peerId);
|
||||
|
||||
[[nodiscard]] rpl::producer<PeerId> changed() const;
|
||||
|
||||
[[nodiscard]] bool has(not_null<DocumentData*> document) const;
|
||||
void save(not_null<DocumentData*> document);
|
||||
void remove(not_null<DocumentData*> document);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::vector<not_null<DocumentData*>> list;
|
||||
mtpRequestId requestId = 0;
|
||||
int total = -1;
|
||||
bool loaded = false;
|
||||
bool reloading = false;
|
||||
};
|
||||
|
||||
void loadMore(PeerId peerId, bool reload);
|
||||
[[nodiscard]] Entry *lookupEntry(PeerId peerId);
|
||||
[[nodiscard]] const Entry *lookupEntry(PeerId peerId) const;
|
||||
[[nodiscard]] uint64 firstPageHash(const Entry &entry) const;
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
std::unordered_map<PeerId, Entry> _entries;
|
||||
rpl::event_stream<PeerId> _changed;
|
||||
|
||||
};
|
||||
|
||||
using SavedMusicSlice = AbstractSparseIds<
|
||||
std::vector<not_null<DocumentData*>>>;
|
||||
|
||||
[[nodiscard]] rpl::producer<SavedMusicSlice> SavedMusicList(
|
||||
not_null<PeerData*> peer,
|
||||
DocumentData *aroundId,
|
||||
int limit);
|
||||
|
||||
} // namespace Data
|
||||
@@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_forum_icons.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_streaming.h"
|
||||
@@ -250,6 +251,7 @@ Session::Session(not_null<Main::Session*> session)
|
||||
, _notifySettings(std::make_unique<NotifySettings>(this))
|
||||
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this))
|
||||
, _stories(std::make_unique<Stories>(this))
|
||||
, _savedMusic(std::make_unique<SavedMusic>(this))
|
||||
, _savedMessages(std::make_unique<SavedMessages>(this))
|
||||
, _chatbots(std::make_unique<Chatbots>(this))
|
||||
, _businessInfo(std::make_unique<BusinessInfo>(this))
|
||||
|
||||
@@ -66,6 +66,7 @@ class GroupCall;
|
||||
class NotifySettings;
|
||||
class CustomEmojiManager;
|
||||
class Stories;
|
||||
class SavedMusic;
|
||||
class SavedMessages;
|
||||
class Chatbots;
|
||||
class BusinessInfo;
|
||||
@@ -184,6 +185,9 @@ public:
|
||||
[[nodiscard]] Stories &stories() const {
|
||||
return *_stories;
|
||||
}
|
||||
[[nodiscard]] SavedMusic &savedMusic() const {
|
||||
return *_savedMusic;
|
||||
}
|
||||
[[nodiscard]] SavedMessages &savedMessages() const {
|
||||
return *_savedMessages;
|
||||
}
|
||||
@@ -1235,6 +1239,7 @@ private:
|
||||
const std::unique_ptr<NotifySettings> _notifySettings;
|
||||
const std::unique_ptr<CustomEmojiManager> _customEmojiManager;
|
||||
const std::unique_ptr<Stories> _stories;
|
||||
const std::unique_ptr<SavedMusic> _savedMusic;
|
||||
const std::unique_ptr<SavedMessages> _savedMessages;
|
||||
const std::unique_ptr<Chatbots> _chatbots;
|
||||
const std::unique_ptr<BusinessInfo> _businessInfo;
|
||||
|
||||
@@ -1721,7 +1721,7 @@ rpl::producer<StoryAlbumIdsKey> Stories::albumIdsChanged() const {
|
||||
|
||||
int Stories::albumIdsCount(PeerId peerId, int albumId) const {
|
||||
const auto set = albumIdsSet(peerId, albumId);
|
||||
return set ? set->total : 0;
|
||||
return set ? std::max(set->total, 0) : 0;
|
||||
}
|
||||
|
||||
bool Stories::albumIdsCountKnown(PeerId peerId, int albumId) const {
|
||||
|
||||
@@ -86,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/components/factchecks.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -2550,6 +2551,20 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
saveDocumentToFile(itemId, document);
|
||||
}), &st::menuIconDownload);
|
||||
|
||||
if (document->isSong()) {
|
||||
if (document->owner().savedMusic().has(document)) {
|
||||
_menu->addAction(u"remove muzlo :("_q, [=] {
|
||||
document->owner().savedMusic().remove(document);
|
||||
_controller->showToast(u"removed from muzlo!"_q);
|
||||
}, &st::menuIconUnfave);
|
||||
} else {
|
||||
_menu->addAction(u"to muzlo!"_q, [=] {
|
||||
document->owner().savedMusic().save(document);
|
||||
_controller->showToast(u"added to muzlo! :)"_q);
|
||||
}, &st::menuIconFave);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryView::AddCopyFilename(
|
||||
_menu,
|
||||
document,
|
||||
|
||||
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/common_groups/info_common_groups_widget.h"
|
||||
#include "info/peer_gifts/info_peer_gifts_common.h"
|
||||
#include "info/saved/info_saved_music_common.h"
|
||||
#include "info/stories/info_stories_common.h"
|
||||
#include "info/info_layer_widget.h"
|
||||
#include "info/info_section_widget.h"
|
||||
@@ -489,6 +490,8 @@ Key ContentMemento::key() const {
|
||||
storiesAlbumId(),
|
||||
storiesAddToAlbumId(),
|
||||
};
|
||||
} else if (const auto music = musicPeer()) {
|
||||
return Saved::MusicTag{ music };
|
||||
} else if (statisticsTag().peer) {
|
||||
return statisticsTag();
|
||||
} else if (const auto starref = starrefPeer()) {
|
||||
@@ -536,6 +539,10 @@ ContentMemento::ContentMemento(Stories::Tag stories)
|
||||
, _storiesAddToAlbumId(stories.addingToAlbumId) {
|
||||
}
|
||||
|
||||
ContentMemento::ContentMemento(Saved::MusicTag music)
|
||||
: _musicPeer(music.peer) {
|
||||
}
|
||||
|
||||
ContentMemento::ContentMemento(PeerGifts::Tag gifts)
|
||||
: _giftsPeer(gifts.peer)
|
||||
, _giftsCollectionId(gifts.collectionId) {
|
||||
|
||||
@@ -68,6 +68,10 @@ namespace Info::Stories {
|
||||
struct Tag;
|
||||
} // namespace Info::Stories
|
||||
|
||||
namespace Info::Saved {
|
||||
struct MusicTag;
|
||||
} // namespace Info::Saved
|
||||
|
||||
namespace Info {
|
||||
|
||||
class ContentMemento;
|
||||
@@ -219,6 +223,7 @@ public:
|
||||
explicit ContentMemento(Settings::Tag settings);
|
||||
explicit ContentMemento(Downloads::Tag downloads);
|
||||
explicit ContentMemento(Stories::Tag stories);
|
||||
explicit ContentMemento(Saved::MusicTag music);
|
||||
explicit ContentMemento(Statistics::Tag statistics);
|
||||
explicit ContentMemento(BotStarRef::Tag starref);
|
||||
explicit ContentMemento(GlobalMedia::Tag global);
|
||||
@@ -261,6 +266,9 @@ public:
|
||||
[[nodiscard]] int storiesAddToAlbumId() const {
|
||||
return _storiesAddToAlbumId;
|
||||
}
|
||||
[[nodiscard]] PeerData *musicPeer() const {
|
||||
return _musicPeer;
|
||||
}
|
||||
[[nodiscard]] PeerData *giftsPeer() const {
|
||||
return _giftsPeer;
|
||||
}
|
||||
@@ -333,6 +341,7 @@ private:
|
||||
PeerData * const _storiesPeer = nullptr;
|
||||
int _storiesAlbumId = 0;
|
||||
int _storiesAddToAlbumId = 0;
|
||||
PeerData * const _musicPeer = nullptr;
|
||||
PeerData * const _giftsPeer = nullptr;
|
||||
int _giftsCollectionId = 0;
|
||||
Statistics::Tag _statisticsTag;
|
||||
|
||||
@@ -49,6 +49,9 @@ Key::Key(Downloads::Tag downloads) : _value(downloads) {
|
||||
Key::Key(Stories::Tag stories) : _value(stories) {
|
||||
}
|
||||
|
||||
Key::Key(Saved::MusicTag music) : _value(music) {
|
||||
}
|
||||
|
||||
Key::Key(Statistics::Tag statistics) : _value(statistics) {
|
||||
}
|
||||
|
||||
@@ -135,6 +138,13 @@ int Key::storiesAddToAlbumId() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
PeerData *Key::musicPeer() const {
|
||||
if (const auto tag = std::get_if<Saved::MusicTag>(&_value)) {
|
||||
return tag->peer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PeerData *Key::giftsPeer() const {
|
||||
if (const auto tag = std::get_if<PeerGifts::Tag>(&_value)) {
|
||||
return tag->peer;
|
||||
@@ -392,6 +402,7 @@ bool Controller::validateMementoPeer(
|
||||
&& memento->migratedPeerId() == migratedPeerId()
|
||||
&& memento->settingsSelf() == settingsSelf()
|
||||
&& memento->storiesPeer() == storiesPeer()
|
||||
&& memento->musicPeer() == musicPeer()
|
||||
&& memento->statisticsTag().peer == statisticsTag().peer
|
||||
&& memento->starrefPeer() == starrefPeer()
|
||||
&& memento->starrefType() == starrefType();
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_message_reaction_id.h"
|
||||
#include "data/data_search_controller.h"
|
||||
#include "info/peer_gifts/info_peer_gifts_common.h"
|
||||
#include "info/saved/info_saved_music_common.h"
|
||||
#include "info/statistics/info_statistics_tag.h"
|
||||
#include "info/stories/info_stories_common.h"
|
||||
#include "window/window_session_controller.h"
|
||||
@@ -82,6 +83,7 @@ public:
|
||||
Key(Settings::Tag settings);
|
||||
Key(Downloads::Tag downloads);
|
||||
Key(Stories::Tag stories);
|
||||
Key(Saved::MusicTag music);
|
||||
Key(Statistics::Tag statistics);
|
||||
Key(PeerGifts::Tag gifts);
|
||||
Key(BotStarRef::Tag starref);
|
||||
@@ -101,6 +103,7 @@ public:
|
||||
[[nodiscard]] PeerData *storiesPeer() const;
|
||||
[[nodiscard]] int storiesAlbumId() const;
|
||||
[[nodiscard]] int storiesAddToAlbumId() const;
|
||||
[[nodiscard]] PeerData *musicPeer() const;
|
||||
[[nodiscard]] PeerData *giftsPeer() const;
|
||||
[[nodiscard]] int giftsCollectionId() const;
|
||||
[[nodiscard]] Statistics::Tag statisticsTag() const;
|
||||
@@ -130,6 +133,7 @@ private:
|
||||
Settings::Tag,
|
||||
Downloads::Tag,
|
||||
Stories::Tag,
|
||||
Saved::MusicTag,
|
||||
Statistics::Tag,
|
||||
PeerGifts::Tag,
|
||||
BotStarRef::Tag,
|
||||
@@ -160,6 +164,7 @@ public:
|
||||
Settings,
|
||||
Downloads,
|
||||
Stories,
|
||||
SavedMusic,
|
||||
PollResults,
|
||||
Statistics,
|
||||
BotStarRef,
|
||||
@@ -239,6 +244,9 @@ public:
|
||||
[[nodiscard]] int storiesAddToAlbumId() const {
|
||||
return key().storiesAddToAlbumId();
|
||||
}
|
||||
[[nodiscard]] PeerData *musicPeer() const {
|
||||
return key().musicPeer();
|
||||
}
|
||||
[[nodiscard]] PeerData *giftsPeer() const {
|
||||
return key().giftsPeer();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/components/recent_shared_media_gifts.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_saved_messages.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -25,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/info_memento.h"
|
||||
#include "info/peer_gifts/info_peer_gifts_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/saved/info_saved_music_widget.h"
|
||||
#include "info/stories/info_stories_widget.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
@@ -392,4 +394,43 @@ not_null<Ui::SettingsButton*> AddPeerGiftsButton(
|
||||
return wrap->entity();
|
||||
}
|
||||
|
||||
not_null<Ui::SettingsButton*> AddMusicButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
auto count = rpl::single(0) | rpl::then(Data::SavedMusicList(
|
||||
peer,
|
||||
nullptr,
|
||||
0
|
||||
) | rpl::map([](const Data::SavedMusicSlice &slice) {
|
||||
return slice.fullCount().value_or(0);
|
||||
}));
|
||||
|
||||
using namespace ::Settings;
|
||||
auto forked = std::move(count)
|
||||
| start_spawning(parent->lifetime());
|
||||
auto text = rpl::duplicate(
|
||||
forked
|
||||
) | rpl::map([](int count) {
|
||||
AssertIsDebug();
|
||||
return QString::number(count) + u" muzla"_q;
|
||||
});
|
||||
auto button = parent->add(object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
parent,
|
||||
object_ptr<Ui::SettingsButton>(
|
||||
parent,
|
||||
std::move(text),
|
||||
st::infoSharedMediaButton))
|
||||
)->setDuration(
|
||||
st::infoSlideDuration
|
||||
)->toggleOn(
|
||||
rpl::duplicate(forked) | rpl::map(rpl::mappers::_1 > 0)
|
||||
)->entity();
|
||||
|
||||
button->addClickHandler([=] {
|
||||
navigation->showSection(Info::Saved::MakeMusic(peer));
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
} // namespace Info::Media
|
||||
|
||||
@@ -77,4 +77,9 @@ using Type = Storage::SharedMediaType;
|
||||
not_null<PeerData*> peer,
|
||||
Ui::MultiSlideTracker &tracker);
|
||||
|
||||
[[nodiscard]] not_null<Ui::SettingsButton*> AddMusicButton(
|
||||
Ui::VerticalLayout *parent,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Info::Media
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/media/info_media_provider.h"
|
||||
#include "info/media/info_media_list_section.h"
|
||||
#include "info/downloads/info_downloads_provider.h"
|
||||
#include "info/saved/info_saved_music_provider.h"
|
||||
#include "info/stories/info_stories_provider.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "layout/layout_mosaic.h"
|
||||
@@ -98,6 +99,8 @@ struct ListWidget::DateBadge {
|
||||
not_null<AbstractController*> controller) {
|
||||
if (controller->isDownloads()) {
|
||||
return std::make_unique<Downloads::Provider>(controller);
|
||||
} else if (controller->musicPeer()) {
|
||||
return std::make_unique<Saved::MusicProvider>(controller);
|
||||
} else if (controller->storiesPeer()) {
|
||||
return std::make_unique<Stories::Provider>(controller);
|
||||
} else if (controller->section().type() == Section::Type::GlobalMedia) {
|
||||
@@ -196,6 +199,9 @@ void ListWidget::start() {
|
||||
setupStoriesTrackIds();
|
||||
trackSession(&session());
|
||||
restart();
|
||||
} else if (_controller->musicPeer()) {
|
||||
trackSession(&session());
|
||||
restart();
|
||||
} else {
|
||||
trackSession(&session());
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_saved_sublist.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -80,6 +81,14 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
|
||||
_cover = AddCover(result, _controller, _peer, _topic, _sublist);
|
||||
if (_topic && _topic->creating()) {
|
||||
return result;
|
||||
} else if (Data::SavedMusic::Supported(_peer->id)) {
|
||||
object_ptr<Profile::FloatingIcon>(
|
||||
Media::AddMusicButton(
|
||||
result,
|
||||
_controller,
|
||||
_peer),
|
||||
st::infoIconMediaAudio,
|
||||
st::infoSharedMediaButtonIconPosition);
|
||||
}
|
||||
|
||||
AddDetails(result, _controller, _peer, _topic, _sublist, origin);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace Info::Saved {
|
||||
|
||||
struct MusicTag {
|
||||
explicit MusicTag(not_null<PeerData*> peer)
|
||||
: peer(peer) {
|
||||
}
|
||||
|
||||
not_null<PeerData*> peer;
|
||||
};
|
||||
|
||||
} // namespace Info::Saved
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
[[nodiscard]] int MinStoryHeight(int width) {
|
||||
auto itemsLeft = st::infoMediaSkip;
|
||||
auto itemsInRow = (width - itemsLeft)
|
||||
/ (st::infoMediaMinGridSize + st::infoMediaSkip);
|
||||
return (st::infoMediaMinGridSize + st::infoMediaSkip) / itemsInRow;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MusicProvider::MusicProvider(not_null<AbstractController*> controller)
|
||||
: _controller(controller)
|
||||
, _peer(controller->key().musicPeer())
|
||||
, _history(_peer->owner().history(_peer)) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_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 !_peer->isSelf();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MusicProvider::hasSelectRestrictionChanges() {
|
||||
return rpl::never<bool>();
|
||||
}
|
||||
|
||||
bool MusicProvider::sectionHasFloatingHeader() {
|
||||
return false;
|
||||
}
|
||||
|
||||
not_null<DocumentData*> MusicProvider::musicIdFromMsgId(MsgId itemId) const {
|
||||
const auto i = _musicIdFromMsgId.find(itemId);
|
||||
Assert(i != end(_musicIdFromMsgId));
|
||||
|
||||
return i->second;
|
||||
}
|
||||
|
||||
not_null<HistoryItem*> MusicProvider::musicIdToMsg(
|
||||
not_null<DocumentData*> id) const {
|
||||
const auto i = _musicIdToMsg.find(id);
|
||||
if (i != end(_musicIdToMsg)) {
|
||||
return i->second.get();
|
||||
}
|
||||
return _musicIdToMsg.emplace(id, _history->makeMessage({
|
||||
.id = _history->nextNonHistoryEntryId(),
|
||||
.flags = (MessageFlag::FakeHistoryItem | MessageFlag::HasFromId),
|
||||
.from = _peer->id,
|
||||
.date = base::unixtime::now(),
|
||||
}, id, TextWithEntities())).first->second.get();
|
||||
}
|
||||
|
||||
MsgId MusicProvider::musicIdToMsgId(not_null<DocumentData*> id) const {
|
||||
return musicIdToMsg(id)->id;
|
||||
}
|
||||
|
||||
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 = MinStoryHeight(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 id = musicIdFromMsgId(layout->getItem()->id);
|
||||
if (!preloadRequired) {
|
||||
preloadRequired = (_idsLimit < preloadIdsLimitMin);
|
||||
}
|
||||
if (!preloadRequired) {
|
||||
auto delta = _slice.distance(_aroundId, id);
|
||||
Assert(delta != std::nullopt);
|
||||
preloadRequired = (qAbs(*delta) >= minIdDelta);
|
||||
}
|
||||
if (preloadRequired) {
|
||||
_idsLimit = preloadIdsLimit;
|
||||
_aroundId = id;
|
||||
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::start_with_next([=](Data::SavedMusicSlice &&slice) {
|
||||
if (!slice.fullCount()) {
|
||||
// Don't display anything while full count is unknown.
|
||||
return;
|
||||
}
|
||||
_slice = std::move(slice);
|
||||
|
||||
auto nearestId = (DocumentData*)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 musicId = _slice[i];
|
||||
if (const auto layout = getLayout(musicId, 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::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 IsStoryMsgId(item->id) && (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<DocumentData*> id,
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
auto it = _layouts.find(id);
|
||||
if (it == _layouts.end()) {
|
||||
if (auto layout = createLayout(id, delegate)) {
|
||||
layout->initDimensions();
|
||||
it = _layouts.emplace(id, std::move(layout)).first;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
it->second.stale = false;
|
||||
return it->second.item.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<BaseLayout> MusicProvider::createLayout(
|
||||
not_null<DocumentData*> id,
|
||||
not_null<Overview::Layout::Delegate*> delegate) {
|
||||
const auto item = musicIdToMsg(id);
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto getPhoto = [&]() -> PhotoData* {
|
||||
if (const auto media = item->media()) {
|
||||
return media->photo();
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto getFile = [&]() -> DocumentData* {
|
||||
if (const auto media = item->media()) {
|
||||
return media->document();
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
const auto peer = item->history()->peer;
|
||||
|
||||
using namespace Overview::Layout;
|
||||
const auto options = MediaOptions{
|
||||
};
|
||||
if (const auto file = getFile()) {
|
||||
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);
|
||||
const auto id = item->id;
|
||||
if (!_musicIdFromMsgId.contains(id)) {
|
||||
return result;
|
||||
}
|
||||
AssertIsDebug();
|
||||
//const auto peer = item->history()->peer;
|
||||
//const auto channel = peer->asChannel();
|
||||
//const auto maybeStory = peer->owner().stories().lookup(
|
||||
// { peer->id, StoryIdFromMsgId(id) });
|
||||
//if (maybeStory) {
|
||||
// const auto story = *maybeStory;
|
||||
// result.canForward = peer->isSelf() && story->canShare();
|
||||
// result.canDelete = story->canDelete();
|
||||
// result.canUnpinStory = story->pinnedToTop();
|
||||
// result.storyInProfile = story->inProfile();
|
||||
//}
|
||||
//result.canToggleStoryPin = peer->isSelf()
|
||||
// || (channel && channel->canEditStories());
|
||||
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 musicId = layoutItem.first;
|
||||
const auto item = musicIdToMsg(musicId);
|
||||
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 false;
|
||||
}
|
||||
|
||||
QString MusicProvider::showInFolderPath(
|
||||
not_null<const HistoryItem*> item,
|
||||
not_null<DocumentData*> document) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
int64 MusicProvider::scrollTopStatePosition(not_null<HistoryItem*> item) {
|
||||
return StoryIdFromMsgId(item->id);
|
||||
}
|
||||
|
||||
HistoryItem *MusicProvider::scrollTopStateItem(ListScrollTopState state) {
|
||||
if (state.item && _slice.indexOf(musicIdFromMsgId(state.item->id))) {
|
||||
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 = (DocumentData*)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 = FullMsgId(_peer->id, musicIdToMsgId(nearestId));
|
||||
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
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
|
||||
|
||||
[[nodiscard]] not_null<DocumentData*> musicIdFromMsgId(
|
||||
MsgId itemId) const;
|
||||
[[nodiscard]] not_null<HistoryItem*> musicIdToMsg(
|
||||
not_null<DocumentData*> id) const;
|
||||
[[nodiscard]] MsgId musicIdToMsgId(not_null<DocumentData*> id) const;
|
||||
|
||||
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 markLayoutsStale();
|
||||
void clearStaleLayouts();
|
||||
void clear();
|
||||
|
||||
[[nodiscard]] HistoryItem *ensureItem(not_null<DocumentData*> id);
|
||||
[[nodiscard]] Media::BaseLayout *getLayout(
|
||||
not_null<DocumentData*> id,
|
||||
not_null<Overview::Layout::Delegate*> delegate);
|
||||
[[nodiscard]] std::unique_ptr<Media::BaseLayout> createLayout(
|
||||
not_null<DocumentData*> musicId,
|
||||
not_null<Overview::Layout::Delegate*> delegate);
|
||||
|
||||
const not_null<AbstractController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<History*> _history;
|
||||
|
||||
DocumentData *_aroundId = nullptr;
|
||||
int _idsLimit = kMinimalIdsLimit;
|
||||
Data::SavedMusicSlice _slice;
|
||||
|
||||
std::unordered_map<not_null<DocumentData*>, Media::CachedItem> _layouts;
|
||||
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
|
||||
rpl::event_stream<> _refreshed;
|
||||
|
||||
mutable base::flat_map<MsgId, not_null<DocumentData*>> _musicIdFromMsgId;
|
||||
mutable base::flat_map<not_null<DocumentData*>, OwnedItem> _musicIdToMsg;
|
||||
|
||||
bool _started = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
rpl::lifetime _viewerLifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Saved
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
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);
|
||||
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::start_with_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 AssertIsDebug()
|
||||
? tr::lng_contacts_loading(Ui::Text::WithEntities)
|
||||
: rpl::single(TextWithEntities{ u"no muzlo found :("_q })),
|
||||
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::start_with_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() {
|
||||
//const auto peer = controller()->key().musicPeer();
|
||||
AssertIsDebug();
|
||||
return rpl::single(u"muzlo"_q);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_saved_music.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -291,6 +292,15 @@ void InnerWidget::createProfileTop() {
|
||||
|
||||
using namespace Profile;
|
||||
AddCover(_top, _controller, _peer, nullptr, nullptr);
|
||||
if (Data::SavedMusic::Supported(_peer->id)) {
|
||||
object_ptr<Profile::FloatingIcon>(
|
||||
Media::AddMusicButton(
|
||||
_top,
|
||||
_controller,
|
||||
_peer),
|
||||
st::infoIconMediaAudio,
|
||||
st::infoSharedMediaButtonIconPosition);
|
||||
}
|
||||
AddDetails(_top, _controller, _peer, nullptr, nullptr, { v::null });
|
||||
|
||||
auto tracker = Ui::MultiSlideTracker();
|
||||
|
||||
@@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#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_info.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user