mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Support global media in chats search.
This commit is contained in:
parent
04e9eed88d
commit
d59eb8e731
31 changed files with 1840 additions and 145 deletions
|
@ -946,6 +946,12 @@ PRIVATE
|
||||||
info/downloads/info_downloads_provider.h
|
info/downloads/info_downloads_provider.h
|
||||||
info/downloads/info_downloads_widget.cpp
|
info/downloads/info_downloads_widget.cpp
|
||||||
info/downloads/info_downloads_widget.h
|
info/downloads/info_downloads_widget.h
|
||||||
|
info/global_media/info_global_media_widget.cpp
|
||||||
|
info/global_media/info_global_media_widget.h
|
||||||
|
info/global_media/info_global_media_inner_widget.cpp
|
||||||
|
info/global_media/info_global_media_inner_widget.h
|
||||||
|
info/global_media/info_global_media_provider.cpp
|
||||||
|
info/global_media/info_global_media_provider.h
|
||||||
info/media/info_media_buttons.h
|
info/media/info_media_buttons.h
|
||||||
info/media/info_media_common.cpp
|
info/media/info_media_common.cpp
|
||||||
info/media/info_media_common.h
|
info/media/info_media_common.h
|
||||||
|
|
|
@ -5832,6 +5832,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_recent_chats" = "Chats";
|
"lng_recent_chats" = "Chats";
|
||||||
"lng_recent_channels" = "Channels";
|
"lng_recent_channels" = "Channels";
|
||||||
"lng_recent_apps" = "Apps";
|
"lng_recent_apps" = "Apps";
|
||||||
|
"lng_all_photos" = "Photos";
|
||||||
|
"lng_all_videos" = "Videos";
|
||||||
|
"lng_all_downloads" = "Downloads";
|
||||||
|
"lng_all_links" = "Links";
|
||||||
|
"lng_all_files" = "Files";
|
||||||
|
"lng_all_music" = "Music";
|
||||||
|
"lng_all_voice" = "Voice";
|
||||||
"lng_channels_none_title" = "No channels yet...";
|
"lng_channels_none_title" = "No channels yet...";
|
||||||
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
||||||
"lng_channels_your_title" = "Channels you joined";
|
"lng_channels_your_title" = "Channels you joined";
|
||||||
|
|
|
@ -3219,6 +3219,31 @@ void ApiWrap::sharedMediaDone(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtpRequestId ApiWrap::requestGlobalMedia(
|
||||||
|
Storage::SharedMediaType type,
|
||||||
|
const QString &query,
|
||||||
|
int32 offsetRate,
|
||||||
|
Data::MessagePosition offsetPosition,
|
||||||
|
Fn<void(Api::GlobalMediaResult)> done) {
|
||||||
|
auto prepared = Api::PrepareGlobalMediaRequest(
|
||||||
|
_session,
|
||||||
|
offsetRate,
|
||||||
|
offsetPosition,
|
||||||
|
type,
|
||||||
|
query);
|
||||||
|
if (!prepared) {
|
||||||
|
done({});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return request(
|
||||||
|
std::move(*prepared)
|
||||||
|
).done([=](const Api::SearchRequestResult &result) {
|
||||||
|
done(Api::ParseGlobalMediaResult(_session, result));
|
||||||
|
}).fail([=] {
|
||||||
|
done({});
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
void ApiWrap::sendAction(const SendAction &action) {
|
void ApiWrap::sendAction(const SendAction &action) {
|
||||||
if (!action.options.scheduled
|
if (!action.options.scheduled
|
||||||
&& !action.options.shortcutId
|
&& !action.options.shortcutId
|
||||||
|
|
|
@ -59,6 +59,7 @@ class Show;
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
|
||||||
struct SearchResult;
|
struct SearchResult;
|
||||||
|
struct GlobalMediaResult;
|
||||||
|
|
||||||
class Updates;
|
class Updates;
|
||||||
class Authorizations;
|
class Authorizations;
|
||||||
|
@ -288,6 +289,12 @@ public:
|
||||||
Storage::SharedMediaType type,
|
Storage::SharedMediaType type,
|
||||||
MsgId messageId,
|
MsgId messageId,
|
||||||
SliceType slice);
|
SliceType slice);
|
||||||
|
mtpRequestId requestGlobalMedia(
|
||||||
|
Storage::SharedMediaType type,
|
||||||
|
const QString &query,
|
||||||
|
int32 offsetRate,
|
||||||
|
Data::MessagePosition offsetPosition,
|
||||||
|
Fn<void(Api::GlobalMediaResult)> done);
|
||||||
|
|
||||||
void readFeaturedSetDelayed(uint64 setId);
|
void readFeaturedSetDelayed(uint64 setId);
|
||||||
|
|
||||||
|
@ -509,6 +516,10 @@ private:
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
SharedMediaType type,
|
SharedMediaType type,
|
||||||
Api::SearchResult &&parsed);
|
Api::SearchResult &&parsed);
|
||||||
|
void globalMediaDone(
|
||||||
|
SharedMediaType type,
|
||||||
|
FullMsgId messageId,
|
||||||
|
Api::GlobalMediaResult &&parsed);
|
||||||
|
|
||||||
void sendSharedContact(
|
void sendSharedContact(
|
||||||
const QString &phone,
|
const QString &phone,
|
||||||
|
@ -672,6 +683,17 @@ private:
|
||||||
};
|
};
|
||||||
base::flat_set<HistoryRequest> _historyRequests;
|
base::flat_set<HistoryRequest> _historyRequests;
|
||||||
|
|
||||||
|
struct GlobalMediaRequest {
|
||||||
|
SharedMediaType mediaType = {};
|
||||||
|
FullMsgId aroundId;
|
||||||
|
SliceType sliceType = {};
|
||||||
|
|
||||||
|
friend inline auto operator<=>(
|
||||||
|
const GlobalMediaRequest&,
|
||||||
|
const GlobalMediaRequest&) = default;
|
||||||
|
};
|
||||||
|
base::flat_set<GlobalMediaRequest> _globalMediaRequests;
|
||||||
|
|
||||||
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
|
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
|
||||||
TimeId _dialogsLoadTill = 0;
|
TimeId _dialogsLoadTill = 0;
|
||||||
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;
|
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;
|
||||||
|
|
|
@ -223,4 +223,13 @@ struct hash<FullStoryId> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<FullMsgId> {
|
||||||
|
size_t operator()(FullMsgId value) const {
|
||||||
|
return QtPrivate::QHashCombine().operator()(
|
||||||
|
std::hash<BareId>()(value.peer.value),
|
||||||
|
value.msg.bare);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
|
@ -26,6 +26,109 @@ constexpr auto kDefaultSearchTimeoutMs = crl::time(200);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
MTPMessagesFilter PrepareSearchFilter(Storage::SharedMediaType type) {
|
||||||
|
using Type = Storage::SharedMediaType;
|
||||||
|
switch (type) {
|
||||||
|
case Type::Photo:
|
||||||
|
return MTP_inputMessagesFilterPhotos();
|
||||||
|
case Type::Video:
|
||||||
|
return MTP_inputMessagesFilterVideo();
|
||||||
|
case Type::PhotoVideo:
|
||||||
|
return MTP_inputMessagesFilterPhotoVideo();
|
||||||
|
case Type::MusicFile:
|
||||||
|
return MTP_inputMessagesFilterMusic();
|
||||||
|
case Type::File:
|
||||||
|
return MTP_inputMessagesFilterDocument();
|
||||||
|
case Type::VoiceFile:
|
||||||
|
return MTP_inputMessagesFilterVoice();
|
||||||
|
case Type::RoundVoiceFile:
|
||||||
|
return MTP_inputMessagesFilterRoundVoice();
|
||||||
|
case Type::RoundFile:
|
||||||
|
return MTP_inputMessagesFilterRoundVideo();
|
||||||
|
case Type::GIF:
|
||||||
|
return MTP_inputMessagesFilterGif();
|
||||||
|
case Type::Link:
|
||||||
|
return MTP_inputMessagesFilterUrl();
|
||||||
|
case Type::ChatPhoto:
|
||||||
|
return MTP_inputMessagesFilterChatPhotos();
|
||||||
|
case Type::Pinned:
|
||||||
|
return MTP_inputMessagesFilterPinned();
|
||||||
|
}
|
||||||
|
return MTP_inputMessagesFilterEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GlobalMediaRequest> PrepareGlobalMediaRequest(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
int32 offsetRate,
|
||||||
|
Data::MessagePosition offsetPosition,
|
||||||
|
Storage::SharedMediaType type,
|
||||||
|
const QString &query) {
|
||||||
|
const auto filter = PrepareSearchFilter(type);
|
||||||
|
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto minDate = 0;
|
||||||
|
const auto maxDate = 0;
|
||||||
|
const auto folderId = 0;
|
||||||
|
const auto limit = offsetPosition.fullId.peer
|
||||||
|
? kSharedMediaLimit
|
||||||
|
: kFirstSharedMediaLimit;
|
||||||
|
return MTPmessages_SearchGlobal(
|
||||||
|
MTP_flags(MTPmessages_SearchGlobal::Flag::f_folder_id), // No archive
|
||||||
|
MTP_int(folderId),
|
||||||
|
MTP_string(query),
|
||||||
|
filter,
|
||||||
|
MTP_int(minDate),
|
||||||
|
MTP_int(maxDate),
|
||||||
|
MTP_int(offsetRate),
|
||||||
|
(offsetPosition.fullId.peer
|
||||||
|
? session->data().peer(PeerId(offsetPosition.fullId.peer))->input
|
||||||
|
: MTP_inputPeerEmpty()),
|
||||||
|
MTP_int(offsetPosition.fullId.msg),
|
||||||
|
MTP_int(limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalMediaResult ParseGlobalMediaResult(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const MTPmessages_Messages &data) {
|
||||||
|
auto result = GlobalMediaResult();
|
||||||
|
|
||||||
|
auto messages = (const QVector<MTPMessage>*)nullptr;
|
||||||
|
data.match([&](const MTPDmessages_messagesNotModified &) {
|
||||||
|
}, [&](const auto &data) {
|
||||||
|
session->data().processUsers(data.vusers());
|
||||||
|
session->data().processChats(data.vchats());
|
||||||
|
messages = &data.vmessages().v;
|
||||||
|
});
|
||||||
|
data.match([&](const MTPDmessages_messagesNotModified &) {
|
||||||
|
}, [&](const MTPDmessages_messages &data) {
|
||||||
|
result.fullCount = data.vmessages().v.size();
|
||||||
|
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||||
|
result.fullCount = data.vcount().v;
|
||||||
|
result.offsetRate = data.vnext_rate().value_or_empty();
|
||||||
|
}, [&](const MTPDmessages_channelMessages &data) {
|
||||||
|
result.fullCount = data.vcount().v;
|
||||||
|
});
|
||||||
|
data.match([&](const MTPDmessages_channelMessages &data) {
|
||||||
|
LOG(("API Error: received messages.channelMessages when "
|
||||||
|
"no channel was passed! (ParseSearchResult)"));
|
||||||
|
}, [](const auto &) {});
|
||||||
|
|
||||||
|
const auto addType = NewMessageType::Existing;
|
||||||
|
result.messageIds.reserve(messages->size());
|
||||||
|
for (const auto &message : *messages) {
|
||||||
|
const auto item = session->data().addNewMessage(
|
||||||
|
message,
|
||||||
|
MessageFlags(),
|
||||||
|
addType);
|
||||||
|
if (item) {
|
||||||
|
result.messageIds.push_back(item->position());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<SearchRequest> PrepareSearchRequest(
|
std::optional<SearchRequest> PrepareSearchRequest(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
|
@ -33,36 +136,7 @@ std::optional<SearchRequest> PrepareSearchRequest(
|
||||||
const QString &query,
|
const QString &query,
|
||||||
MsgId messageId,
|
MsgId messageId,
|
||||||
Data::LoadDirection direction) {
|
Data::LoadDirection direction) {
|
||||||
const auto filter = [&] {
|
const auto filter = PrepareSearchFilter(type);
|
||||||
using Type = Storage::SharedMediaType;
|
|
||||||
switch (type) {
|
|
||||||
case Type::Photo:
|
|
||||||
return MTP_inputMessagesFilterPhotos();
|
|
||||||
case Type::Video:
|
|
||||||
return MTP_inputMessagesFilterVideo();
|
|
||||||
case Type::PhotoVideo:
|
|
||||||
return MTP_inputMessagesFilterPhotoVideo();
|
|
||||||
case Type::MusicFile:
|
|
||||||
return MTP_inputMessagesFilterMusic();
|
|
||||||
case Type::File:
|
|
||||||
return MTP_inputMessagesFilterDocument();
|
|
||||||
case Type::VoiceFile:
|
|
||||||
return MTP_inputMessagesFilterVoice();
|
|
||||||
case Type::RoundVoiceFile:
|
|
||||||
return MTP_inputMessagesFilterRoundVoice();
|
|
||||||
case Type::RoundFile:
|
|
||||||
return MTP_inputMessagesFilterRoundVideo();
|
|
||||||
case Type::GIF:
|
|
||||||
return MTP_inputMessagesFilterGif();
|
|
||||||
case Type::Link:
|
|
||||||
return MTP_inputMessagesFilterUrl();
|
|
||||||
case Type::ChatPhoto:
|
|
||||||
return MTP_inputMessagesFilterChatPhotos();
|
|
||||||
case Type::Pinned:
|
|
||||||
return MTP_inputMessagesFilterPinned();
|
|
||||||
}
|
|
||||||
return MTP_inputMessagesFilterEmpty();
|
|
||||||
}();
|
|
||||||
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
|
if (query.isEmpty() && filter.type() == mtpc_inputMessagesFilterEmpty) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ class Session;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
enum class LoadDirection : char;
|
enum class LoadDirection : char;
|
||||||
|
struct MessagePosition;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
@ -36,6 +37,27 @@ using HistoryResult = SearchResult;
|
||||||
using HistoryRequest = MTPmessages_GetHistory;
|
using HistoryRequest = MTPmessages_GetHistory;
|
||||||
using HistoryRequestResult = MTPmessages_Messages;
|
using HistoryRequestResult = MTPmessages_Messages;
|
||||||
|
|
||||||
|
using GlobalMediaRequest = MTPmessages_SearchGlobal;
|
||||||
|
struct GlobalMediaResult {
|
||||||
|
std::vector<Data::MessagePosition> messageIds;
|
||||||
|
int32 offsetRate = 0;
|
||||||
|
int fullCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] MTPMessagesFilter PrepareSearchFilter(
|
||||||
|
Storage::SharedMediaType type);
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<GlobalMediaRequest> PrepareGlobalMediaRequest(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
int32 offsetRate,
|
||||||
|
Data::MessagePosition offsetPosition,
|
||||||
|
Storage::SharedMediaType type,
|
||||||
|
const QString &query);
|
||||||
|
|
||||||
|
[[nodiscard]] GlobalMediaResult ParseGlobalMediaResult(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const MTPmessages_Messages &data);
|
||||||
|
|
||||||
[[nodiscard]] std::optional<SearchRequest> PrepareSearchRequest(
|
[[nodiscard]] std::optional<SearchRequest> PrepareSearchRequest(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
|
|
|
@ -23,10 +23,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "dialogs/ui/chat_search_empty.h"
|
#include "dialogs/ui/chat_search_empty.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "info/downloads/info_downloads_widget.h"
|
||||||
|
#include "info/media/info_media_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "info/info_memento.h"
|
||||||
|
#include "info/info_wrap_widget.h"
|
||||||
#include "inline_bots/bot_attach_web_view.h"
|
#include "inline_bots/bot_attach_web_view.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "settings/settings_common.h"
|
#include "settings/settings_common.h"
|
||||||
|
#include "storage/storage_shared_media.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
|
@ -1297,6 +1303,18 @@ Suggestions::Suggestions(
|
||||||
, _tabs(
|
, _tabs(
|
||||||
_tabsScroll->setOwnedWidget(
|
_tabsScroll->setOwnedWidget(
|
||||||
object_ptr<Ui::SettingsSlider>(this, st::dialogsSearchTabs)))
|
object_ptr<Ui::SettingsSlider>(this, st::dialogsSearchTabs)))
|
||||||
|
, _tabKeys{
|
||||||
|
{ Tab::Chats },
|
||||||
|
{ Tab::Channels },
|
||||||
|
{ Tab::Apps },
|
||||||
|
{ Tab::Media, MediaType::Photo },
|
||||||
|
{ Tab::Media, MediaType::Video },
|
||||||
|
{ Tab::Downloads },
|
||||||
|
{ Tab::Media, MediaType::Link },
|
||||||
|
{ Tab::Media, MediaType::File },
|
||||||
|
{ Tab::Media, MediaType::MusicFile },
|
||||||
|
{ Tab::Media, MediaType::RoundVoiceFile },
|
||||||
|
}
|
||||||
, _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
|
, _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||||
, _chatsContent(
|
, _chatsContent(
|
||||||
_chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
_chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||||
|
@ -1375,21 +1393,37 @@ void Suggestions::setupTabs() {
|
||||||
shadow->setGeometry(0, height - line, width, line);
|
shadow->setGeometry(0, height - line, width, line);
|
||||||
}, shadow->lifetime());
|
}, shadow->lifetime());
|
||||||
|
|
||||||
shadow->showOn(_tabs->shownValue());
|
shadow->showOn(_tabsScroll->shownValue());
|
||||||
|
|
||||||
auto sections = std::vector<QString>{
|
const auto labels = base::flat_map<Key, QString>{
|
||||||
tr::lng_recent_chats(tr::now),
|
{ Key{ Tab::Chats }, tr::lng_recent_chats(tr::now) },
|
||||||
tr::lng_recent_channels(tr::now),
|
{ Key{ Tab::Channels }, tr::lng_recent_channels(tr::now) },
|
||||||
tr::lng_recent_apps(tr::now),
|
{ Key{ Tab::Apps }, tr::lng_recent_apps(tr::now) },
|
||||||
|
{ Key{ Tab::Media, MediaType::Photo }, tr::lng_all_photos(tr::now) },
|
||||||
|
{ Key{ Tab::Media, MediaType::Video }, tr::lng_all_videos(tr::now) },
|
||||||
|
{ Key{ Tab::Downloads }, tr::lng_all_downloads(tr::now) },
|
||||||
|
{ Key{ Tab::Media, MediaType::Link }, tr::lng_all_links(tr::now) },
|
||||||
|
{ Key{ Tab::Media, MediaType::File }, tr::lng_all_files(tr::now) },
|
||||||
|
{
|
||||||
|
Key{ Tab::Media, MediaType::MusicFile },
|
||||||
|
tr::lng_all_music(tr::now),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key{ Tab::Media, MediaType::RoundVoiceFile },
|
||||||
|
tr::lng_all_voice(tr::now),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
auto sections = std::vector<QString>();
|
||||||
|
for (const auto key : _tabKeys) {
|
||||||
|
const auto i = labels.find(key);
|
||||||
|
Assert(i != end(labels));
|
||||||
|
sections.push_back(i->second);
|
||||||
|
}
|
||||||
_tabs->setSections(sections);
|
_tabs->setSections(sections);
|
||||||
_tabs->sectionActivated(
|
_tabs->sectionActivated(
|
||||||
) | rpl::start_with_next([=](int section) {
|
) | rpl::start_with_next([=](int section) {
|
||||||
switchTab(section == 2
|
Assert(section >= 0 && section < _tabKeys.size());
|
||||||
? Tab::Apps
|
switchTab(_tabKeys[section]);
|
||||||
: section
|
|
||||||
? Tab::Channels
|
|
||||||
: Tab::Chats);
|
|
||||||
}, _tabs->lifetime());
|
}, _tabs->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1459,7 +1493,7 @@ void Suggestions::setupChats() {
|
||||||
_chatsScroll->viewportEvent(e);
|
_chatsScroll->viewportEvent(e);
|
||||||
}, _topPeers->lifetime());
|
}, _topPeers->lifetime());
|
||||||
|
|
||||||
_chatsScroll->setVisible(_tab.current() == Tab::Chats);
|
_chatsScroll->setVisible(_key.current().tab == Tab::Chats);
|
||||||
_chatsScroll->setCustomTouchProcess(_recent->processTouch);
|
_chatsScroll->setCustomTouchProcess(_recent->processTouch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1493,7 +1527,7 @@ void Suggestions::setupChannels() {
|
||||||
rpl::mappers::_1 + rpl::mappers::_2 == 0),
|
rpl::mappers::_1 + rpl::mappers::_2 == 0),
|
||||||
anim::type::instant);
|
anim::type::instant);
|
||||||
|
|
||||||
_channelsScroll->setVisible(_tab.current() == Tab::Channels);
|
_channelsScroll->setVisible(_key.current().tab == Tab::Channels);
|
||||||
_channelsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
|
_channelsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
|
||||||
const auto myChannels = _myChannels->processTouch(e);
|
const auto myChannels = _myChannels->processTouch(e);
|
||||||
const auto recommendations = _recommendations->processTouch(e);
|
const auto recommendations = _recommendations->processTouch(e);
|
||||||
|
@ -1510,7 +1544,7 @@ void Suggestions::setupApps() {
|
||||||
_popularApps->wrap->toggle(count > 0, anim::type::instant);
|
_popularApps->wrap->toggle(count > 0, anim::type::instant);
|
||||||
}, _popularApps->wrap->lifetime());
|
}, _popularApps->wrap->lifetime());
|
||||||
|
|
||||||
_appsScroll->setVisible(_tab.current() == Tab::Apps);
|
_appsScroll->setVisible(_key.current().tab == Tab::Apps);
|
||||||
_appsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
|
_appsScroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
|
||||||
const auto recentApps = _recentApps->processTouch(e);
|
const auto recentApps = _recentApps->processTouch(e);
|
||||||
const auto popularApps = _popularApps->processTouch(e);
|
const auto popularApps = _popularApps->processTouch(e);
|
||||||
|
@ -1519,12 +1553,11 @@ void Suggestions::setupApps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
||||||
switch (_tab.current()) {
|
switch (_key.current().tab) {
|
||||||
case Tab::Chats: selectJumpChats(direction, pageSize); return;
|
case Tab::Chats: selectJumpChats(direction, pageSize); return;
|
||||||
case Tab::Channels: selectJumpChannels(direction, pageSize); return;
|
case Tab::Channels: selectJumpChannels(direction, pageSize); return;
|
||||||
case Tab::Apps: selectJumpApps(direction, pageSize); return;
|
case Tab::Apps: selectJumpApps(direction, pageSize); return;
|
||||||
}
|
}
|
||||||
Unexpected("Tab in Suggestions::selectJump.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) {
|
void Suggestions::selectJumpChats(Qt::Key direction, int pageSize) {
|
||||||
|
@ -1702,7 +1735,7 @@ void Suggestions::selectJumpApps(Qt::Key direction, int pageSize) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::chooseRow() {
|
void Suggestions::chooseRow() {
|
||||||
switch (_tab.current()) {
|
switch (_key.current().tab) {
|
||||||
case Tab::Chats:
|
case Tab::Chats:
|
||||||
if (!_topPeers->chooseRow()) {
|
if (!_topPeers->chooseRow()) {
|
||||||
_recent->choose();
|
_recent->choose();
|
||||||
|
@ -1722,9 +1755,11 @@ void Suggestions::chooseRow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) {
|
Data::Thread *Suggestions::updateFromParentDrag(QPoint globalPosition) {
|
||||||
return (_tab.current() == Tab::Chats)
|
switch (_key.current().tab) {
|
||||||
? updateFromChatsDrag(globalPosition)
|
case Tab::Chats: return updateFromChatsDrag(globalPosition);
|
||||||
: updateFromChannelsDrag(globalPosition);
|
case Tab::Channels: return updateFromChannelsDrag(globalPosition);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::Thread *Suggestions::updateFromChatsDrag(QPoint globalPosition) {
|
Data::Thread *Suggestions::updateFromChatsDrag(QPoint globalPosition) {
|
||||||
|
@ -1785,39 +1820,69 @@ void Suggestions::hide(anim::type animated, Fn<void()> finish) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::switchTab(Tab tab) {
|
void Suggestions::switchTab(Key key) {
|
||||||
const auto was = _tab.current();
|
const auto was = _key.current();
|
||||||
if (was == tab) {
|
if (was == key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_tab = tab;
|
_key = key;
|
||||||
_persist = false;
|
_persist = false;
|
||||||
if (_tabs->isHidden()) {
|
if (_tabs->isHidden()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startSlideAnimation(was, tab);
|
startSlideAnimation(was, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::startSlideAnimation(Tab was, Tab now) {
|
void Suggestions::ensureContent(Key key) {
|
||||||
if (!_slideAnimation.animating()) {
|
if (key.tab != Tab::Downloads && key.tab != Tab::Media) {
|
||||||
_slideLeft = (was == Tab::Chats || now == Tab::Chats)
|
return;
|
||||||
? Ui::GrabWidget(_chatsScroll.get())
|
|
||||||
: Ui::GrabWidget(_channelsScroll.get());
|
|
||||||
_slideLeftTop = (was == Tab::Chats || now == Tab::Chats)
|
|
||||||
? _chatsScroll->y()
|
|
||||||
: _channelsScroll->y();
|
|
||||||
_slideRight = (was == Tab::Apps || now == Tab::Apps)
|
|
||||||
? Ui::GrabWidget(_appsScroll.get())
|
|
||||||
: Ui::GrabWidget(_channelsScroll.get());
|
|
||||||
_slideRightTop = (was == Tab::Apps || now == Tab::Apps)
|
|
||||||
? _appsScroll->y()
|
|
||||||
: _channelsScroll->y();
|
|
||||||
_chatsScroll->hide();
|
|
||||||
_channelsScroll->hide();
|
|
||||||
_appsScroll->hide();
|
|
||||||
}
|
}
|
||||||
const auto from = (now > was) ? 0. : 1.;
|
auto &list = _mediaLists[key];
|
||||||
const auto to = (now > was) ? 1. : 0.;
|
if (list.wrap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto self = _controller->session().user();
|
||||||
|
const auto memento = (key.tab == Tab::Downloads)
|
||||||
|
? Info::Downloads::Make(self)
|
||||||
|
: std::make_shared<Info::Memento>(
|
||||||
|
self,
|
||||||
|
Info::Section(key.mediaType, Info::Section::Type::GlobalMedia));
|
||||||
|
list.wrap = Ui::CreateChild<Info::WrapWidget>(
|
||||||
|
this,
|
||||||
|
_controller,
|
||||||
|
Info::Wrap::Search,
|
||||||
|
memento.get());
|
||||||
|
list.wrap->show();
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::startSlideAnimation(Key was, Key now) {
|
||||||
|
ensureContent(now);
|
||||||
|
const auto wasIndex = ranges::find(_tabKeys, was);
|
||||||
|
const auto nowIndex = ranges::find(_tabKeys, now);
|
||||||
|
if (!_slideAnimation.animating()) {
|
||||||
|
const auto find = [&](Key key) -> not_null<QWidget*> {
|
||||||
|
switch (key.tab) {
|
||||||
|
case Tab::Chats: return _chatsScroll.get();
|
||||||
|
case Tab::Channels: return _channelsScroll.get();
|
||||||
|
case Tab::Apps: return _appsScroll.get();
|
||||||
|
}
|
||||||
|
return _mediaLists[key].wrap;
|
||||||
|
};
|
||||||
|
auto left = find(was);
|
||||||
|
auto right = find(now);
|
||||||
|
if (wasIndex > nowIndex) {
|
||||||
|
std::swap(left, right);
|
||||||
|
}
|
||||||
|
_slideLeft = Ui::GrabWidget(left);
|
||||||
|
_slideLeftTop = left->y();
|
||||||
|
_slideRight = Ui::GrabWidget(right);
|
||||||
|
_slideRightTop = right->y();
|
||||||
|
left->hide();
|
||||||
|
right->hide();
|
||||||
|
}
|
||||||
|
const auto from = (nowIndex > wasIndex) ? 0. : 1.;
|
||||||
|
const auto to = (nowIndex > wasIndex) ? 1. : 0.;
|
||||||
_slideAnimation.start([=] {
|
_slideAnimation.start([=] {
|
||||||
update();
|
update();
|
||||||
if (!_slideAnimation.animating() && !_shownAnimation.animating()) {
|
if (!_slideAnimation.animating() && !_shownAnimation.animating()) {
|
||||||
|
@ -1852,6 +1917,9 @@ void Suggestions::startShownAnimation(bool shown, Fn<void()> finish) {
|
||||||
_chatsScroll->hide();
|
_chatsScroll->hide();
|
||||||
_channelsScroll->hide();
|
_channelsScroll->hide();
|
||||||
_appsScroll->hide();
|
_appsScroll->hide();
|
||||||
|
for (const auto &[key, list] : _mediaLists) {
|
||||||
|
list.wrap->hide();
|
||||||
|
}
|
||||||
_slideAnimation.stop();
|
_slideAnimation.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1864,10 +1932,13 @@ void Suggestions::finishShow() {
|
||||||
_cache = QPixmap();
|
_cache = QPixmap();
|
||||||
|
|
||||||
_tabsScroll->show();
|
_tabsScroll->show();
|
||||||
const auto tab = _tab.current();
|
const auto key = _key.current();
|
||||||
_chatsScroll->setVisible(tab == Tab::Chats);
|
_chatsScroll->setVisible(key == Key{ Tab::Chats });
|
||||||
_channelsScroll->setVisible(tab == Tab::Channels);
|
_channelsScroll->setVisible(key == Key{ Tab::Channels });
|
||||||
_appsScroll->setVisible(tab == Tab::Apps);
|
_appsScroll->setVisible(key == Key{ Tab::Apps });
|
||||||
|
for (const auto &[mediaKey, list] : _mediaLists) {
|
||||||
|
list.wrap->setVisible(key == mediaKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 Suggestions::shownOpacity() const {
|
float64 Suggestions::shownOpacity() const {
|
||||||
|
@ -1887,7 +1958,7 @@ void Suggestions::paintEvent(QPaintEvent *e) {
|
||||||
p.drawPixmap(0, (opacity - 1.) * slide, _cache);
|
p.drawPixmap(0, (opacity - 1.) * slide, _cache);
|
||||||
} else if (!_slideLeft.isNull()) {
|
} else if (!_slideLeft.isNull()) {
|
||||||
const auto slide = st::topPeers.height + st::searchedBarHeight;
|
const auto slide = st::topPeers.height + st::searchedBarHeight;
|
||||||
const auto right = (_tab.current() == Tab::Channels);
|
const auto right = (_key.current().tab == Tab::Channels);
|
||||||
const auto progress = _slideAnimation.value(right ? 1. : 0.);
|
const auto progress = _slideAnimation.value(right ? 1. : 0.);
|
||||||
p.setOpacity(1. - progress);
|
p.setOpacity(1. - progress);
|
||||||
p.drawPixmap(
|
p.drawPixmap(
|
||||||
|
@ -1903,20 +1974,39 @@ void Suggestions::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::resizeEvent(QResizeEvent *e) {
|
void Suggestions::resizeEvent(QResizeEvent *e) {
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::updateControlsGeometry() {
|
||||||
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
||||||
_tabs->fitWidthToSections();
|
_tabs->fitWidthToSections();
|
||||||
|
|
||||||
const auto tabs = _tabs->height();
|
const auto tabs = _tabs->height();
|
||||||
_tabsScroll->setGeometry(0, 0, w, tabs);
|
_tabsScroll->setGeometry(0, 0, w, tabs);
|
||||||
|
|
||||||
_chatsScroll->setGeometry(0, tabs, w, height() - tabs);
|
const auto content = QRect(0, tabs, w, height() - tabs);
|
||||||
|
|
||||||
|
_chatsScroll->setGeometry(content);
|
||||||
_chatsContent->resizeToWidth(w);
|
_chatsContent->resizeToWidth(w);
|
||||||
|
|
||||||
_channelsScroll->setGeometry(0, tabs, w, height() - tabs);
|
_channelsScroll->setGeometry(content);
|
||||||
_channelsContent->resizeToWidth(w);
|
_channelsContent->resizeToWidth(w);
|
||||||
|
|
||||||
_appsScroll->setGeometry(0, tabs, w, height() - tabs);
|
_appsScroll->setGeometry(content);
|
||||||
_appsContent->resizeToWidth(w);
|
_appsContent->resizeToWidth(w);
|
||||||
|
|
||||||
|
const auto expanding = false;
|
||||||
|
for (const auto &[key, list] : _mediaLists) {
|
||||||
|
const auto full = !list.wrap->scrollBottomSkip();
|
||||||
|
const auto additionalScroll = (full ? st::boxRadius : 0);
|
||||||
|
const auto height = content.height() - (full ? 0 : st::boxRadius);
|
||||||
|
const auto wrapGeometry = QRect{ 0, tabs, w, height};
|
||||||
|
list.wrap->updateGeometry(
|
||||||
|
wrapGeometry,
|
||||||
|
expanding,
|
||||||
|
additionalScroll,
|
||||||
|
content.height());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Suggestions::setupRecentPeers(RecentPeersList recentPeers)
|
auto Suggestions::setupRecentPeers(RecentPeersList recentPeers)
|
||||||
|
@ -2070,8 +2160,8 @@ auto Suggestions::setupRecommendations() -> std::unique_ptr<ObjectList> {
|
||||||
_persist = true;
|
_persist = true;
|
||||||
}, list->lifetime());
|
}, list->lifetime());
|
||||||
|
|
||||||
_tab.value() | rpl::filter(
|
_key.value() | rpl::filter(
|
||||||
rpl::mappers::_1 == Tab::Channels
|
rpl::mappers::_1 == Key{ Tab::Channels }
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
controller->load();
|
controller->load();
|
||||||
}, list->lifetime());
|
}, list->lifetime());
|
||||||
|
@ -2185,8 +2275,8 @@ auto Suggestions::setupPopularApps() -> std::unique_ptr<ObjectList> {
|
||||||
_persist = true;
|
_persist = true;
|
||||||
}, list->lifetime());
|
}, list->lifetime());
|
||||||
|
|
||||||
_tab.value() | rpl::filter(
|
_key.value() | rpl::filter(
|
||||||
rpl::mappers::_1 == Tab::Apps
|
rpl::mappers::_1 == Key{ Tab::Apps }
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
controller->load();
|
controller->load();
|
||||||
}, list->lifetime());
|
}, list->lifetime());
|
||||||
|
|
|
@ -18,10 +18,18 @@ namespace Data {
|
||||||
class Thread;
|
class Thread;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
class WrapWidget;
|
||||||
|
} // namespace Info
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
enum class SharedMediaType : signed char;
|
||||||
|
} // namespace Storage
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class BoxContent;
|
class BoxContent;
|
||||||
class ScrollArea;
|
class ScrollArea;
|
||||||
|
@ -97,10 +105,13 @@ public:
|
||||||
class ObjectListController;
|
class ObjectListController;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using MediaType = Storage::SharedMediaType;
|
||||||
enum class Tab : uchar {
|
enum class Tab : uchar {
|
||||||
Chats,
|
Chats,
|
||||||
Channels,
|
Channels,
|
||||||
Apps,
|
Apps,
|
||||||
|
Media,
|
||||||
|
Downloads,
|
||||||
};
|
};
|
||||||
enum class JumpResult : uchar {
|
enum class JumpResult : uchar {
|
||||||
NotApplied,
|
NotApplied,
|
||||||
|
@ -108,6 +119,14 @@ private:
|
||||||
AppliedAndOut,
|
AppliedAndOut,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Key {
|
||||||
|
Tab tab = Tab::Chats;
|
||||||
|
MediaType mediaType = {};
|
||||||
|
|
||||||
|
friend inline auto operator<=>(Key, Key) = default;
|
||||||
|
friend inline bool operator==(Key, Key) = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct ObjectList {
|
struct ObjectList {
|
||||||
not_null<Ui::SlideWrap<PeerListContent>*> wrap;
|
not_null<Ui::SlideWrap<PeerListContent>*> wrap;
|
||||||
rpl::variable<int> count;
|
rpl::variable<int> count;
|
||||||
|
@ -119,6 +138,11 @@ private:
|
||||||
rpl::event_stream<not_null<PeerData*>> chosen;
|
rpl::event_stream<not_null<PeerData*>> chosen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MediaList {
|
||||||
|
Info::WrapWidget *wrap = nullptr;
|
||||||
|
rpl::variable<int> count;
|
||||||
|
};
|
||||||
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
|
@ -161,19 +185,22 @@ private:
|
||||||
SearchEmptyIcon icon,
|
SearchEmptyIcon icon,
|
||||||
rpl::producer<QString> text);
|
rpl::producer<QString> text);
|
||||||
|
|
||||||
void switchTab(Tab tab);
|
void switchTab(Key key);
|
||||||
void startShownAnimation(bool shown, Fn<void()> finish);
|
void startShownAnimation(bool shown, Fn<void()> finish);
|
||||||
void startSlideAnimation(Tab was, Tab now);
|
void startSlideAnimation(Key was, Key now);
|
||||||
|
void ensureContent(Key key);
|
||||||
void finishShow();
|
void finishShow();
|
||||||
|
|
||||||
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
|
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
|
||||||
|
void updateControlsGeometry();
|
||||||
|
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
|
||||||
const std::unique_ptr<Ui::ScrollArea> _tabsScroll;
|
const std::unique_ptr<Ui::ScrollArea> _tabsScroll;
|
||||||
const not_null<Ui::SettingsSlider*> _tabs;
|
const not_null<Ui::SettingsSlider*> _tabs;
|
||||||
Ui::Animations::Simple _tabsScrollAnimation;
|
Ui::Animations::Simple _tabsScrollAnimation;
|
||||||
rpl::variable<Tab> _tab = Tab::Chats;
|
const std::vector<Key> _tabKeys;
|
||||||
|
rpl::variable<Key> _key;
|
||||||
|
|
||||||
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
|
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
|
||||||
const not_null<Ui::VerticalLayout*> _chatsContent;
|
const not_null<Ui::VerticalLayout*> _chatsContent;
|
||||||
|
@ -203,6 +230,8 @@ private:
|
||||||
const std::unique_ptr<ObjectList> _recentApps;
|
const std::unique_ptr<ObjectList> _recentApps;
|
||||||
const std::unique_ptr<ObjectList> _popularApps;
|
const std::unique_ptr<ObjectList> _popularApps;
|
||||||
|
|
||||||
|
base::flat_map<Key, MediaList> _mediaLists;
|
||||||
|
|
||||||
Ui::Animations::Simple _shownAnimation;
|
Ui::Animations::Simple _shownAnimation;
|
||||||
Fn<void()> _showFinished;
|
Fn<void()> _showFinished;
|
||||||
bool _hidden = false;
|
bool _hidden = false;
|
||||||
|
|
|
@ -108,9 +108,7 @@ bool InnerWidget::showInternal(not_null<Memento*> memento) {
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Media::ListWidget> InnerWidget::setupList() {
|
object_ptr<Media::ListWidget> InnerWidget::setupList() {
|
||||||
auto result = object_ptr<Media::ListWidget>(
|
auto result = object_ptr<Media::ListWidget>(this, _controller);
|
||||||
this,
|
|
||||||
_controller);
|
|
||||||
result->heightValue(
|
result->heightValue(
|
||||||
) | rpl::start_with_next(
|
) | rpl::start_with_next(
|
||||||
[this] { refreshHeight(); },
|
[this] { refreshHeight(); },
|
||||||
|
|
|
@ -65,9 +65,6 @@ Widget::Widget(
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||||
if (!controller()->validateMementoPeer(memento)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (auto downloadsMemento = dynamic_cast<Memento*>(memento.get())) {
|
if (auto downloadsMemento = dynamic_cast<Memento*>(memento.get())) {
|
||||||
restoreState(downloadsMemento);
|
restoreState(downloadsMemento);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
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/global_media/info_global_media_inner_widget.h"
|
||||||
|
|
||||||
|
#include "info/global_media/info_global_media_provider.h"
|
||||||
|
#include "info/global_media/info_global_media_widget.h"
|
||||||
|
#include "info/media/info_media_list_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/search_field_controller.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
|
||||||
|
class EmptyWidget : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
EmptyWidget(QWidget *parent);
|
||||||
|
|
||||||
|
void setFullHeight(rpl::producer<int> fullHeightValue);
|
||||||
|
void setSearchQuery(const QString &query);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
object_ptr<Ui::FlatLabel> _text;
|
||||||
|
int _height = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EmptyWidget::EmptyWidget(QWidget *parent)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _text(this, st::infoEmptyLabel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmptyWidget::setFullHeight(rpl::producer<int> fullHeightValue) {
|
||||||
|
std::move(
|
||||||
|
fullHeightValue
|
||||||
|
) | rpl::start_with_next([this](int fullHeight) {
|
||||||
|
// Make icon center be on 1/3 height.
|
||||||
|
auto iconCenter = fullHeight / 3;
|
||||||
|
auto iconHeight = st::infoEmptyFile.height();
|
||||||
|
auto iconTop = iconCenter - iconHeight / 2;
|
||||||
|
_height = iconTop + st::infoEmptyIconTop;
|
||||||
|
resizeToWidth(width());
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmptyWidget::setSearchQuery(const QString &query) {
|
||||||
|
_text->setText(query.isEmpty()
|
||||||
|
? tr::lng_media_file_empty(tr::now)
|
||||||
|
: tr::lng_media_file_empty_search(tr::now));
|
||||||
|
resizeToWidth(width());
|
||||||
|
}
|
||||||
|
|
||||||
|
int EmptyWidget::resizeGetHeight(int newWidth) {
|
||||||
|
auto labelTop = _height - st::infoEmptyLabelTop;
|
||||||
|
auto labelWidth = newWidth - 2 * st::infoEmptyLabelSkip;
|
||||||
|
_text->resizeToNaturalWidth(labelWidth);
|
||||||
|
|
||||||
|
auto labelLeft = (newWidth - _text->width()) / 2;
|
||||||
|
_text->moveToLeft(labelLeft, labelTop, newWidth);
|
||||||
|
|
||||||
|
update();
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmptyWidget::paintEvent(QPaintEvent *e) {
|
||||||
|
auto p = QPainter(this);
|
||||||
|
|
||||||
|
const auto iconLeft = (width() - st::infoEmptyFile.width()) / 2;
|
||||||
|
const auto iconTop = height() - st::infoEmptyIconTop;
|
||||||
|
st::infoEmptyFile.paint(p, iconLeft, iconTop, width());
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerWidget::InnerWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _controller(controller)
|
||||||
|
, _empty(this) {
|
||||||
|
_empty->heightValue(
|
||||||
|
) | rpl::start_with_next(
|
||||||
|
[this] { refreshHeight(); },
|
||||||
|
_empty->lifetime());
|
||||||
|
_list = setupList();
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<Media::ListWidget> InnerWidget::setupList() {
|
||||||
|
auto result = object_ptr<Media::ListWidget>(this, _controller);
|
||||||
|
|
||||||
|
// Setup list widget connections
|
||||||
|
result->heightValue(
|
||||||
|
) | rpl::start_with_next([this] {
|
||||||
|
refreshHeight();
|
||||||
|
}, result->lifetime());
|
||||||
|
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
result->scrollToRequests(
|
||||||
|
) | rpl::map([widget = result.data()](int to) {
|
||||||
|
return Ui::ScrollToRequest{
|
||||||
|
widget->y() + to,
|
||||||
|
-1
|
||||||
|
};
|
||||||
|
}) | rpl::start_to_stream(
|
||||||
|
_scrollToRequests,
|
||||||
|
result->lifetime());
|
||||||
|
|
||||||
|
_controller->searchQueryValue(
|
||||||
|
) | rpl::start_with_next([this](const QString &query) {
|
||||||
|
_empty->setSearchQuery(query);
|
||||||
|
}, result->lifetime());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::SharedMediaType InnerWidget::type() const {
|
||||||
|
return _controller->section().mediaType();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::visibleTopBottomUpdated(
|
||||||
|
int visibleTop,
|
||||||
|
int visibleBottom) {
|
||||||
|
setChildVisibleTopBottom(_list, visibleTop, visibleBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InnerWidget::showInternal(not_null<Memento*> memento) {
|
||||||
|
if (memento->section().type() == Section::Type::GlobalMedia
|
||||||
|
&& memento->section().mediaType() == type()) {
|
||||||
|
restoreState(memento);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::saveState(not_null<Memento*> memento) {
|
||||||
|
_list->saveState(&memento->media());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::restoreState(not_null<Memento*> memento) {
|
||||||
|
_list->restoreState(&memento->media());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<SelectedItems> InnerWidget::selectedListValue() const {
|
||||||
|
return _selectedLists.events_starting_with(
|
||||||
|
_list->selectedListValue()
|
||||||
|
) | rpl::flatten_latest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::selectionAction(SelectionAction action) {
|
||||||
|
_list->selectionAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerWidget::~InnerWidget() = default;
|
||||||
|
|
||||||
|
int InnerWidget::resizeGetHeight(int newWidth) {
|
||||||
|
_inResize = true;
|
||||||
|
auto guard = gsl::finally([this] { _inResize = false; });
|
||||||
|
|
||||||
|
_list->resizeToWidth(newWidth);
|
||||||
|
_empty->resizeToWidth(newWidth);
|
||||||
|
return recountHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::refreshHeight() {
|
||||||
|
if (_inResize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resize(width(), recountHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
int InnerWidget::recountHeight() {
|
||||||
|
auto top = 0;
|
||||||
|
auto listHeight = 0;
|
||||||
|
if (_list) {
|
||||||
|
_list->moveToLeft(0, top);
|
||||||
|
listHeight = _list->heightNoMargins();
|
||||||
|
top += listHeight;
|
||||||
|
}
|
||||||
|
if (listHeight > 0) {
|
||||||
|
_empty->hide();
|
||||||
|
} else {
|
||||||
|
_empty->show();
|
||||||
|
_empty->moveToLeft(0, top);
|
||||||
|
top += _empty->heightNoMargins();
|
||||||
|
}
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::setScrollHeightValue(rpl::producer<int> value) {
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
_empty->setFullHeight(rpl::combine(
|
||||||
|
std::move(value),
|
||||||
|
_listTops.events_starting_with(
|
||||||
|
_list->topValue()
|
||||||
|
) | rpl::flatten_latest(),
|
||||||
|
_1 - _2));
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Ui::ScrollToRequest> InnerWidget::scrollToRequests() const {
|
||||||
|
return _scrollToRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Info::GlobalMedia
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/widgets/scroll_area.h"
|
||||||
|
#include "base/unique_qptr.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class VerticalLayout;
|
||||||
|
class SearchFieldController;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
enum class SharedMediaType : signed char;
|
||||||
|
} // namespace Storage
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
|
||||||
|
class Controller;
|
||||||
|
struct SelectedItems;
|
||||||
|
enum class SelectionAction;
|
||||||
|
|
||||||
|
namespace Media {
|
||||||
|
class ListWidget;
|
||||||
|
} // namespace Media
|
||||||
|
|
||||||
|
namespace GlobalMedia {
|
||||||
|
|
||||||
|
class Memento;
|
||||||
|
class EmptyWidget;
|
||||||
|
|
||||||
|
class InnerWidget final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
InnerWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller);
|
||||||
|
|
||||||
|
bool showInternal(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
void saveState(not_null<Memento*> memento);
|
||||||
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
void setScrollHeightValue(rpl::producer<int> value);
|
||||||
|
|
||||||
|
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||||
|
rpl::producer<SelectedItems> selectedListValue() const;
|
||||||
|
void selectionAction(SelectionAction action);
|
||||||
|
|
||||||
|
~InnerWidget();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
void visibleTopBottomUpdated(
|
||||||
|
int visibleTop,
|
||||||
|
int visibleBottom) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int recountHeight();
|
||||||
|
void refreshHeight();
|
||||||
|
|
||||||
|
Storage::SharedMediaType type() const;
|
||||||
|
|
||||||
|
object_ptr<Media::ListWidget> setupList();
|
||||||
|
|
||||||
|
const not_null<Controller*> _controller;
|
||||||
|
|
||||||
|
object_ptr<Media::ListWidget> _list = { nullptr };
|
||||||
|
object_ptr<EmptyWidget> _empty;
|
||||||
|
|
||||||
|
bool _inResize = false;
|
||||||
|
|
||||||
|
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||||
|
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
|
||||||
|
rpl::event_stream<rpl::producer<int>> _listTops;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GlobalMedia
|
||||||
|
} // namespace Info
|
|
@ -0,0 +1,632 @@
|
||||||
|
/*
|
||||||
|
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/global_media/info_global_media_provider.h"
|
||||||
|
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "info/media/info_media_widget.h"
|
||||||
|
#include "info/media/info_media_list_section.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "ui/text/format_song_document_name.h"
|
||||||
|
#include "ui/ui_utility.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_media_types.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "main/main_account.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_helpers.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "storage/storage_shared_media.h"
|
||||||
|
#include "layout/layout_selection.h"
|
||||||
|
#include "styles/style_overview.h"
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kPerPage = 50;
|
||||||
|
constexpr auto kPreloadedScreensCount = 4;
|
||||||
|
constexpr auto kPreloadedScreensCountFull
|
||||||
|
= kPreloadedScreensCount + 1 + kPreloadedScreensCount;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
GlobalMediaSlice::GlobalMediaSlice(
|
||||||
|
Key key,
|
||||||
|
std::vector<Data::MessagePosition> items,
|
||||||
|
std::optional<int> fullCount,
|
||||||
|
int skippedAfter)
|
||||||
|
: _key(key)
|
||||||
|
, _items(std::move(items))
|
||||||
|
, _fullCount(fullCount)
|
||||||
|
, _skippedAfter(skippedAfter) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GlobalMediaSlice::fullCount() const {
|
||||||
|
return _fullCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GlobalMediaSlice::skippedBefore() const {
|
||||||
|
return _fullCount
|
||||||
|
? int(*_fullCount - _skippedAfter - _items.size())
|
||||||
|
: std::optional<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GlobalMediaSlice::skippedAfter() const {
|
||||||
|
return _skippedAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GlobalMediaSlice::indexOf(Value position) const {
|
||||||
|
const auto it = ranges::find(_items, position);
|
||||||
|
return (it != end(_items))
|
||||||
|
? std::make_optional(int(it - begin(_items)))
|
||||||
|
: std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GlobalMediaSlice::size() const {
|
||||||
|
return _items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalMediaSlice::Value GlobalMediaSlice::operator[](int index) const {
|
||||||
|
Expects(index >= 0 && index < size());
|
||||||
|
|
||||||
|
return _items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> GlobalMediaSlice::distance(
|
||||||
|
const Key &a,
|
||||||
|
const Key &b) const {
|
||||||
|
const auto i = indexOf(a.aroundId);
|
||||||
|
const auto j = indexOf(b.aroundId);
|
||||||
|
return (i && j) ? std::make_optional(*j - *i) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GlobalMediaSlice::Value> GlobalMediaSlice::nearest(
|
||||||
|
Value position) const {
|
||||||
|
if (_items.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto it = ranges::lower_bound(
|
||||||
|
_items,
|
||||||
|
position,
|
||||||
|
std::greater<>{});
|
||||||
|
|
||||||
|
if (it == end(_items)) {
|
||||||
|
return _items.back();
|
||||||
|
} else if (it == begin(_items)) {
|
||||||
|
return _items.front();
|
||||||
|
}
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider::Provider(not_null<AbstractController*> controller)
|
||||||
|
: _controller(controller)
|
||||||
|
, _type(_controller->section().mediaType())
|
||||||
|
, _slice(sliceKey(_aroundId)) {
|
||||||
|
_controller->session().data().itemRemoved(
|
||||||
|
) | rpl::start_with_next([this](auto item) {
|
||||||
|
itemRemoved(item);
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
for (auto &layout : _layouts) {
|
||||||
|
layout.second.item->invalidateCache();
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider::Type Provider::type() {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::hasSelectRestriction() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
|
||||||
|
return rpl::never<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::sectionHasFloatingHeader() {
|
||||||
|
switch (_type) {
|
||||||
|
case Type::Photo:
|
||||||
|
case Type::GIF:
|
||||||
|
case Type::Video:
|
||||||
|
case Type::RoundFile:
|
||||||
|
case Type::RoundVoiceFile:
|
||||||
|
case Type::MusicFile:
|
||||||
|
return false;
|
||||||
|
case Type::File:
|
||||||
|
case Type::Link:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Unexpected("Type in HasFloatingHeader()");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Provider::sectionTitle(not_null<const BaseLayout*> item) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::sectionItemBelongsHere(
|
||||||
|
not_null<const BaseLayout*> item,
|
||||||
|
not_null<const BaseLayout*> previous) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
|
||||||
|
return item->media() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> Provider::fullCount() {
|
||||||
|
return _slice.fullCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::restart() {
|
||||||
|
_layouts.clear();
|
||||||
|
_aroundId = Data::MaxMessagePosition;
|
||||||
|
_idsLimit = kMinimalIdsLimit;
|
||||||
|
_slice = GlobalMediaSlice(sliceKey(_aroundId));
|
||||||
|
refreshViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::checkPreload(
|
||||||
|
QSize viewport,
|
||||||
|
not_null<BaseLayout*> topLayout,
|
||||||
|
not_null<BaseLayout*> bottomLayout,
|
||||||
|
bool preloadTop,
|
||||||
|
bool preloadBottom) {
|
||||||
|
const auto visibleWidth = viewport.width();
|
||||||
|
const auto visibleHeight = viewport.height();
|
||||||
|
const auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight;
|
||||||
|
const auto minItemHeight = Media::MinItemHeight(_type, visibleWidth);
|
||||||
|
const auto preloadedCount = preloadedHeight / minItemHeight;
|
||||||
|
const auto preloadIdsLimitMin = (preloadedCount / 2) + 1;
|
||||||
|
const auto preloadIdsLimit = preloadIdsLimitMin
|
||||||
|
+ (visibleHeight / minItemHeight);
|
||||||
|
const auto after = _slice.skippedAfter();
|
||||||
|
const auto topLoaded = after && (*after == 0);
|
||||||
|
const auto before = _slice.skippedBefore();
|
||||||
|
const auto bottomLoaded = before && (*before == 0);
|
||||||
|
|
||||||
|
const auto minScreenDelta = kPreloadedScreensCount
|
||||||
|
- Media::kPreloadIfLessThanScreens;
|
||||||
|
const auto minUniversalIdDelta = (minScreenDelta * visibleHeight)
|
||||||
|
/ minItemHeight;
|
||||||
|
const auto preloadAroundItem = [&](not_null<BaseLayout*> layout) {
|
||||||
|
auto preloadRequired = false;
|
||||||
|
auto aroundId = layout->getItem()->position();
|
||||||
|
if (!preloadRequired) {
|
||||||
|
preloadRequired = (_idsLimit < preloadIdsLimitMin);
|
||||||
|
}
|
||||||
|
if (!preloadRequired) {
|
||||||
|
auto delta = _slice.distance(
|
||||||
|
sliceKey(_aroundId),
|
||||||
|
sliceKey(aroundId));
|
||||||
|
Assert(delta != std::nullopt);
|
||||||
|
preloadRequired = (qAbs(*delta) >= minUniversalIdDelta);
|
||||||
|
}
|
||||||
|
if (preloadRequired) {
|
||||||
|
_idsLimit = preloadIdsLimit;
|
||||||
|
_aroundId = aroundId;
|
||||||
|
refreshViewer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (preloadTop && !topLoaded) {
|
||||||
|
preloadAroundItem(topLayout);
|
||||||
|
} else if (preloadBottom && !bottomLoaded) {
|
||||||
|
preloadAroundItem(bottomLayout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::applyListQuery(const QString &query) {
|
||||||
|
if (_totalListQuery == query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_totalListQuery = query;
|
||||||
|
_totalList.clear();
|
||||||
|
_totalOffsetPosition = Data::MessagePosition();
|
||||||
|
_totalOffsetRate = 0;
|
||||||
|
_totalFullCount = 0;
|
||||||
|
_totalLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<GlobalMediaSlice> Provider::source(
|
||||||
|
Type type,
|
||||||
|
Data::MessagePosition aroundId,
|
||||||
|
QString query,
|
||||||
|
int limitBefore,
|
||||||
|
int limitAfter) {
|
||||||
|
Expects(_type == type);
|
||||||
|
|
||||||
|
applyListQuery(query);
|
||||||
|
return [=](auto consumer) {
|
||||||
|
auto lifetime = rpl::lifetime();
|
||||||
|
const auto session = &_controller->session();
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
State(not_null<Main::Session*> session) : session(session) {
|
||||||
|
}
|
||||||
|
~State() {
|
||||||
|
session->api().request(requestId).cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
const not_null<Main::Session*> session;
|
||||||
|
Fn<void()> pushAndLoadMore;
|
||||||
|
mtpRequestId requestId = 0;
|
||||||
|
};
|
||||||
|
const auto state = lifetime.make_state<State>(session);
|
||||||
|
|
||||||
|
state->pushAndLoadMore = [=] {
|
||||||
|
auto result = fillRequest(aroundId, limitBefore, limitAfter);
|
||||||
|
consumer.put_next(std::move(result.slice));
|
||||||
|
if (!_totalLoaded && result.notEnough) {
|
||||||
|
state->requestId = requestMore(state->pushAndLoadMore);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state->pushAndLoadMore();
|
||||||
|
|
||||||
|
return lifetime;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mtpRequestId Provider::requestMore(Fn<void()> loaded) {
|
||||||
|
const auto done = [=](const Api::GlobalMediaResult &result) {
|
||||||
|
if (result.messageIds.empty()) {
|
||||||
|
_totalLoaded = true;
|
||||||
|
_totalFullCount = _totalList.size();
|
||||||
|
} else {
|
||||||
|
_totalList.reserve(_totalList.size() + result.messageIds.size());
|
||||||
|
_totalFullCount = result.fullCount;
|
||||||
|
for (const auto &position : result.messageIds) {
|
||||||
|
_seenIds.emplace(position.fullId);
|
||||||
|
_totalOffsetPosition = position;
|
||||||
|
_totalList.push_back(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result.offsetRate) {
|
||||||
|
_totalLoaded = true;
|
||||||
|
} else {
|
||||||
|
_totalOffsetRate = result.offsetRate;
|
||||||
|
}
|
||||||
|
loaded();
|
||||||
|
};
|
||||||
|
return _controller->session().api().requestGlobalMedia(
|
||||||
|
_type,
|
||||||
|
_totalListQuery,
|
||||||
|
_totalOffsetRate,
|
||||||
|
_totalOffsetPosition,
|
||||||
|
done);
|
||||||
|
}
|
||||||
|
|
||||||
|
Provider::FillResult Provider::fillRequest(
|
||||||
|
Data::MessagePosition aroundId,
|
||||||
|
int limitBefore,
|
||||||
|
int limitAfter) {
|
||||||
|
const auto i = ranges::lower_bound(
|
||||||
|
_totalList,
|
||||||
|
aroundId,
|
||||||
|
std::greater<>());
|
||||||
|
const auto hasAfter = int(i - begin(_totalList));
|
||||||
|
const auto hasBefore = int(end(_totalList) - i);
|
||||||
|
const auto takeAfter = std::min(limitAfter, hasAfter);
|
||||||
|
const auto takeBefore = std::min(limitBefore, hasBefore);
|
||||||
|
auto list = std::vector<Data::MessagePosition>{
|
||||||
|
i - takeAfter,
|
||||||
|
i + takeBefore,
|
||||||
|
};
|
||||||
|
return FillResult{
|
||||||
|
.slice = GlobalMediaSlice(
|
||||||
|
GlobalMediaKey{ aroundId },
|
||||||
|
std::move(list),
|
||||||
|
((!_totalList.empty() || _totalLoaded)
|
||||||
|
? _totalFullCount
|
||||||
|
: std::optional<int>()),
|
||||||
|
hasAfter - takeAfter),
|
||||||
|
.notEnough = (takeBefore < limitBefore),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::refreshViewer() {
|
||||||
|
_viewerLifetime.destroy();
|
||||||
|
_controller->searchQueryValue(
|
||||||
|
) | rpl::map([=](QString query) {
|
||||||
|
return source(
|
||||||
|
_type,
|
||||||
|
sliceKey(_aroundId).aroundId,
|
||||||
|
query,
|
||||||
|
_idsLimit,
|
||||||
|
_idsLimit);
|
||||||
|
}) | rpl::flatten_latest(
|
||||||
|
) | rpl::start_with_next([=](GlobalMediaSlice &&slice) {
|
||||||
|
if (!slice.fullCount()) {
|
||||||
|
// Don't display anything while full count is unknown.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_slice = std::move(slice);
|
||||||
|
if (auto nearest = _slice.nearest(_aroundId)) {
|
||||||
|
_aroundId = *nearest;
|
||||||
|
}
|
||||||
|
_refreshed.fire({});
|
||||||
|
}, _viewerLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> Provider::refreshed() {
|
||||||
|
return _refreshed.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Media::ListSection> Provider::fillSections(
|
||||||
|
not_null<Overview::Layout::Delegate*> delegate) {
|
||||||
|
markLayoutsStale();
|
||||||
|
const auto guard = gsl::finally([&] { clearStaleLayouts(); });
|
||||||
|
|
||||||
|
auto result = std::vector<Media::ListSection>();
|
||||||
|
result.emplace_back(_type, sectionDelegate());
|
||||||
|
auto §ion = result.back();
|
||||||
|
for (auto i = 0, count = int(_slice.size()); i != count; ++i) {
|
||||||
|
auto position = _slice[i];
|
||||||
|
if (auto layout = getLayout(position.fullId, delegate)) {
|
||||||
|
section.addItem(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (section.empty()) {
|
||||||
|
result.pop_back();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::markLayoutsStale() {
|
||||||
|
for (auto &layout : _layouts) {
|
||||||
|
layout.second.stale = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::clearStaleLayouts() {
|
||||||
|
for (auto i = _layouts.begin(); i != _layouts.end();) {
|
||||||
|
if (i->second.stale) {
|
||||||
|
_layoutRemoved.fire(i->second.item.get());
|
||||||
|
i = _layouts.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<not_null<Media::BaseLayout*>> Provider::layoutRemoved() {
|
||||||
|
return _layoutRemoved.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
Media::BaseLayout *Provider::lookupLayout(
|
||||||
|
const HistoryItem *item) {
|
||||||
|
const auto i = _layouts.find(item ? item->fullId() : FullMsgId());
|
||||||
|
return (i != _layouts.end()) ? i->second.item.get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::isMyItem(not_null<const HistoryItem*> item) {
|
||||||
|
return _seenIds.contains(item->fullId());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::isAfter(
|
||||||
|
not_null<const HistoryItem*> a,
|
||||||
|
not_null<const HistoryItem*> b) {
|
||||||
|
return (a->fullId() < b->fullId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::setSearchQuery(QString query) {
|
||||||
|
Unexpected("Media::Provider::setSearchQuery.");
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalMediaKey Provider::sliceKey(Data::MessagePosition aroundId) const {
|
||||||
|
return GlobalMediaKey{ aroundId };
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::itemRemoved(not_null<const HistoryItem*> item) {
|
||||||
|
const auto id = item->fullId();
|
||||||
|
if (const auto i = _layouts.find(id); i != end(_layouts)) {
|
||||||
|
_layoutRemoved.fire(i->second.item.get());
|
||||||
|
_layouts.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Media::BaseLayout *Provider::getLayout(
|
||||||
|
FullMsgId itemId,
|
||||||
|
not_null<Overview::Layout::Delegate*> delegate) {
|
||||||
|
auto it = _layouts.find(itemId);
|
||||||
|
if (it == _layouts.end()) {
|
||||||
|
if (auto layout = createLayout(itemId, delegate, _type)) {
|
||||||
|
layout->initDimensions();
|
||||||
|
it = _layouts.emplace(
|
||||||
|
itemId,
|
||||||
|
std::move(layout)).first;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it->second.stale = false;
|
||||||
|
return it->second.item.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Media::BaseLayout> Provider::createLayout(
|
||||||
|
FullMsgId itemId,
|
||||||
|
not_null<Overview::Layout::Delegate*> delegate,
|
||||||
|
Type type) {
|
||||||
|
const auto item = _controller->session().data().message(itemId);
|
||||||
|
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 &songSt = st::overviewFileLayout;
|
||||||
|
using namespace Overview::Layout;
|
||||||
|
const auto options = [&] {
|
||||||
|
const auto media = item->media();
|
||||||
|
return MediaOptions{ .spoiler = media && media->hasSpoiler() };
|
||||||
|
};
|
||||||
|
switch (type) {
|
||||||
|
case Type::Photo:
|
||||||
|
if (const auto photo = getPhoto()) {
|
||||||
|
return std::make_unique<Photo>(
|
||||||
|
delegate,
|
||||||
|
item,
|
||||||
|
photo,
|
||||||
|
options());
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
case Type::GIF:
|
||||||
|
if (const auto file = getFile()) {
|
||||||
|
return std::make_unique<Gif>(delegate, item, file);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
case Type::Video:
|
||||||
|
if (const auto file = getFile()) {
|
||||||
|
return std::make_unique<Video>(delegate, item, file, options());
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
case Type::File:
|
||||||
|
if (const auto file = getFile()) {
|
||||||
|
return std::make_unique<Document>(
|
||||||
|
delegate,
|
||||||
|
item,
|
||||||
|
DocumentFields{ .document = file },
|
||||||
|
songSt);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
case Type::MusicFile:
|
||||||
|
if (const auto file = getFile()) {
|
||||||
|
return std::make_unique<Document>(
|
||||||
|
delegate,
|
||||||
|
item,
|
||||||
|
DocumentFields{ .document = file },
|
||||||
|
songSt);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
case Type::RoundVoiceFile:
|
||||||
|
if (const auto file = getFile()) {
|
||||||
|
return std::make_unique<Voice>(delegate, item, file, songSt);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
case Type::Link:
|
||||||
|
return std::make_unique<Link>(delegate, item, item->media());
|
||||||
|
case Type::RoundFile:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Unexpected("Type in ListWidget::createLayout()");
|
||||||
|
}
|
||||||
|
|
||||||
|
Media::ListItemSelectionData Provider::computeSelectionData(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
TextSelection selection) {
|
||||||
|
auto result = Media::ListItemSelectionData(selection);
|
||||||
|
result.canDelete = item->canDelete();
|
||||||
|
result.canForward = item->allowsForward();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Provider::allowSaveFileAs(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
not_null<DocumentData*> document) {
|
||||||
|
return item->allowsForward();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Provider::showInFolderPath(
|
||||||
|
not_null<const HistoryItem*> item,
|
||||||
|
not_null<DocumentData*> document) {
|
||||||
|
return document->filepath(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::applyDragSelection(
|
||||||
|
Media::ListSelectedMap &selected,
|
||||||
|
not_null<const HistoryItem*> fromItem,
|
||||||
|
bool skipFrom,
|
||||||
|
not_null<const HistoryItem*> tillItem,
|
||||||
|
bool skipTill) {
|
||||||
|
#if 0 // not used for now
|
||||||
|
const auto fromId = GetUniversalId(fromItem) - (skipFrom ? 1 : 0);
|
||||||
|
const auto tillId = GetUniversalId(tillItem) - (skipTill ? 0 : 1);
|
||||||
|
for (auto i = selected.begin(); i != selected.end();) {
|
||||||
|
const auto itemId = GetUniversalId(i->first);
|
||||||
|
if (itemId > fromId || itemId <= tillId) {
|
||||||
|
i = selected.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto &layoutItem : _layouts) {
|
||||||
|
auto &&universalId = layoutItem.first;
|
||||||
|
if (universalId <= fromId && universalId > tillId) {
|
||||||
|
const auto item = layoutItem.second.item->getItem();
|
||||||
|
ChangeItemSelection(
|
||||||
|
selected,
|
||||||
|
item,
|
||||||
|
computeSelectionData(item, FullSelection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // todo global media
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 Provider::scrollTopStatePosition(not_null<HistoryItem*> item) {
|
||||||
|
return item->position().date;
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryItem *Provider::scrollTopStateItem(Media::ListScrollTopState state) {
|
||||||
|
const auto maybe = Data::MessagePosition{
|
||||||
|
.date = TimeId(state.position),
|
||||||
|
};
|
||||||
|
if (state.item && _slice.indexOf(state.item->position())) {
|
||||||
|
return state.item;
|
||||||
|
} else if (const auto position = _slice.nearest(maybe)) {
|
||||||
|
const auto id = position->fullId;
|
||||||
|
if (const auto item = _controller->session().data().message(id)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::saveState(
|
||||||
|
not_null<Media::Memento*> memento,
|
||||||
|
Media::ListScrollTopState scrollState) {
|
||||||
|
if (_aroundId != Data::MaxMessagePosition && scrollState.item) {
|
||||||
|
memento->setAroundId(_aroundId.fullId);
|
||||||
|
memento->setIdsLimit(_idsLimit);
|
||||||
|
memento->setScrollTopItem(scrollState.item->globalId());
|
||||||
|
memento->setScrollTopItemPosition(scrollState.position);
|
||||||
|
memento->setScrollTopShift(scrollState.shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Provider::restoreState(
|
||||||
|
not_null<Media::Memento*> memento,
|
||||||
|
Fn<void(Media::ListScrollTopState)> restoreScrollState) {
|
||||||
|
if (const auto limit = memento->idsLimit()) {
|
||||||
|
_idsLimit = limit;
|
||||||
|
_aroundId = { memento->aroundId() };
|
||||||
|
restoreScrollState({
|
||||||
|
.position = memento->scrollTopItemPosition(),
|
||||||
|
.item = MessageByGlobalId(memento->scrollTopItem()),
|
||||||
|
.shift = memento->scrollTopShift(),
|
||||||
|
});
|
||||||
|
refreshViewer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Info::GlobalMedia
|
|
@ -0,0 +1,188 @@
|
||||||
|
/*
|
||||||
|
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_messages.h"
|
||||||
|
#include "info/media/info_media_common.h"
|
||||||
|
#include "base/weak_ptr.h"
|
||||||
|
|
||||||
|
namespace Info {
|
||||||
|
class AbstractController;
|
||||||
|
} // namespace Info
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
|
||||||
|
struct GlobalMediaKey {
|
||||||
|
Data::MessagePosition aroundId;
|
||||||
|
|
||||||
|
friend inline constexpr bool operator==(
|
||||||
|
const GlobalMediaKey &,
|
||||||
|
const GlobalMediaKey &) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GlobalMediaSlice final {
|
||||||
|
public:
|
||||||
|
using Key = GlobalMediaKey;
|
||||||
|
using Value = Data::MessagePosition;
|
||||||
|
|
||||||
|
explicit GlobalMediaSlice(
|
||||||
|
Key key,
|
||||||
|
std::vector<Data::MessagePosition> items = {},
|
||||||
|
std::optional<int> fullCount = std::nullopt,
|
||||||
|
int skippedAfter = 0);
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<int> fullCount() const;
|
||||||
|
[[nodiscard]] std::optional<int> skippedBefore() const;
|
||||||
|
[[nodiscard]] std::optional<int> skippedAfter() const;
|
||||||
|
[[nodiscard]] std::optional<int> indexOf(Value fullId) const;
|
||||||
|
[[nodiscard]] int size() const;
|
||||||
|
[[nodiscard]] Value operator[](int index) const;
|
||||||
|
[[nodiscard]] std::optional<int> distance(
|
||||||
|
const Key &a,
|
||||||
|
const Key &b) const;
|
||||||
|
[[nodiscard]] std::optional<Value> nearest(Value id) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GlobalMediaKey _key;
|
||||||
|
std::vector<Data::MessagePosition> _items;
|
||||||
|
std::optional<int> _fullCount;
|
||||||
|
int _skippedAfter = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Provider final
|
||||||
|
: public Media::ListProvider
|
||||||
|
, private Media::ListSectionDelegate {
|
||||||
|
public:
|
||||||
|
using Type = Media::Type;
|
||||||
|
using BaseLayout = Media::BaseLayout;
|
||||||
|
|
||||||
|
explicit Provider(not_null<AbstractController*> controller);
|
||||||
|
|
||||||
|
Type type() override;
|
||||||
|
bool hasSelectRestriction() override;
|
||||||
|
rpl::producer<bool> hasSelectRestrictionChanges() override;
|
||||||
|
bool isPossiblyMyItem(not_null<const HistoryItem*> item) override;
|
||||||
|
|
||||||
|
std::optional<int> fullCount() override;
|
||||||
|
|
||||||
|
void restart() override;
|
||||||
|
void checkPreload(
|
||||||
|
QSize viewport,
|
||||||
|
not_null<BaseLayout*> topLayout,
|
||||||
|
not_null<BaseLayout*> bottomLayout,
|
||||||
|
bool preloadTop,
|
||||||
|
bool preloadBottom) override;
|
||||||
|
void refreshViewer() override;
|
||||||
|
rpl::producer<> refreshed() override;
|
||||||
|
|
||||||
|
std::vector<Media::ListSection> fillSections(
|
||||||
|
not_null<Overview::Layout::Delegate*> delegate) override;
|
||||||
|
rpl::producer<not_null<BaseLayout*>> layoutRemoved() override;
|
||||||
|
BaseLayout *lookupLayout(const HistoryItem *item) override;
|
||||||
|
bool isMyItem(not_null<const HistoryItem*> item) override;
|
||||||
|
bool isAfter(
|
||||||
|
not_null<const HistoryItem*> a,
|
||||||
|
not_null<const HistoryItem*> b) override;
|
||||||
|
|
||||||
|
void setSearchQuery(QString query) 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;
|
||||||
|
|
||||||
|
struct FillResult {
|
||||||
|
GlobalMediaSlice slice;
|
||||||
|
bool notEnough = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool sectionHasFloatingHeader() override;
|
||||||
|
QString sectionTitle(not_null<const BaseLayout*> item) override;
|
||||||
|
bool sectionItemBelongsHere(
|
||||||
|
not_null<const BaseLayout*> item,
|
||||||
|
not_null<const BaseLayout*> previous) override;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<GlobalMediaSlice> source(
|
||||||
|
Type type,
|
||||||
|
Data::MessagePosition aroundId,
|
||||||
|
QString query,
|
||||||
|
int limitBefore,
|
||||||
|
int limitAfter);
|
||||||
|
|
||||||
|
[[nodiscard]] BaseLayout *getLayout(
|
||||||
|
FullMsgId itemId,
|
||||||
|
not_null<Overview::Layout::Delegate*> delegate);
|
||||||
|
[[nodiscard]] std::unique_ptr<BaseLayout> createLayout(
|
||||||
|
FullMsgId itemId,
|
||||||
|
not_null<Overview::Layout::Delegate*> delegate,
|
||||||
|
Type type);
|
||||||
|
|
||||||
|
[[nodiscard]] GlobalMediaKey sliceKey(
|
||||||
|
Data::MessagePosition aroundId) const;
|
||||||
|
|
||||||
|
void itemRemoved(not_null<const HistoryItem*> item);
|
||||||
|
void markLayoutsStale();
|
||||||
|
void clearStaleLayouts();
|
||||||
|
void applyListQuery(const QString &query);
|
||||||
|
[[nodiscard]] FillResult fillRequest(
|
||||||
|
Data::MessagePosition aroundId,
|
||||||
|
int limitBefore,
|
||||||
|
int limitAfter);
|
||||||
|
mtpRequestId requestMore(Fn<void()> loaded);
|
||||||
|
|
||||||
|
const not_null<AbstractController*> _controller;
|
||||||
|
const Type _type = {};
|
||||||
|
|
||||||
|
Data::MessagePosition _aroundId = Data::MaxMessagePosition;
|
||||||
|
int _idsLimit = kMinimalIdsLimit;
|
||||||
|
GlobalMediaSlice _slice;
|
||||||
|
|
||||||
|
base::flat_set<FullMsgId> _seenIds;
|
||||||
|
std::unordered_map<FullMsgId, Media::CachedItem> _layouts;
|
||||||
|
rpl::event_stream<not_null<BaseLayout*>> _layoutRemoved;
|
||||||
|
rpl::event_stream<> _refreshed;
|
||||||
|
|
||||||
|
QString _totalListQuery;
|
||||||
|
std::vector<Data::MessagePosition> _totalList;
|
||||||
|
Data::MessagePosition _totalOffsetPosition;
|
||||||
|
int32 _totalOffsetRate = 0;
|
||||||
|
int _totalFullCount = 0;
|
||||||
|
bool _totalLoaded = false;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
rpl::lifetime _viewerLifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Info::GlobalMedia
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "info/global_media/info_global_media_widget.h"
|
||||||
|
|
||||||
|
#include "info/global_media/info_global_media_inner_widget.h"
|
||||||
|
#include "info/info_controller.h"
|
||||||
|
#include "info/info_memento.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "ui/boxes/confirm_box.h"
|
||||||
|
#include "ui/search_field_controller.h"
|
||||||
|
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||||
|
#include "ui/widgets/scroll_area.h"
|
||||||
|
#include "ui/ui_utility.h"
|
||||||
|
#include "data/data_download_manager.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
|
||||||
|
Memento::Memento(not_null<Controller*> controller)
|
||||||
|
: ContentMemento(Tag{ controller->session().user() })
|
||||||
|
, _media(controller) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Memento::Memento(not_null<UserData*> self, Storage::SharedMediaType type)
|
||||||
|
: ContentMemento(Tag{ self })
|
||||||
|
, _media(self, 0, type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Memento::~Memento() = default;
|
||||||
|
|
||||||
|
Section Memento::section() const {
|
||||||
|
return Section(_media.type(), Section::Type::GlobalMedia);
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<ContentWidget> Memento::createWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
const QRect &geometry) {
|
||||||
|
auto result = object_ptr<Widget>(parent, controller);
|
||||||
|
result->setInternalState(geometry, this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget::Widget(QWidget *parent, not_null<Controller*> controller)
|
||||||
|
: ContentWidget(parent, controller) {
|
||||||
|
_inner = setInnerWidget(object_ptr<InnerWidget>(
|
||||||
|
this,
|
||||||
|
controller));
|
||||||
|
_inner->setScrollHeightValue(scrollHeightValue());
|
||||||
|
_inner->scrollToRequests(
|
||||||
|
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||||
|
scrollTo(request);
|
||||||
|
}, _inner->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Widget::showInternal(not_null<ContentMemento*> memento) {
|
||||||
|
if (auto globalMediaMemento = dynamic_cast<Memento*>(memento.get())) {
|
||||||
|
restoreState(globalMediaMemento);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::setInternalState(
|
||||||
|
const QRect &geometry,
|
||||||
|
not_null<Memento*> memento) {
|
||||||
|
setGeometry(geometry);
|
||||||
|
Ui::SendPendingMoveResizeEvents(this);
|
||||||
|
restoreState(memento);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ContentMemento> Widget::doCreateMemento() {
|
||||||
|
auto result = std::make_shared<Memento>(controller());
|
||||||
|
saveState(result.get());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::saveState(not_null<Memento*> memento) {
|
||||||
|
memento->setScrollTop(scrollTopSave());
|
||||||
|
_inner->saveState(memento);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::restoreState(not_null<Memento*> memento) {
|
||||||
|
_inner->restoreState(memento);
|
||||||
|
scrollTopRestore(memento->scrollTop());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<SelectedItems> Widget::selectedListValue() const {
|
||||||
|
return _inner->selectedListValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::selectionAction(SelectionAction action) {
|
||||||
|
_inner->selectionAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
|
||||||
|
const auto window = controller()->parentController();
|
||||||
|
const auto deleteAll = [=] {
|
||||||
|
auto &manager = Core::App().downloadManager();
|
||||||
|
const auto phrase = tr::lng_downloads_delete_sure_all(tr::now);
|
||||||
|
const auto added = manager.loadedHasNonCloudFile()
|
||||||
|
? QString()
|
||||||
|
: tr::lng_downloads_delete_in_cloud(tr::now);
|
||||||
|
const auto deleteSure = [=, &manager](Fn<void()> close) {
|
||||||
|
Ui::PostponeCall(this, close);
|
||||||
|
manager.deleteAll();
|
||||||
|
};
|
||||||
|
window->show(Ui::MakeConfirmBox({
|
||||||
|
.text = phrase + (added.isEmpty() ? QString() : "\n\n" + added),
|
||||||
|
.confirmed = deleteSure,
|
||||||
|
.confirmText = tr::lng_box_delete(tr::now),
|
||||||
|
.confirmStyle = &st::attentionBoxButton,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
addAction(
|
||||||
|
tr::lng_context_delete_all_files(tr::now),
|
||||||
|
deleteAll,
|
||||||
|
&st::menuIconDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> Widget::title() {
|
||||||
|
return tr::lng_profile_shared_media();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Info::Memento> Make(
|
||||||
|
not_null<UserData*> self,
|
||||||
|
Storage::SharedMediaType type) {
|
||||||
|
return std::make_shared<Info::Memento>(
|
||||||
|
std::vector<std::shared_ptr<ContentMemento>>(
|
||||||
|
1,
|
||||||
|
std::make_shared<Memento>(self, type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Info::GlobalMedia
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "info/info_content_widget.h"
|
||||||
|
#include "info/media/info_media_widget.h"
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
enum class SharedMediaType : signed char;
|
||||||
|
} // namespace Storage
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
|
||||||
|
class InnerWidget;
|
||||||
|
|
||||||
|
class Memento final : public ContentMemento {
|
||||||
|
public:
|
||||||
|
Memento(not_null<Controller*> controller);
|
||||||
|
Memento(not_null<UserData*> self, Storage::SharedMediaType type);
|
||||||
|
~Memento();
|
||||||
|
|
||||||
|
object_ptr<ContentWidget> createWidget(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
const QRect &geometry) override;
|
||||||
|
|
||||||
|
Section section() const override;
|
||||||
|
|
||||||
|
[[nodiscard]] Media::Memento &media() {
|
||||||
|
return _media;
|
||||||
|
}
|
||||||
|
[[nodiscard]] const Media::Memento &media() const {
|
||||||
|
return _media;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Media::Memento _media;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Widget final : public ContentWidget {
|
||||||
|
public:
|
||||||
|
Widget(QWidget *parent, not_null<Controller*> controller);
|
||||||
|
|
||||||
|
bool showInternal(
|
||||||
|
not_null<ContentMemento*> memento) override;
|
||||||
|
|
||||||
|
void setInternalState(
|
||||||
|
const QRect &geometry,
|
||||||
|
not_null<Memento*> memento);
|
||||||
|
|
||||||
|
rpl::producer<SelectedItems> selectedListValue() const override;
|
||||||
|
void selectionAction(SelectionAction action) override;
|
||||||
|
|
||||||
|
void fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) override;
|
||||||
|
|
||||||
|
rpl::producer<QString> title() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void saveState(not_null<Memento*> memento);
|
||||||
|
void restoreState(not_null<Memento*> memento);
|
||||||
|
|
||||||
|
std::shared_ptr<ContentMemento> doCreateMemento() override;
|
||||||
|
|
||||||
|
InnerWidget *_inner = nullptr;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<Info::Memento> Make(
|
||||||
|
not_null<UserData*> self,
|
||||||
|
Storage::SharedMediaType type);
|
||||||
|
|
||||||
|
} // namespace Info::GlobalMedia
|
|
@ -644,7 +644,7 @@ infoMediaHeaderPosition: point(14px, 6px);
|
||||||
infoMediaSkip: 2px;
|
infoMediaSkip: 2px;
|
||||||
infoMediaLeft: 3px;
|
infoMediaLeft: 3px;
|
||||||
infoMediaMargin: margins(0px, 6px, 0px, 2px);
|
infoMediaMargin: margins(0px, 6px, 0px, 2px);
|
||||||
infoMediaMinGridSize: 90px;
|
infoMediaMinGridSize: 82px;
|
||||||
|
|
||||||
infoCommonGroupsMargin: margins(0px, 2px, 0px, 2px);
|
infoCommonGroupsMargin: margins(0px, 2px, 0px, 2px);
|
||||||
infoCommonGroupsListItem: PeerListItem(defaultPeerListItem) {
|
infoCommonGroupsListItem: PeerListItem(defaultPeerListItem) {
|
||||||
|
@ -1163,3 +1163,9 @@ infoHoursOuter: RoundButton(defaultActiveButton) {
|
||||||
}
|
}
|
||||||
infoHoursOuterMargin: margins(8px, 4px, 8px, 4px);
|
infoHoursOuterMargin: margins(8px, 4px, 8px, 4px);
|
||||||
infoHoursDaySkip: 6px;
|
infoHoursDaySkip: 6px;
|
||||||
|
|
||||||
|
infoSharedMediaScroll: ScrollArea(defaultScrollArea) {
|
||||||
|
round: 1px;
|
||||||
|
width: 5px;
|
||||||
|
deltax: 2px;
|
||||||
|
}
|
||||||
|
|
|
@ -42,7 +42,11 @@ ContentWidget::ContentWidget(
|
||||||
not_null<Controller*> controller)
|
not_null<Controller*> controller)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _controller(controller)
|
, _controller(controller)
|
||||||
, _scroll(this) {
|
, _scroll(
|
||||||
|
this,
|
||||||
|
(_controller->wrap() == Wrap::Search
|
||||||
|
? st::infoSharedMediaScroll
|
||||||
|
: st::defaultScrollArea)) {
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
|
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
@ -394,6 +398,8 @@ Key ContentMemento::key() const {
|
||||||
return BotStarRef::Tag(starref, starrefType());
|
return BotStarRef::Tag(starref, starrefType());
|
||||||
} else if (const auto who = reactionsWhoReadIds()) {
|
} else if (const auto who = reactionsWhoReadIds()) {
|
||||||
return Key(who, _reactionsSelected, _pollReactionsContextId);
|
return Key(who, _reactionsSelected, _pollReactionsContextId);
|
||||||
|
} else if (const auto another = globalMediaSelf()) {
|
||||||
|
return GlobalMedia::Tag{ another };
|
||||||
} else {
|
} else {
|
||||||
return Downloads::Tag();
|
return Downloads::Tag();
|
||||||
}
|
}
|
||||||
|
@ -439,6 +445,10 @@ ContentMemento::ContentMemento(BotStarRef::Tag starref)
|
||||||
, _starrefType(starref.type) {
|
, _starrefType(starref.type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentMemento::ContentMemento(GlobalMedia::Tag global)
|
||||||
|
: _globalMediaSelf(global.self) {
|
||||||
|
}
|
||||||
|
|
||||||
ContentMemento::ContentMemento(
|
ContentMemento::ContentMemento(
|
||||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||||
FullMsgId contextId,
|
FullMsgId contextId,
|
||||||
|
|
|
@ -57,6 +57,10 @@ enum class Type : uchar;
|
||||||
struct Tag;
|
struct Tag;
|
||||||
} // namespace Info::BotStarRef
|
} // namespace Info::BotStarRef
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
struct Tag;
|
||||||
|
} // namespace Info::GlobalMedia
|
||||||
|
|
||||||
namespace Info {
|
namespace Info {
|
||||||
|
|
||||||
class ContentMemento;
|
class ContentMemento;
|
||||||
|
@ -198,6 +202,7 @@ public:
|
||||||
explicit ContentMemento(Stories::Tag stories);
|
explicit ContentMemento(Stories::Tag stories);
|
||||||
explicit ContentMemento(Statistics::Tag statistics);
|
explicit ContentMemento(Statistics::Tag statistics);
|
||||||
explicit ContentMemento(BotStarRef::Tag starref);
|
explicit ContentMemento(BotStarRef::Tag starref);
|
||||||
|
explicit ContentMemento(GlobalMedia::Tag global);
|
||||||
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
ContentMemento(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
: _poll(poll)
|
: _poll(poll)
|
||||||
, _pollReactionsContextId(contextId) {
|
, _pollReactionsContextId(contextId) {
|
||||||
|
@ -254,6 +259,9 @@ public:
|
||||||
FullMsgId reactionsContextId() const {
|
FullMsgId reactionsContextId() const {
|
||||||
return _reactionsWhoReadIds ? _pollReactionsContextId : FullMsgId();
|
return _reactionsWhoReadIds ? _pollReactionsContextId : FullMsgId();
|
||||||
}
|
}
|
||||||
|
UserData *globalMediaSelf() const {
|
||||||
|
return _globalMediaSelf;
|
||||||
|
}
|
||||||
Key key() const;
|
Key key() const;
|
||||||
|
|
||||||
virtual Section section() const = 0;
|
virtual Section section() const = 0;
|
||||||
|
@ -299,6 +307,7 @@ private:
|
||||||
std::shared_ptr<Api::WhoReadList> _reactionsWhoReadIds;
|
std::shared_ptr<Api::WhoReadList> _reactionsWhoReadIds;
|
||||||
Data::ReactionId _reactionsSelected;
|
Data::ReactionId _reactionsSelected;
|
||||||
const FullMsgId _pollReactionsContextId;
|
const FullMsgId _pollReactionsContextId;
|
||||||
|
UserData * const _globalMediaSelf = nullptr;
|
||||||
|
|
||||||
int _scrollTop = 0;
|
int _scrollTop = 0;
|
||||||
QString _searchFieldQuery;
|
QString _searchFieldQuery;
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_shared_media.h"
|
#include "data/data_shared_media.h"
|
||||||
#include "info/info_content_widget.h"
|
#include "info/info_content_widget.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
|
#include "info/global_media/info_global_media_widget.h"
|
||||||
#include "info/media/info_media_widget.h"
|
#include "info/media/info_media_widget.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
@ -49,6 +50,9 @@ Key::Key(Statistics::Tag statistics) : _value(statistics) {
|
||||||
Key::Key(BotStarRef::Tag starref) : _value(starref) {
|
Key::Key(BotStarRef::Tag starref) : _value(starref) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Key::Key(GlobalMedia::Tag global) : _value(global) {
|
||||||
|
}
|
||||||
|
|
||||||
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
Key::Key(not_null<PollData*> poll, FullMsgId contextId)
|
||||||
: _value(PollKey{ poll, contextId }) {
|
: _value(PollKey{ poll, contextId }) {
|
||||||
}
|
}
|
||||||
|
@ -88,6 +92,10 @@ bool Key::isDownloads() const {
|
||||||
return v::is<Downloads::Tag>(_value);
|
return v::is<Downloads::Tag>(_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Key::isGlobalMedia() const {
|
||||||
|
return v::is<GlobalMedia::Tag>(_value);
|
||||||
|
}
|
||||||
|
|
||||||
PeerData *Key::storiesPeer() const {
|
PeerData *Key::storiesPeer() const {
|
||||||
if (const auto tag = std::get_if<Stories::Tag>(&_value)) {
|
if (const auto tag = std::get_if<Stories::Tag>(&_value)) {
|
||||||
return tag->peer;
|
return tag->peer;
|
||||||
|
@ -350,7 +358,8 @@ void Controller::updateSearchControllers(
|
||||||
not_null<ContentMemento*> memento) {
|
not_null<ContentMemento*> memento) {
|
||||||
using Type = Section::Type;
|
using Type = Section::Type;
|
||||||
const auto type = _section.type();
|
const auto type = _section.type();
|
||||||
const auto isMedia = (type == Type::Media);
|
const auto isMedia = (type == Type::Media)
|
||||||
|
|| (type == Type::GlobalMedia);
|
||||||
const auto mediaType = isMedia
|
const auto mediaType = isMedia
|
||||||
? _section.mediaType()
|
? _section.mediaType()
|
||||||
: Section::MediaType::kCount;
|
: Section::MediaType::kCount;
|
||||||
|
@ -362,13 +371,12 @@ void Controller::updateSearchControllers(
|
||||||
const auto hasMembersSearch = (type == Type::Members)
|
const auto hasMembersSearch = (type == Type::Members)
|
||||||
|| (type == Type::Profile);
|
|| (type == Type::Profile);
|
||||||
const auto searchQuery = memento->searchFieldQuery();
|
const auto searchQuery = memento->searchFieldQuery();
|
||||||
if (isMedia) {
|
if (type == Type::Media) {
|
||||||
_searchController
|
_searchController
|
||||||
= std::make_unique<Api::DelayedSearchController>(&session());
|
= std::make_unique<Api::DelayedSearchController>(&session());
|
||||||
auto mediaMemento = dynamic_cast<Media::Memento*>(memento.get());
|
auto mediaMemento = dynamic_cast<Media::Memento*>(memento.get());
|
||||||
Assert(mediaMemento != nullptr);
|
Assert(mediaMemento != nullptr);
|
||||||
_searchController->restoreState(
|
_searchController->restoreState(mediaMemento->searchState());
|
||||||
mediaMemento->searchState());
|
|
||||||
} else {
|
} else {
|
||||||
_searchController = nullptr;
|
_searchController = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -449,7 +457,8 @@ rpl::producer<QString> Controller::mediaSourceQueryValue() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<QString> Controller::searchQueryValue() const {
|
rpl::producer<QString> Controller::searchQueryValue() const {
|
||||||
return searchFieldController()->queryValue();
|
const auto controller = searchFieldController();
|
||||||
|
return controller ? controller->queryValue() : rpl::single(QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(
|
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(
|
||||||
|
|
|
@ -42,6 +42,17 @@ struct Tag {
|
||||||
|
|
||||||
} // namespace Info::Downloads
|
} // namespace Info::Downloads
|
||||||
|
|
||||||
|
namespace Info::GlobalMedia {
|
||||||
|
|
||||||
|
struct Tag {
|
||||||
|
explicit Tag(not_null<UserData*> self) : self(self) {
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<UserData*> self;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Info::GlobalMedia
|
||||||
|
|
||||||
namespace Info::Stories {
|
namespace Info::Stories {
|
||||||
|
|
||||||
enum class Tab {
|
enum class Tab {
|
||||||
|
@ -88,6 +99,7 @@ public:
|
||||||
Key(Stories::Tag stories);
|
Key(Stories::Tag stories);
|
||||||
Key(Statistics::Tag statistics);
|
Key(Statistics::Tag statistics);
|
||||||
Key(BotStarRef::Tag starref);
|
Key(BotStarRef::Tag starref);
|
||||||
|
Key(GlobalMedia::Tag global);
|
||||||
Key(not_null<PollData*> poll, FullMsgId contextId);
|
Key(not_null<PollData*> poll, FullMsgId contextId);
|
||||||
Key(
|
Key(
|
||||||
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
std::shared_ptr<Api::WhoReadList> whoReadIds,
|
||||||
|
@ -98,6 +110,7 @@ public:
|
||||||
Data::ForumTopic *topic() const;
|
Data::ForumTopic *topic() const;
|
||||||
UserData *settingsSelf() const;
|
UserData *settingsSelf() const;
|
||||||
bool isDownloads() const;
|
bool isDownloads() const;
|
||||||
|
bool isGlobalMedia() const;
|
||||||
PeerData *storiesPeer() const;
|
PeerData *storiesPeer() const;
|
||||||
Stories::Tab storiesTab() const;
|
Stories::Tab storiesTab() const;
|
||||||
Statistics::Tag statisticsTag() const;
|
Statistics::Tag statisticsTag() const;
|
||||||
|
@ -127,6 +140,7 @@ private:
|
||||||
Stories::Tag,
|
Stories::Tag,
|
||||||
Statistics::Tag,
|
Statistics::Tag,
|
||||||
BotStarRef::Tag,
|
BotStarRef::Tag,
|
||||||
|
GlobalMedia::Tag,
|
||||||
PollKey,
|
PollKey,
|
||||||
ReactionsKey> _value;
|
ReactionsKey> _value;
|
||||||
|
|
||||||
|
@ -142,6 +156,7 @@ public:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
Profile,
|
Profile,
|
||||||
Media,
|
Media,
|
||||||
|
GlobalMedia,
|
||||||
CommonGroups,
|
CommonGroups,
|
||||||
SimilarChannels,
|
SimilarChannels,
|
||||||
RequestsList,
|
RequestsList,
|
||||||
|
@ -163,10 +178,12 @@ public:
|
||||||
using MediaType = Storage::SharedMediaType;
|
using MediaType = Storage::SharedMediaType;
|
||||||
|
|
||||||
Section(Type type) : _type(type) {
|
Section(Type type) : _type(type) {
|
||||||
Expects(type != Type::Media && type != Type::Settings);
|
Expects(type != Type::Media
|
||||||
|
&& type != Type::GlobalMedia
|
||||||
|
&& type != Type::Settings);
|
||||||
}
|
}
|
||||||
Section(MediaType mediaType)
|
Section(MediaType mediaType, Type type = Type::Media)
|
||||||
: _type(Type::Media)
|
: _type(type)
|
||||||
, _mediaType(mediaType) {
|
, _mediaType(mediaType) {
|
||||||
}
|
}
|
||||||
Section(SettingsType settingsType)
|
Section(SettingsType settingsType)
|
||||||
|
@ -174,15 +191,15 @@ public:
|
||||||
, _settingsType(settingsType) {
|
, _settingsType(settingsType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Type type() const {
|
[[nodiscard]] Type type() const {
|
||||||
return _type;
|
return _type;
|
||||||
}
|
}
|
||||||
MediaType mediaType() const {
|
[[nodiscard]] MediaType mediaType() const {
|
||||||
Expects(_type == Type::Media);
|
Expects(_type == Type::Media || _type == Type::GlobalMedia);
|
||||||
|
|
||||||
return _mediaType;
|
return _mediaType;
|
||||||
}
|
}
|
||||||
SettingsType settingsType() const {
|
[[nodiscard]] SettingsType settingsType() const {
|
||||||
Expects(_type == Type::Settings);
|
Expects(_type == Type::Settings);
|
||||||
|
|
||||||
return _settingsType;
|
return _settingsType;
|
||||||
|
@ -214,6 +231,9 @@ public:
|
||||||
[[nodiscard]] bool isDownloads() const {
|
[[nodiscard]] bool isDownloads() const {
|
||||||
return key().isDownloads();
|
return key().isDownloads();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool isGlobalMedia() const {
|
||||||
|
return key().isGlobalMedia();
|
||||||
|
}
|
||||||
[[nodiscard]] PeerData *storiesPeer() const {
|
[[nodiscard]] PeerData *storiesPeer() const {
|
||||||
return key().storiesPeer();
|
return key().storiesPeer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
|
|
||||||
|
#include "info/global_media/info_global_media_widget.h"
|
||||||
#include "info/profile/info_profile_widget.h"
|
#include "info/profile/info_profile_widget.h"
|
||||||
#include "info/media/info_media_widget.h"
|
#include "info/media/info_media_widget.h"
|
||||||
#include "info/members/info_members_widget.h"
|
#include "info/members/info_members_widget.h"
|
||||||
|
@ -165,6 +166,10 @@ std::shared_ptr<ContentMemento> Memento::DefaultContent(
|
||||||
peer,
|
peer,
|
||||||
migratedPeerId,
|
migratedPeerId,
|
||||||
section.mediaType());
|
section.mediaType());
|
||||||
|
case Section::Type::GlobalMedia:
|
||||||
|
return std::make_shared<GlobalMedia::Memento>(
|
||||||
|
peer->asUser(),
|
||||||
|
section.mediaType());
|
||||||
case Section::Type::CommonGroups:
|
case Section::Type::CommonGroups:
|
||||||
return std::make_shared<CommonGroups::Memento>(peer->asUser());
|
return std::make_shared<CommonGroups::Memento>(peer->asUser());
|
||||||
case Section::Type::SimilarChannels:
|
case Section::Type::SimilarChannels:
|
||||||
|
|
|
@ -218,7 +218,8 @@ void WrapWidget::injectActivePeerProfile(not_null<PeerData*> peer) {
|
||||||
: _controller->section().type();
|
: _controller->section().type();
|
||||||
const auto firstSectionMediaType = [&] {
|
const auto firstSectionMediaType = [&] {
|
||||||
if (firstSectionType == Section::Type::Profile
|
if (firstSectionType == Section::Type::Profile
|
||||||
|| firstSectionType == Section::Type::SavedSublists) {
|
|| firstSectionType == Section::Type::SavedSublists
|
||||||
|
|| firstSectionType == Section::Type::Downloads) {
|
||||||
return Section::MediaType::kCount;
|
return Section::MediaType::kCount;
|
||||||
}
|
}
|
||||||
return hasStackHistory()
|
return hasStackHistory()
|
||||||
|
@ -309,7 +310,7 @@ void WrapWidget::forceContentRepaint() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WrapWidget::setupTop() {
|
void WrapWidget::setupTop() {
|
||||||
if (HasCustomTopBar(_controller.get())) {
|
if (HasCustomTopBar(_controller.get()) || wrap() == Wrap::Search) {
|
||||||
_topBar.destroy();
|
_topBar.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ enum class Wrap {
|
||||||
Layer,
|
Layer,
|
||||||
Narrow,
|
Narrow,
|
||||||
Side,
|
Side,
|
||||||
|
Search,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SelectedItem {
|
struct SelectedItem {
|
||||||
|
|
|
@ -8,6 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/media/info_media_common.h"
|
#include "info/media/info_media_common.h"
|
||||||
|
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "storage/storage_shared_media.h"
|
||||||
|
#include "styles/style_info.h"
|
||||||
|
#include "styles/style_overview.h"
|
||||||
|
|
||||||
namespace Info::Media {
|
namespace Info::Media {
|
||||||
|
|
||||||
|
@ -48,4 +51,40 @@ bool ChangeItemSelection(
|
||||||
return changeExisting(selected.find(item));
|
return changeExisting(selected.find(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MinItemHeight(Type type, int width) {
|
||||||
|
auto &songSt = st::overviewFileLayout;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Type::Photo:
|
||||||
|
case Type::GIF:
|
||||||
|
case Type::Video:
|
||||||
|
case Type::RoundFile: {
|
||||||
|
auto itemsLeft = st::infoMediaSkip;
|
||||||
|
auto itemsInRow = (width - itemsLeft)
|
||||||
|
/ (st::infoMediaMinGridSize + st::infoMediaSkip);
|
||||||
|
return (st::infoMediaMinGridSize + st::infoMediaSkip) / itemsInRow;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Type::RoundVoiceFile:
|
||||||
|
return songSt.songPadding.top()
|
||||||
|
+ songSt.songThumbSize
|
||||||
|
+ songSt.songPadding.bottom()
|
||||||
|
+ st::lineWidth;
|
||||||
|
case Type::File:
|
||||||
|
return songSt.filePadding.top()
|
||||||
|
+ songSt.fileThumbSize
|
||||||
|
+ songSt.filePadding.bottom()
|
||||||
|
+ st::lineWidth;
|
||||||
|
case Type::MusicFile:
|
||||||
|
return songSt.songPadding.top()
|
||||||
|
+ songSt.songThumbSize
|
||||||
|
+ songSt.songPadding.bottom();
|
||||||
|
case Type::Link:
|
||||||
|
return st::linksPhotoSize
|
||||||
|
+ st::linksMargin.top()
|
||||||
|
+ st::linksMargin.bottom()
|
||||||
|
+ st::linksBorder;
|
||||||
|
}
|
||||||
|
Unexpected("Type in MinItemHeight()");
|
||||||
|
}
|
||||||
} // namespace Info::Media
|
} // namespace Info::Media
|
||||||
|
|
|
@ -172,4 +172,6 @@ public:
|
||||||
virtual ~ListProvider() = default;
|
virtual ~ListProvider() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] int MinItemHeight(Type type, int width);
|
||||||
|
|
||||||
} // namespace Info::Media
|
} // namespace Info::Media
|
||||||
|
|
|
@ -137,9 +137,7 @@ bool InnerWidget::showInternal(not_null<Memento*> memento) {
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<ListWidget> InnerWidget::setupList() {
|
object_ptr<ListWidget> InnerWidget::setupList() {
|
||||||
auto result = object_ptr<ListWidget>(
|
auto result = object_ptr<ListWidget>(this, _controller);
|
||||||
this,
|
|
||||||
_controller);
|
|
||||||
result->heightValue(
|
result->heightValue(
|
||||||
) | rpl::start_with_next(
|
) | rpl::start_with_next(
|
||||||
[this] { refreshHeight(); },
|
[this] { refreshHeight(); },
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "info/media/info_media_list_widget.h"
|
#include "info/media/info_media_list_widget.h"
|
||||||
|
|
||||||
|
#include "info/global_media/info_global_media_provider.h"
|
||||||
#include "info/media/info_media_common.h"
|
#include "info/media/info_media_common.h"
|
||||||
#include "info/media/info_media_provider.h"
|
#include "info/media/info_media_provider.h"
|
||||||
#include "info/media/info_media_list_section.h"
|
#include "info/media/info_media_list_section.h"
|
||||||
|
@ -95,6 +96,8 @@ struct ListWidget::DateBadge {
|
||||||
return std::make_unique<Downloads::Provider>(controller);
|
return std::make_unique<Downloads::Provider>(controller);
|
||||||
} else if (controller->storiesPeer()) {
|
} else if (controller->storiesPeer()) {
|
||||||
return std::make_unique<Stories::Provider>(controller);
|
return std::make_unique<Stories::Provider>(controller);
|
||||||
|
} else if (controller->section().type() == Section::Type::GlobalMedia) {
|
||||||
|
return std::make_unique<GlobalMedia::Provider>(controller);
|
||||||
}
|
}
|
||||||
return std::make_unique<Provider>(controller);
|
return std::make_unique<Provider>(controller);
|
||||||
}
|
}
|
||||||
|
@ -185,7 +188,9 @@ void ListWidget::start() {
|
||||||
} else {
|
} else {
|
||||||
trackSession(&session());
|
trackSession(&session());
|
||||||
|
|
||||||
_controller->mediaSourceQueryValue(
|
(_controller->key().isGlobalMedia()
|
||||||
|
? _controller->searchQueryValue()
|
||||||
|
: _controller->mediaSourceQueryValue()
|
||||||
) | rpl::start_with_next([this] {
|
) | rpl::start_with_next([this] {
|
||||||
restart();
|
restart();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
@ -614,7 +619,7 @@ auto ListWidget::findItemByItem(const HistoryItem *item)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ListWidget::findItemDetails(not_null<BaseLayout*> item)
|
auto ListWidget::findItemDetails(not_null<BaseLayout*> item)
|
||||||
-> FoundItem {
|
-> FoundItem {
|
||||||
const auto sectionIt = findSectionByItem(item->getItem());
|
const auto sectionIt = findSectionByItem(item->getItem());
|
||||||
Assert(sectionIt != _sections.end());
|
Assert(sectionIt != _sections.end());
|
||||||
return foundItemInSection(sectionIt->findItemDetails(item), *sectionIt);
|
return foundItemInSection(sectionIt->findItemDetails(item), *sectionIt);
|
||||||
|
@ -2066,7 +2071,7 @@ std::vector<ListSection>::iterator ListWidget::findSectionByItem(
|
||||||
if (_sections.size() < 2) {
|
if (_sections.size() < 2) {
|
||||||
return _sections.begin();
|
return _sections.begin();
|
||||||
}
|
}
|
||||||
Assert(!_controller->isDownloads());
|
Assert(!_controller->isDownloads() && !_controller->isGlobalMedia());
|
||||||
return ranges::lower_bound(
|
return ranges::lower_bound(
|
||||||
_sections,
|
_sections,
|
||||||
GetUniversalId(item),
|
GetUniversalId(item),
|
||||||
|
|
|
@ -33,32 +33,6 @@ constexpr auto kPreloadedScreensCount = 4;
|
||||||
constexpr auto kPreloadedScreensCountFull
|
constexpr auto kPreloadedScreensCountFull
|
||||||
= kPreloadedScreensCount + 1 + kPreloadedScreensCount;
|
= kPreloadedScreensCount + 1 + kPreloadedScreensCount;
|
||||||
|
|
||||||
[[nodiscard]] int MinItemHeight(Type type, int width) {
|
|
||||||
auto &songSt = st::overviewFileLayout;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case Type::Photo:
|
|
||||||
case Type::GIF:
|
|
||||||
case Type::Video:
|
|
||||||
case Type::RoundFile: {
|
|
||||||
auto itemsLeft = st::infoMediaSkip;
|
|
||||||
auto itemsInRow = (width - itemsLeft)
|
|
||||||
/ (st::infoMediaMinGridSize + st::infoMediaSkip);
|
|
||||||
return (st::infoMediaMinGridSize + st::infoMediaSkip) / itemsInRow;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case Type::RoundVoiceFile:
|
|
||||||
return songSt.songPadding.top() + songSt.songThumbSize + songSt.songPadding.bottom() + st::lineWidth;
|
|
||||||
case Type::File:
|
|
||||||
return songSt.filePadding.top() + songSt.fileThumbSize + songSt.filePadding.bottom() + st::lineWidth;
|
|
||||||
case Type::MusicFile:
|
|
||||||
return songSt.songPadding.top() + songSt.songThumbSize + songSt.songPadding.bottom();
|
|
||||||
case Type::Link:
|
|
||||||
return st::linksPhotoSize + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder;
|
|
||||||
}
|
|
||||||
Unexpected("Type in MinItemHeight()");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Provider::Provider(not_null<AbstractController*> controller)
|
Provider::Provider(not_null<AbstractController*> controller)
|
||||||
|
|
|
@ -95,9 +95,7 @@ object_ptr<ContentWidget> Memento::createWidget(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget::Widget(
|
Widget::Widget(QWidget *parent, not_null<Controller*> controller)
|
||||||
QWidget *parent,
|
|
||||||
not_null<Controller*> controller)
|
|
||||||
: ContentWidget(parent, controller) {
|
: ContentWidget(parent, controller) {
|
||||||
_inner = setInnerWidget(object_ptr<InnerWidget>(
|
_inner = setInnerWidget(object_ptr<InnerWidget>(
|
||||||
this,
|
this,
|
||||||
|
|
Loading…
Add table
Reference in a new issue