mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 10:11:41 -05:00
Added search to files and links shared media.
This commit is contained in:
parent
a27edcad1c
commit
eb2719fad1
25 changed files with 1110 additions and 220 deletions
|
@ -41,6 +41,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "storage/storage_shared_media.h"
|
||||
#include "storage/storage_user_photos.h"
|
||||
#include "history/history_sparse_ids.h"
|
||||
#include "history/history_search_controller.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -1939,71 +1940,22 @@ void ApiWrap::requestSharedMedia(
|
|||
return;
|
||||
}
|
||||
|
||||
auto filter = [&] {
|
||||
using Type = SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
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();
|
||||
}
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
if (filter.type() == mtpc_inputMessagesFilterEmpty) {
|
||||
auto prepared = Api::PrepareSearchRequest(
|
||||
peer,
|
||||
type,
|
||||
QString(),
|
||||
messageId,
|
||||
slice);
|
||||
if (prepared.vfilter.type() == mtpc_inputMessagesFilterEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto minId = 0;
|
||||
auto maxId = 0;
|
||||
auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
auto offsetId = [&] {
|
||||
switch (slice) {
|
||||
case SliceType::Before:
|
||||
case SliceType::Around: return messageId;
|
||||
case SliceType::After: return messageId + 1;
|
||||
}
|
||||
Unexpected("Slice type in ApiWrap::requestSharedMedia");
|
||||
}();
|
||||
auto addOffset = [&] {
|
||||
switch (slice) {
|
||||
case SliceType::Before: return 0;
|
||||
case SliceType::Around: return -limit / 2;
|
||||
case SliceType::After: return -limit;
|
||||
}
|
||||
Unexpected("Slice type in ApiWrap::requestSharedMedia");
|
||||
}();
|
||||
|
||||
auto requestId = request(MTPmessages_Search(
|
||||
MTP_flags(0),
|
||||
peer->input,
|
||||
MTPstring(),
|
||||
MTP_inputUserEmpty(),
|
||||
filter,
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId)
|
||||
)).done([this, peer, type, messageId, slice](const MTPmessages_Messages &result) {
|
||||
_sharedMediaRequests.remove(std::make_tuple(peer, type, messageId, slice));
|
||||
auto requestId = request(
|
||||
std::move(prepared)
|
||||
).done([this, peer, type, messageId, slice](
|
||||
const MTPmessages_Messages &result) {
|
||||
auto key = std::make_tuple(peer, type, messageId, slice);
|
||||
_sharedMediaRequests.remove(key);
|
||||
sharedMediaDone(peer, type, messageId, slice, result);
|
||||
}).fail([this, key](const RPCError &error) {
|
||||
_sharedMediaRequests.remove(key);
|
||||
|
@ -2017,74 +1969,18 @@ void ApiWrap::sharedMediaDone(
|
|||
MsgId messageId,
|
||||
SliceType slice,
|
||||
const MTPmessages_Messages &result) {
|
||||
auto fullCount = 0;
|
||||
auto &messages = *[&] {
|
||||
switch (result.type()) {
|
||||
case mtpc_messages_messages: {
|
||||
auto &d = result.c_messages_messages();
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
fullCount = d.vmessages.v.size();
|
||||
return &d.vmessages.v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesSlice: {
|
||||
auto &d = result.c_messages_messagesSlice();
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
fullCount = d.vcount.v;
|
||||
return &d.vmessages.v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = result.c_messages_channelMessages();
|
||||
if (auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts.v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when no channel was passed! (ApiWrap::sharedMediaDone)"));
|
||||
}
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
fullCount = d.vcount.v;
|
||||
return &d.vmessages.v;
|
||||
} break;
|
||||
}
|
||||
Unexpected("messages.Messages type in sharedMediaDone()");
|
||||
}();
|
||||
|
||||
auto noSkipRange = MsgRange { messageId, messageId };
|
||||
auto messageIds = std::vector<MsgId>();
|
||||
auto addType = NewMessageExisting;
|
||||
messageIds.reserve(messages.size());
|
||||
for (auto &message : messages) {
|
||||
if (auto item = App::histories().addNewMessage(message, addType)) {
|
||||
if (item->sharedMediaTypes().test(type)) {
|
||||
auto itemId = item->id;
|
||||
messageIds.push_back(itemId);
|
||||
accumulate_min(noSkipRange.from, itemId);
|
||||
accumulate_max(noSkipRange.till, itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messageId && messageIds.empty()) {
|
||||
noSkipRange = [&]() -> MsgRange {
|
||||
switch (slice) {
|
||||
case SliceType::Before: // All old loaded.
|
||||
return { 0, noSkipRange.till };
|
||||
case SliceType::Around: // All loaded.
|
||||
return { 0, ServerMaxMsgId };
|
||||
case SliceType::After: // All new loaded.
|
||||
return { noSkipRange.from, ServerMaxMsgId };
|
||||
}
|
||||
Unexpected("Slice type in ApiWrap::sharedMediaDone");
|
||||
}();
|
||||
}
|
||||
auto parsed = Api::ParseSearchResult(
|
||||
peer,
|
||||
type,
|
||||
messageId,
|
||||
slice,
|
||||
result);
|
||||
Auth().storage().add(Storage::SharedMediaAddSlice(
|
||||
peer->id,
|
||||
type,
|
||||
std::move(messageIds),
|
||||
noSkipRange,
|
||||
fullCount
|
||||
std::move(parsed.messageIds),
|
||||
parsed.noSkipRange,
|
||||
parsed.fullCount
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -603,8 +603,8 @@ namespace {
|
|||
}
|
||||
if (updatedFrom) {
|
||||
channel->mgInfo->migrateFromPtr = cdata;
|
||||
if (History *h = App::historyLoaded(cdata->id)) {
|
||||
if (History *hto = App::historyLoaded(channel->id)) {
|
||||
if (auto h = App::historyLoaded(cdata->id)) {
|
||||
if (auto hto = App::historyLoaded(channel->id)) {
|
||||
if (!h->isEmpty()) {
|
||||
h->clear(true);
|
||||
}
|
||||
|
|
|
@ -64,9 +64,6 @@ public:
|
|||
base::Observable<void> &savedGifsUpdated() {
|
||||
return _savedGifsUpdated;
|
||||
}
|
||||
base::Observable<not_null<History*>> &historyCleared() {
|
||||
return _historyCleared;
|
||||
}
|
||||
base::Observable<void> &pendingHistoryResize() {
|
||||
return _pendingHistoryResize;
|
||||
}
|
||||
|
@ -78,23 +75,35 @@ public:
|
|||
return _queryItemVisibility;
|
||||
}
|
||||
void markItemLayoutChanged(not_null<const HistoryItem*> item) {
|
||||
_itemLayoutChanged.fire(std::move(item));
|
||||
_itemLayoutChanged.fire_copy(item);
|
||||
}
|
||||
rpl::producer<not_null<const HistoryItem*>> itemLayoutChanged() const {
|
||||
return _itemLayoutChanged.events();
|
||||
}
|
||||
void requestItemRepaint(not_null<const HistoryItem*> item) {
|
||||
_itemRepaintRequest.fire(std::move(item));
|
||||
_itemRepaintRequest.fire_copy(item);
|
||||
}
|
||||
rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const {
|
||||
return _itemRepaintRequest.events();
|
||||
}
|
||||
void markItemRemoved(not_null<const HistoryItem*> item) {
|
||||
_itemRemoved.fire(std::move(item));
|
||||
_itemRemoved.fire_copy(item);
|
||||
}
|
||||
rpl::producer<not_null<const HistoryItem*>> itemRemoved() const {
|
||||
return _itemRemoved.events();
|
||||
}
|
||||
void markHistoryUnloaded(not_null<const History*> history) {
|
||||
_historyUnloaded.fire_copy(history);
|
||||
}
|
||||
rpl::producer<not_null<const History*>> historyUnloaded() const {
|
||||
return _historyUnloaded.events();
|
||||
}
|
||||
void markHistoryCleared(not_null<const History*> history) {
|
||||
_historyCleared.fire_copy(history);
|
||||
}
|
||||
rpl::producer<not_null<const History*>> historyCleared() const {
|
||||
return _historyCleared.events();
|
||||
}
|
||||
using MegagroupParticipant = std::tuple<
|
||||
not_null<ChannelData*>,
|
||||
not_null<UserData*>>;
|
||||
|
@ -241,12 +250,13 @@ private:
|
|||
base::Observable<void> _moreChatsLoaded;
|
||||
base::Observable<void> _stickersUpdated;
|
||||
base::Observable<void> _savedGifsUpdated;
|
||||
base::Observable<not_null<History*>> _historyCleared;
|
||||
base::Observable<void> _pendingHistoryResize;
|
||||
base::Observable<ItemVisibilityQuery> _queryItemVisibility;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanged;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
|
||||
rpl::event_stream<not_null<const History*>> _historyUnloaded;
|
||||
rpl::event_stream<not_null<const History*>> _historyCleared;
|
||||
rpl::event_stream<MegagroupParticipant> _megagroupParticipantRemoved;
|
||||
rpl::event_stream<MegagroupParticipant> _megagroupParticipantAdded;
|
||||
|
||||
|
|
|
@ -75,8 +75,11 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
bool has_value() const {
|
||||
return !is<none_type>();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return (get_if<none_type>(&_impl) == nullptr);
|
||||
return has_value();
|
||||
}
|
||||
bool operator==(const optional_variant &other) const {
|
||||
return _impl == other._impl;
|
||||
|
|
122
Telegram/SourceFiles/base/unique_qptr.h
Normal file
122
Telegram/SourceFiles/base/unique_qptr.h
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace base {
|
||||
|
||||
template <typename T>
|
||||
class unique_qptr {
|
||||
public:
|
||||
unique_qptr() = default;
|
||||
unique_qptr(std::nullptr_t) {
|
||||
}
|
||||
explicit unique_qptr(T *pointer)
|
||||
: _object(pointer) {
|
||||
}
|
||||
|
||||
unique_qptr(const unique_qptr &other) = delete;
|
||||
unique_qptr &operator=(const unique_qptr &other) = delete;
|
||||
unique_qptr(unique_qptr &&other)
|
||||
: _object(base::take(other._object)) {
|
||||
}
|
||||
unique_qptr &operator=(unique_qptr &&other) {
|
||||
if (_object != other._object) {
|
||||
destroy();
|
||||
_object = std::move(other._object);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <
|
||||
typename U,
|
||||
typename = std::enable_if_t<std::is_base_of_v<T, U>>>
|
||||
unique_qptr(unique_qptr<U> &&other)
|
||||
: _object(base::take(other._object)) {
|
||||
}
|
||||
|
||||
template <
|
||||
typename U,
|
||||
typename = std::enable_if_t<std::is_base_of_v<T, U>>>
|
||||
unique_qptr &operator=(unique_qptr<U> &&other) {
|
||||
if (_object != other._object) {
|
||||
destroy();
|
||||
_object = std::move(other._object);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
unique_qptr &operator=(std::nullptr_t) {
|
||||
destroy();
|
||||
_object = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset(T *value) {
|
||||
if (_object != value) {
|
||||
destroy();
|
||||
_object = value;
|
||||
}
|
||||
}
|
||||
|
||||
T *get() const {
|
||||
return static_cast<T*>(_object.data());
|
||||
}
|
||||
operator T*() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
T *release() {
|
||||
return static_cast<T*>(base::take(_object).data());
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return _object != nullptr;
|
||||
}
|
||||
|
||||
T *operator->() const {
|
||||
return get();
|
||||
}
|
||||
T &operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
delete base::take(_object).data();
|
||||
}
|
||||
|
||||
~unique_qptr() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename U>
|
||||
friend class unique_qptr;
|
||||
|
||||
QPointer<QObject> _object;
|
||||
|
||||
};
|
||||
|
||||
template <typename T, typename ...Args>
|
||||
inline unique_qptr<T> make_unique_q(Args &&...args) {
|
||||
return unique_qptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
} // namespace base
|
|
@ -2038,7 +2038,7 @@ void History::getReadyFor(MsgId msgId) {
|
|||
return;
|
||||
}
|
||||
if (msgId == ShowAtUnreadMsgId && peer->migrateFrom()) {
|
||||
if (History *h = App::historyLoaded(peer->migrateFrom()->id)) {
|
||||
if (auto h = App::historyLoaded(peer->migrateFrom()->id)) {
|
||||
if (h->unreadCount()) {
|
||||
clear(true);
|
||||
h->getReadyFor(msgId);
|
||||
|
@ -2217,7 +2217,9 @@ void History::clear(bool leaveItems) {
|
|||
if (scrollTopItem) {
|
||||
forgetScrollState();
|
||||
}
|
||||
if (!leaveItems) {
|
||||
if (leaveItems) {
|
||||
Auth().data().markHistoryUnloaded(this);
|
||||
} else {
|
||||
setLastMessage(nullptr);
|
||||
notifies.clear();
|
||||
auto &pending = Global::RefPendingRepaintItems();
|
||||
|
@ -2238,6 +2240,7 @@ void History::clear(bool leaveItems) {
|
|||
}
|
||||
}
|
||||
Auth().storage().remove(Storage::SharedMediaRemoveAll(peer->id));
|
||||
Auth().data().markHistoryCleared(this);
|
||||
}
|
||||
clearBlocks(leaveItems);
|
||||
if (leaveItems) {
|
||||
|
@ -2263,9 +2266,6 @@ void History::clear(bool leaveItems) {
|
|||
peer->asChannel()->mgInfo->markupSenders.clear();
|
||||
}
|
||||
}
|
||||
if (leaveItems) {
|
||||
Auth().data().historyCleared().notify(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
void History::clearBlocks(bool leaveItems) {
|
||||
|
|
|
@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#include "history/history_inner_widget.h"
|
||||
|
||||
#include <rpl/merge.h>
|
||||
#include "styles/style_history.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "history/history_message.h"
|
||||
|
@ -110,10 +111,6 @@ HistoryInner::HistoryInner(
|
|||
notifyIsBotChanged();
|
||||
|
||||
setMouseTracking(true);
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
lifetime());
|
||||
subscribe(_controller->gifPauseLevelChanged(), [this] {
|
||||
if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::Any)) {
|
||||
update();
|
||||
|
@ -122,11 +119,19 @@ HistoryInner::HistoryInner(
|
|||
subscribe(_controller->window()->dragFinished(), [this] {
|
||||
mouseActionUpdate(QCursor::pos());
|
||||
});
|
||||
subscribe(Auth().data().historyCleared(), [this](not_null<History*> history) {
|
||||
if (_history == history) {
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::start_with_next(
|
||||
[this](auto item) { itemRemoved(item); },
|
||||
lifetime());
|
||||
rpl::merge(
|
||||
Auth().data().historyUnloaded(),
|
||||
Auth().data().historyCleared())
|
||||
| rpl::filter([this](not_null<const History*> history) {
|
||||
return (_history == history);
|
||||
})
|
||||
| rpl::start_with_next([this] {
|
||||
mouseActionCancel();
|
||||
}
|
||||
});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void HistoryInner::messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages) {
|
||||
|
|
307
Telegram/SourceFiles/history/history_search_controller.cpp
Normal file
307
Telegram/SourceFiles/history/history_search_controller.cpp
Normal file
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "history/history_search_controller.h"
|
||||
|
||||
#include "auth_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSharedMediaLimit = 100;
|
||||
constexpr auto kDefaultSearchTimeoutMs = TimeMs(200);
|
||||
|
||||
} // namespace
|
||||
|
||||
MTPmessages_Search PrepareSearchRequest(
|
||||
not_null<PeerData*> peer,
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction) {
|
||||
auto filter = [&] {
|
||||
using Type = Storage::SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
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();
|
||||
}
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
|
||||
auto minId = 0;
|
||||
auto maxId = 0;
|
||||
auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
auto offsetId = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before:
|
||||
case SparseIdsLoadDirection::Around: return messageId;
|
||||
case SparseIdsLoadDirection::After: return messageId + 1;
|
||||
}
|
||||
Unexpected("Direction in PrepareSearchRequest");
|
||||
}();
|
||||
auto addOffset = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before: return 0;
|
||||
case SparseIdsLoadDirection::Around: return -limit / 2;
|
||||
case SparseIdsLoadDirection::After: return -limit;
|
||||
}
|
||||
Unexpected("Direction in PrepareSearchRequest");
|
||||
}();
|
||||
|
||||
return MTPmessages_Search(
|
||||
MTP_flags(0),
|
||||
peer->input,
|
||||
MTP_string(query),
|
||||
MTP_inputUserEmpty(),
|
||||
filter,
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId));
|
||||
}
|
||||
|
||||
SearchResult ParseSearchResult(
|
||||
not_null<PeerData*> peer,
|
||||
Storage::SharedMediaType type,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction,
|
||||
const MTPmessages_Messages &data) {
|
||||
auto result = SearchResult();
|
||||
auto &messages = *[&] {
|
||||
switch (data.type()) {
|
||||
case mtpc_messages_messages: {
|
||||
auto &d = data.c_messages_messages();
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
result.fullCount = d.vmessages.v.size();
|
||||
return &d.vmessages.v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesSlice: {
|
||||
auto &d = data.c_messages_messagesSlice();
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
result.fullCount = d.vcount.v;
|
||||
return &d.vmessages.v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = data.c_messages_channelMessages();
|
||||
if (auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts.v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when no channel was passed! (ParseSearchResult)"));
|
||||
}
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
result.fullCount = d.vcount.v;
|
||||
return &d.vmessages.v;
|
||||
} break;
|
||||
}
|
||||
Unexpected("messages.Messages type in ParseSearchResult()");
|
||||
}();
|
||||
|
||||
result.noSkipRange = MsgRange{ messageId, messageId };
|
||||
auto addType = NewMessageExisting;
|
||||
result.messageIds.reserve(messages.size());
|
||||
for (auto &message : messages) {
|
||||
if (auto item = App::histories().addNewMessage(message, addType)) {
|
||||
if ((type == Storage::SharedMediaType::kCount)
|
||||
|| item->sharedMediaTypes().test(type)) {
|
||||
auto itemId = item->id;
|
||||
result.messageIds.push_back(itemId);
|
||||
accumulate_min(result.noSkipRange.from, itemId);
|
||||
accumulate_max(result.noSkipRange.till, itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (messageId && result.messageIds.empty()) {
|
||||
result.noSkipRange = [&]() -> MsgRange {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before: // All old loaded.
|
||||
return { 0, result.noSkipRange.till };
|
||||
case SparseIdsLoadDirection::Around: // All loaded.
|
||||
return { 0, ServerMaxMsgId };
|
||||
case SparseIdsLoadDirection::After: // All new loaded.
|
||||
return { result.noSkipRange.from, ServerMaxMsgId };
|
||||
}
|
||||
Unexpected("Direction in ParseSearchResult");
|
||||
}();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
SingleSearchController::SingleSearchController(const Query &query)
|
||||
: _query(query)
|
||||
, _peerData(App::peer(query.peerId))
|
||||
, _migratedData(query.migratedPeerId
|
||||
? base::make_optional(Data(App::peer(query.migratedPeerId)))
|
||||
: base::none) {
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SingleSearchController::idsSlice(
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
auto createSimpleViewer = [this](
|
||||
PeerId peerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return simpleIdsSlice(
|
||||
peerId,
|
||||
simpleKey,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
};
|
||||
return SparseIdsMergedSlice::CreateViewer(
|
||||
SparseIdsMergedSlice::Key(
|
||||
_query.peerId,
|
||||
_query.migratedPeerId,
|
||||
aroundId),
|
||||
limitBefore,
|
||||
limitAfter,
|
||||
std::move(createSimpleViewer));
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsSlice> SingleSearchController::simpleIdsSlice(
|
||||
PeerId peerId,
|
||||
MsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(peerId != 0);
|
||||
Expects(IsServerMsgId(aroundId) || (aroundId == 0));
|
||||
Expects((aroundId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
Expects((_query.peerId == peerId)
|
||||
|| (_query.migratedPeerId == peerId));
|
||||
|
||||
auto listData = (peerId == _query.peerId)
|
||||
? &_peerData
|
||||
: &*_migratedData;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
auto builder = lifetime.make_state<SparseIdsSliceBuilder>(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
builder->insufficientAround()
|
||||
| rpl::start_with_next([=](
|
||||
const SparseIdsSliceBuilder::AroundData &data) {
|
||||
requestMore(data, listData);
|
||||
}, lifetime);
|
||||
|
||||
auto pushNextSnapshot = [=] {
|
||||
consumer.put_next(builder->snapshot());
|
||||
};
|
||||
|
||||
listData->list.sliceUpdated()
|
||||
| rpl::filter([=](const SliceUpdate &update) {
|
||||
return builder->applyUpdate(update);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
Auth().data().itemRemoved()
|
||||
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->history()->peer->id == peerId);
|
||||
})
|
||||
| rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return builder->removeOne(item->id);
|
||||
})
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
Auth().data().historyCleared()
|
||||
| rpl::filter([=](not_null<const History*> history) {
|
||||
return (history->peer->id == peerId);
|
||||
})
|
||||
| rpl::filter([=] { return builder->removeAll(); })
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
builder->checkInsufficient();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
void SingleSearchController::requestMore(
|
||||
const SparseIdsSliceBuilder::AroundData &key,
|
||||
Data *listData) {
|
||||
if (listData->requests.contains(key)) {
|
||||
return;
|
||||
}
|
||||
auto requestId = request(PrepareSearchRequest(
|
||||
listData->peer,
|
||||
_query.type,
|
||||
_query.query,
|
||||
key.aroundId,
|
||||
key.direction)
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
auto parsed = ParseSearchResult(
|
||||
listData->peer,
|
||||
_query.type,
|
||||
key.aroundId,
|
||||
key.direction,
|
||||
result);
|
||||
listData->list.addSlice(
|
||||
std::move(parsed.messageIds),
|
||||
parsed.noSkipRange,
|
||||
parsed.fullCount);
|
||||
}).send();
|
||||
|
||||
listData->requests.emplace(key, requestId);
|
||||
}
|
||||
|
||||
DelayedSearchController::DelayedSearchController() {
|
||||
_timer.setCallback([this] { setQueryFast(_nextQuery); });
|
||||
}
|
||||
|
||||
void DelayedSearchController::setQuery(const Query &query) {
|
||||
setQuery(
|
||||
query,
|
||||
query.query.isEmpty() ? 0 : kDefaultSearchTimeoutMs);
|
||||
}
|
||||
|
||||
void DelayedSearchController::setQueryFast(const Query &query) {
|
||||
_controller.setQuery(query);
|
||||
_sourceChanges.fire({});
|
||||
}
|
||||
|
||||
} // namespace Api
|
166
Telegram/SourceFiles/history/history_search_controller.h
Normal file
166
Telegram/SourceFiles/history/history_search_controller.h
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "history/history_sparse_ids.h"
|
||||
#include "storage/storage_sparse_ids_list.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct SearchResult {
|
||||
std::vector<MsgId> messageIds;
|
||||
MsgRange noSkipRange;
|
||||
int fullCount = 0;
|
||||
};
|
||||
|
||||
MTPmessages_Search PrepareSearchRequest(
|
||||
not_null<PeerData*> peer,
|
||||
Storage::SharedMediaType type,
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction);
|
||||
|
||||
SearchResult ParseSearchResult(
|
||||
not_null<PeerData*> peer,
|
||||
Storage::SharedMediaType type,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction,
|
||||
const MTPmessages_Messages &data);
|
||||
|
||||
class SingleSearchController : private MTP::Sender {
|
||||
public:
|
||||
struct Query {
|
||||
using MediaType = Storage::SharedMediaType;
|
||||
|
||||
PeerId peerId = 0;
|
||||
PeerId migratedPeerId = 0;
|
||||
MediaType type = MediaType::kCount;
|
||||
QString query;
|
||||
// from_id, min_date, max_date
|
||||
};
|
||||
|
||||
SingleSearchController(const Query &query);
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> idsSlice(
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
Query query() const {
|
||||
return _query;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Data {
|
||||
explicit Data(not_null<PeerData*> peer) : peer(peer) {
|
||||
}
|
||||
|
||||
not_null<PeerData*> peer;
|
||||
Storage::SparseIdsList list;
|
||||
base::flat_map<
|
||||
SparseIdsSliceBuilder::AroundData,
|
||||
mtpRequestId> requests;
|
||||
};
|
||||
using SliceUpdate = Storage::SparseIdsSliceUpdate;
|
||||
|
||||
rpl::producer<SparseIdsSlice> simpleIdsSlice(
|
||||
PeerId peerId,
|
||||
MsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
void requestMore(
|
||||
const SparseIdsSliceBuilder::AroundData &key,
|
||||
Data *listData);
|
||||
|
||||
Query _query;
|
||||
Data _peerData;
|
||||
base::optional<Data> _migratedData;
|
||||
|
||||
};
|
||||
|
||||
class SearchController {
|
||||
public:
|
||||
using Query = SingleSearchController::Query;
|
||||
void setQuery(const Query &query) {
|
||||
_controller = SingleSearchController(query);
|
||||
}
|
||||
|
||||
Query query() const {
|
||||
return _controller ? _controller->query() : Query();
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> idsSlice(
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(_controller.has_value());
|
||||
return _controller->idsSlice(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
}
|
||||
|
||||
private:
|
||||
base::optional<SingleSearchController> _controller;
|
||||
|
||||
};
|
||||
|
||||
class DelayedSearchController {
|
||||
public:
|
||||
DelayedSearchController();
|
||||
|
||||
using Query = SingleSearchController::Query;
|
||||
void setQuery(const Query &query);
|
||||
void setQuery(const Query &query, TimeMs delay) {
|
||||
_nextQuery = query;
|
||||
_timer.callOnce(delay);
|
||||
}
|
||||
void setQueryFast(const Query &query);
|
||||
|
||||
Query currentQuery() const {
|
||||
return _controller.query();
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> idsSlice(
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return _controller.idsSlice(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
}
|
||||
|
||||
rpl::producer<> sourceChanged() const {
|
||||
return _sourceChanges.events();
|
||||
}
|
||||
|
||||
private:
|
||||
SearchController _controller;
|
||||
Query _nextQuery;
|
||||
base::Timer _timer;
|
||||
rpl::event_stream<> _sourceChanges;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
|
@ -48,7 +48,7 @@ inline MediaOverviewType SharedMediaTypeToOverview(Type type) {
|
|||
} // namespace
|
||||
|
||||
base::optional<Storage::SharedMediaType> SharedMediaOverviewType(
|
||||
Storage::SharedMediaType type) {
|
||||
Storage::SharedMediaType type) {
|
||||
if (SharedMediaTypeToOverview(type) != OverviewCount) {
|
||||
return type;
|
||||
}
|
||||
|
@ -56,13 +56,22 @@ base::optional<Storage::SharedMediaType> SharedMediaOverviewType(
|
|||
}
|
||||
|
||||
void SharedMediaShowOverview(
|
||||
Storage::SharedMediaType type,
|
||||
not_null<History*> history) {
|
||||
Storage::SharedMediaType type,
|
||||
not_null<History*> history) {
|
||||
if (SharedMediaOverviewType(type)) {
|
||||
Ui::showPeerOverview(history, SharedMediaTypeToOverview(type));
|
||||
}
|
||||
}
|
||||
|
||||
bool SharedMediaAllowSearch(Storage::SharedMediaType type) {
|
||||
switch (type) {
|
||||
case Type::MusicFile:
|
||||
case Type::File:
|
||||
case Type::Link: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
Storage::SharedMediaKey key,
|
||||
int limitBefore,
|
||||
|
@ -83,8 +92,8 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
|||
Auth().api().requestSharedMedia(
|
||||
peer,
|
||||
type,
|
||||
data.first,
|
||||
data.second);
|
||||
data.aroundId,
|
||||
data.direction);
|
||||
};
|
||||
builder->insufficientAround()
|
||||
| rpl::start_with_next(requestMediaAround, lifetime);
|
||||
|
@ -120,9 +129,7 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
|||
| rpl::filter([=](const AllRemoved &update) {
|
||||
return (update.peerId == key.peerId);
|
||||
})
|
||||
| rpl::filter([=](const AllRemoved &update) {
|
||||
return builder->removeAll();
|
||||
})
|
||||
| rpl::filter([=] { return builder->removeAll(); })
|
||||
| rpl::start_with_next(pushNextSnapshot, lifetime);
|
||||
|
||||
using Result = Storage::SharedMediaResult;
|
||||
|
@ -147,52 +154,25 @@ rpl::producer<SparseIdsMergedSlice> SharedMediaMergedViewer(
|
|||
SharedMediaMergedKey key,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
Expects(IsServerMsgId(key.mergedKey.universalId)
|
||||
|| (key.mergedKey.universalId == 0)
|
||||
|| (IsServerMsgId(ServerMaxMsgId + key.mergedKey.universalId) && key.mergedKey.migratedPeerId != 0));
|
||||
Expects((key.mergedKey.universalId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
|
||||
return [=](auto consumer) {
|
||||
if (!key.mergedKey.migratedPeerId) {
|
||||
return SharedMediaViewer(
|
||||
Storage::SharedMediaKey(
|
||||
key.mergedKey.peerId,
|
||||
key.type,
|
||||
SparseIdsMergedSlice::PartKey(key.mergedKey)),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
) | rpl::start_with_next([=](SparseIdsSlice &&part) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key.mergedKey,
|
||||
std::move(part),
|
||||
base::none));
|
||||
});
|
||||
}
|
||||
return rpl::combine(
|
||||
SharedMediaViewer(
|
||||
Storage::SharedMediaKey(
|
||||
key.mergedKey.peerId,
|
||||
key.type,
|
||||
SparseIdsMergedSlice::PartKey(key.mergedKey)),
|
||||
limitBefore,
|
||||
limitAfter),
|
||||
SharedMediaViewer(
|
||||
Storage::SharedMediaKey(
|
||||
key.mergedKey.migratedPeerId,
|
||||
key.type,
|
||||
SparseIdsMergedSlice::MigratedKey(key.mergedKey)),
|
||||
limitBefore,
|
||||
limitAfter)
|
||||
) | rpl::start_with_next([=](
|
||||
SparseIdsSlice &&part,
|
||||
SparseIdsSlice &&migrated) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key.mergedKey,
|
||||
std::move(part),
|
||||
std::move(migrated)));
|
||||
});
|
||||
auto createSimpleViewer = [=](
|
||||
PeerId peerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
return SharedMediaViewer(
|
||||
Storage::SharedMediaKey(
|
||||
peerId,
|
||||
key.type,
|
||||
simpleKey),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
);
|
||||
};
|
||||
return SparseIdsMergedSlice::CreateViewer(
|
||||
key.mergedKey,
|
||||
limitBefore,
|
||||
limitAfter,
|
||||
std::move(createSimpleViewer));
|
||||
}
|
||||
|
||||
SharedMediaWithLastSlice::SharedMediaWithLastSlice(Key key)
|
||||
|
|
|
@ -29,6 +29,7 @@ base::optional<Storage::SharedMediaType> SharedMediaOverviewType(
|
|||
void SharedMediaShowOverview(
|
||||
Storage::SharedMediaType type,
|
||||
not_null<History*> history);
|
||||
bool SharedMediaAllowSearch(Storage::SharedMediaType type);
|
||||
|
||||
rpl::producer<SparseIdsSlice> SharedMediaViewer(
|
||||
Storage::SharedMediaKey key,
|
||||
|
|
|
@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#include "history/history_sparse_ids.h"
|
||||
|
||||
#include <rpl/combine.h>
|
||||
#include "storage/storage_sparse_ids_list.h"
|
||||
|
||||
SparseIdsSlice::SparseIdsSlice(
|
||||
|
@ -389,3 +390,49 @@ SparseIdsSlice SparseIdsSliceBuilder::snapshot() const {
|
|||
_skippedBefore,
|
||||
_skippedAfter);
|
||||
}
|
||||
|
||||
rpl::producer<SparseIdsMergedSlice> SparseIdsMergedSlice::CreateViewer(
|
||||
SparseIdsMergedSlice::Key key,
|
||||
int limitBefore,
|
||||
int limitAfter,
|
||||
base::lambda<SimpleViewerFunction> simpleViewer) {
|
||||
Expects(IsServerMsgId(key.universalId)
|
||||
|| (key.universalId == 0)
|
||||
|| (IsServerMsgId(ServerMaxMsgId + key.universalId) && key.migratedPeerId != 0));
|
||||
Expects((key.universalId != 0)
|
||||
|| (limitBefore == 0 && limitAfter == 0));
|
||||
|
||||
return [=](auto consumer) {
|
||||
auto partViewer = simpleViewer(
|
||||
key.peerId,
|
||||
SparseIdsMergedSlice::PartKey(key),
|
||||
limitBefore,
|
||||
limitAfter
|
||||
);
|
||||
if (!key.migratedPeerId) {
|
||||
return std::move(partViewer)
|
||||
| rpl::start_with_next([=](SparseIdsSlice &&part) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key,
|
||||
std::move(part),
|
||||
base::none));
|
||||
});
|
||||
}
|
||||
auto migratedViewer = simpleViewer(
|
||||
key.migratedPeerId,
|
||||
SparseIdsMergedSlice::MigratedKey(key),
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
return rpl::combine(
|
||||
std::move(partViewer),
|
||||
std::move(migratedViewer)
|
||||
) | rpl::start_with_next([=](
|
||||
SparseIdsSlice &&part,
|
||||
SparseIdsSlice &&migrated) {
|
||||
consumer.put_next(SparseIdsMergedSlice(
|
||||
key,
|
||||
std::move(part),
|
||||
std::move(migrated)));
|
||||
});
|
||||
};
|
||||
}
|
|
@ -101,16 +101,26 @@ public:
|
|||
base::optional<int> distance(const Key &a, const Key &b) const;
|
||||
base::optional<UniversalMsgId> nearest(UniversalMsgId id) const;
|
||||
|
||||
using SimpleViewerFunction = rpl::producer<SparseIdsSlice>(
|
||||
PeerId peerId,
|
||||
SparseIdsSlice::Key simpleKey,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
static rpl::producer<SparseIdsMergedSlice> CreateViewer(
|
||||
SparseIdsMergedSlice::Key key,
|
||||
int limitBefore,
|
||||
int limitAfter,
|
||||
base::lambda<SimpleViewerFunction> simpleViewer);
|
||||
|
||||
private:
|
||||
static SparseIdsSlice::Key PartKey(const Key &key) {
|
||||
return (key.universalId < 0) ? 1 : key.universalId;
|
||||
}
|
||||
static SparseIdsSlice::Key MigratedKey(const Key &key) {
|
||||
return (key.universalId < 0)
|
||||
? (ServerMaxMsgId + key.universalId)
|
||||
: (key.universalId > 0) ? (ServerMaxMsgId - 1) : 0;
|
||||
? (ServerMaxMsgId + key.universalId)
|
||||
: (key.universalId > 0) ? (ServerMaxMsgId - 1) : 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static base::optional<SparseIdsSlice> MigratedSlice(const Key &key) {
|
||||
return key.migratedPeerId
|
||||
? base::make_optional(SparseIdsSlice())
|
||||
|
@ -176,7 +186,17 @@ public:
|
|||
bool removeAll();
|
||||
|
||||
void checkInsufficient();
|
||||
using AroundData = std::pair<MsgId, SparseIdsLoadDirection>;
|
||||
struct AroundData {
|
||||
MsgId aroundId = 0;
|
||||
SparseIdsLoadDirection direction
|
||||
= SparseIdsLoadDirection::Around;
|
||||
|
||||
inline bool operator<(const AroundData &other) const {
|
||||
return (aroundId < other.aroundId)
|
||||
|| ((aroundId == other.aroundId)
|
||||
&& (direction < other.direction));
|
||||
}
|
||||
};
|
||||
auto insufficientAround() const {
|
||||
return _insufficientAround.events();
|
||||
}
|
||||
|
|
|
@ -404,3 +404,13 @@ infoCommonGroupsList: PeerList(infoMembersList) {
|
|||
statusPosition: point(79px, 31px);
|
||||
}
|
||||
}
|
||||
|
||||
infoMediaSearch: SearchFieldRow {
|
||||
height: 44px;
|
||||
padding: margins(8px, 6px, 8px, 6px);
|
||||
field: contactsSearchField;
|
||||
fieldIcon: boxFieldSearchIcon;
|
||||
fieldIconSkip: 36px;
|
||||
fieldCancel: contactsSearchCancel;
|
||||
fieldCancelSkip: 40px;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include "ui/widgets/discrete_sliders.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
|
@ -230,11 +231,36 @@ object_ptr<ListWidget> InnerWidget::setupList(
|
|||
not_null<Window::Controller*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
Type type) {
|
||||
if (SharedMediaAllowSearch(type)) {
|
||||
_searchFieldController
|
||||
= std::make_unique<Ui::SearchFieldController>();
|
||||
_searchFieldController->queryValue()
|
||||
| rpl::start_with_next([=](QString &&query) {
|
||||
_searchController.setQuery(produceSearchQuery(
|
||||
peer,
|
||||
type,
|
||||
std::move(query)));
|
||||
}, _searchFieldController->lifetime());
|
||||
_searchField = _searchFieldController->createView(
|
||||
this,
|
||||
st::infoMediaSearch);
|
||||
_searchField->resizeToWidth(width());
|
||||
_searchField->show();
|
||||
} else {
|
||||
_searchField = nullptr;
|
||||
_searchFieldController = nullptr;
|
||||
}
|
||||
_searchController.setQueryFast(produceSearchQuery(peer, type));
|
||||
auto result = object_ptr<ListWidget>(
|
||||
this,
|
||||
controller,
|
||||
peer,
|
||||
type);
|
||||
type,
|
||||
produceListSource());
|
||||
_searchController.sourceChanged()
|
||||
| rpl::start_with_next([widget = result.data()]{
|
||||
widget->restart();
|
||||
}, result->lifetime());
|
||||
result->heightValue()
|
||||
| rpl::start_with_next(
|
||||
[this] { refreshHeight(); },
|
||||
|
@ -268,6 +294,49 @@ void InnerWidget::cancelSelection() {
|
|||
_list->cancelSelection();
|
||||
}
|
||||
|
||||
InnerWidget::~InnerWidget() = default;
|
||||
|
||||
ListWidget::Source InnerWidget::produceListSource() {
|
||||
return [this](
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
auto query = _searchController.currentQuery();
|
||||
if (query.query.isEmpty()) {
|
||||
return SharedMediaMergedViewer(
|
||||
SharedMediaMergedKey(
|
||||
SparseIdsMergedSlice::Key(
|
||||
query.peerId,
|
||||
query.migratedPeerId,
|
||||
aroundId),
|
||||
query.type),
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
}
|
||||
return _searchController.idsSlice(
|
||||
aroundId,
|
||||
limitBefore,
|
||||
limitAfter);
|
||||
};
|
||||
}
|
||||
|
||||
auto InnerWidget::produceSearchQuery(
|
||||
not_null<PeerData*> peer,
|
||||
Type type,
|
||||
QString &&query) const -> SearchQuery {
|
||||
auto result = SearchQuery();
|
||||
result.type = type;
|
||||
result.peerId = peer->id;
|
||||
result.query = std::move(query);
|
||||
result.migratedPeerId = [&] {
|
||||
if (auto migrateFrom = peer->migrateFrom()) {
|
||||
return migrateFrom->id;
|
||||
}
|
||||
return PeerId(0);
|
||||
}();
|
||||
return result;
|
||||
}
|
||||
|
||||
int InnerWidget::resizeGetHeight(int newWidth) {
|
||||
_inResize = true;
|
||||
auto guard = gsl::finally([this] { _inResize = false; });
|
||||
|
@ -276,6 +345,9 @@ int InnerWidget::resizeGetHeight(int newWidth) {
|
|||
_otherTypes->resizeToWidth(newWidth);
|
||||
_otherTabsShadow->resizeToWidth(newWidth);
|
||||
}
|
||||
if (_searchField) {
|
||||
_searchField->resizeToWidth(newWidth);
|
||||
}
|
||||
_list->resizeToWidth(newWidth);
|
||||
return recountHeight();
|
||||
}
|
||||
|
@ -294,6 +366,10 @@ int InnerWidget::recountHeight() {
|
|||
top += _otherTypes->heightNoMargins() - st::lineWidth;
|
||||
_otherTabsShadow->moveToLeft(0, top);
|
||||
}
|
||||
if (_searchField) {
|
||||
_searchField->moveToLeft(0, top);
|
||||
top += _searchField->heightNoMargins() - st::lineWidth;
|
||||
}
|
||||
if (_list) {
|
||||
_list->moveToLeft(0, top);
|
||||
top += _list->heightNoMargins();
|
||||
|
|
|
@ -22,10 +22,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "info/media/info_media_widget.h"
|
||||
#include "info/media/info_media_list_widget.h"
|
||||
#include "history/history_search_controller.h"
|
||||
|
||||
namespace Ui {
|
||||
class SettingsSlider;
|
||||
class VerticalLayout;
|
||||
class SearchFieldController;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
|
@ -58,6 +61,8 @@ public:
|
|||
rpl::producer<SelectedItems> selectedListValue() const;
|
||||
void cancelSelection();
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void visibleTopBottomUpdated(
|
||||
|
@ -73,6 +78,13 @@ private:
|
|||
void createTabs();
|
||||
void switchToTab(Memento &&memento);
|
||||
|
||||
using SearchQuery = Api::DelayedSearchController::Query;
|
||||
ListWidget::Source produceListSource();
|
||||
SearchQuery produceSearchQuery(
|
||||
not_null<PeerData*> peer,
|
||||
Type type,
|
||||
QString &&query = QString()) const;
|
||||
|
||||
not_null<Window::Controller*> controller() const;
|
||||
|
||||
object_ptr<ListWidget> setupList(
|
||||
|
@ -85,10 +97,13 @@ private:
|
|||
Ui::SettingsSlider *_otherTabs = nullptr;
|
||||
object_ptr<Ui::VerticalLayout> _otherTypes = { nullptr };
|
||||
object_ptr<Ui::PlainShadow> _otherTabsShadow = { nullptr };
|
||||
std::unique_ptr<Ui::SearchFieldController> _searchFieldController;
|
||||
Ui::RpWidget *_searchField = nullptr;
|
||||
object_ptr<ListWidget> _list = { nullptr };
|
||||
|
||||
rpl::event_stream<int> _scrollToRequests;
|
||||
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
|
||||
Api::DelayedSearchController _searchController;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -540,11 +540,13 @@ ListWidget::ListWidget(
|
|||
QWidget *parent,
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
Type type)
|
||||
Type type,
|
||||
Source source)
|
||||
: RpWidget(parent)
|
||||
, _controller(controller)
|
||||
, _peer(peer)
|
||||
, _type(type)
|
||||
, _source(std::move(source))
|
||||
, _slice(sliceKey(_universalAroundId)) {
|
||||
setAttribute(Qt::WA_MouseTracking);
|
||||
start();
|
||||
|
@ -574,6 +576,20 @@ void ListWidget::start() {
|
|||
}, lifetime());
|
||||
}
|
||||
|
||||
void ListWidget::restart() {
|
||||
mouseActionCancel();
|
||||
|
||||
_overLayout = nullptr;
|
||||
_sections.clear();
|
||||
_layouts.clear();
|
||||
|
||||
_universalAroundId = kDefaultAroundId;
|
||||
_idsLimit = kMinimalIdsLimit;
|
||||
_slice = SparseIdsMergedSlice(sliceKey(_universalAroundId));
|
||||
|
||||
refreshViewer();
|
||||
}
|
||||
|
||||
void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
if (isMyItem(item)) {
|
||||
auto universalId = GetUniversalId(item);
|
||||
|
@ -753,12 +769,7 @@ SparseIdsMergedSlice::Key ListWidget::sliceKey(
|
|||
|
||||
void ListWidget::refreshViewer() {
|
||||
_viewerLifetime.destroy();
|
||||
SharedMediaMergedViewer(
|
||||
SharedMediaMergedKey(
|
||||
sliceKey(_universalAroundId),
|
||||
_type),
|
||||
_idsLimit,
|
||||
_idsLimit)
|
||||
_source(_universalAroundId, _idsLimit, _idsLimit)
|
||||
| rpl::start_with_next([this](
|
||||
SparseIdsMergedSlice &&slice) {
|
||||
_slice = std::move(slice);
|
||||
|
|
|
@ -47,11 +47,17 @@ using UniversalMsgId = int32;
|
|||
class ListWidget : public Ui::RpWidget {
|
||||
public:
|
||||
using Type = Widget::Type;
|
||||
using Source = base::lambda<
|
||||
rpl::producer<SparseIdsMergedSlice>(
|
||||
SparseIdsMergedSlice::UniversalMsgId aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter)>;
|
||||
ListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
Type type);
|
||||
Type type,
|
||||
Source source);
|
||||
|
||||
not_null<Window::Controller*> controller() const {
|
||||
return _controller;
|
||||
|
@ -63,6 +69,8 @@ public:
|
|||
return _type;
|
||||
}
|
||||
|
||||
void restart();
|
||||
|
||||
rpl::producer<int> scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
@ -275,6 +283,7 @@ private:
|
|||
not_null<Window::Controller*> _controller;
|
||||
not_null<PeerData*> _peer;
|
||||
Type _type = Type::Photo;
|
||||
Source _source;
|
||||
|
||||
static constexpr auto kMinimalIdsLimit = 16;
|
||||
static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1);
|
||||
|
|
|
@ -319,8 +319,10 @@ private:
|
|||
RequestWrap(RequestWrap &&other) : _id(base::take(other._id)) {
|
||||
}
|
||||
RequestWrap &operator=(RequestWrap &&other) {
|
||||
cancelRequest();
|
||||
_id = base::take(other._id);
|
||||
if (_id != other._id) {
|
||||
cancelRequest();
|
||||
_id = base::take(other._id);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,27 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
#include <rpl/event_stream.h>
|
||||
#include <rpl/map.h>
|
||||
#include <rpl/distinct_until_changed.h>
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
template <typename Widget, typename ...Args>
|
||||
inline base::unique_qptr<Widget> CreateObject(Args &&...args) {
|
||||
return base::make_unique_q<Widget>(
|
||||
nullptr,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename Widget, typename Parent, typename ...Args>
|
||||
inline Widget *CreateChild(
|
||||
Parent *parent,
|
||||
Args &&...args) {
|
||||
Expects(parent != nullptr);
|
||||
return base::make_unique_q<Widget>(
|
||||
parent,
|
||||
std::forward<Args>(args)...).release();
|
||||
}
|
||||
|
||||
template <typename Widget>
|
||||
using RpWidgetParent = std::conditional_t<
|
||||
std::is_same_v<Widget, QWidget>,
|
||||
|
|
108
Telegram/SourceFiles/ui/search_field_controller.cpp
Normal file
108
Telegram/SourceFiles/ui/search_field_controller.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "ui/search_field_controller.h"
|
||||
|
||||
#include "styles/style_widgets.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
object_ptr<Ui::RpWidget> SearchFieldController::createView(
|
||||
QWidget *parent,
|
||||
const style::SearchFieldRow &st) {
|
||||
auto result = object_ptr<Ui::FixedHeightWidget>(
|
||||
parent,
|
||||
st.height);
|
||||
|
||||
auto cancel = CreateChild<Ui::CrossButton>(
|
||||
result.data(),
|
||||
st.fieldCancel);
|
||||
cancel->addClickHandler([=] { clearQuery(); });
|
||||
|
||||
auto field = CreateChild<Ui::InputField>(
|
||||
result.data(),
|
||||
st.field,
|
||||
langFactory(lng_dlg_filter),
|
||||
_query.current());
|
||||
field->show();
|
||||
field->connect(field, &Ui::InputField::changed, [=] {
|
||||
setQueryFromField(field->getLastText());
|
||||
});
|
||||
field->connect(field, &Ui::InputField::cancelled, [=] {
|
||||
clearQuery();
|
||||
});
|
||||
|
||||
auto shadow = CreateChild<Ui::PlainShadow>(result.data());
|
||||
shadow->show();
|
||||
|
||||
result->widthValue()
|
||||
| rpl::start_with_next([=, &st](int newWidth) {
|
||||
auto availableWidth = newWidth
|
||||
- st.fieldIconSkip
|
||||
- st.fieldCancelSkip;
|
||||
field->setGeometryToLeft(
|
||||
st.padding.left() + st.fieldIconSkip,
|
||||
st.padding.top(),
|
||||
availableWidth,
|
||||
field->height());
|
||||
cancel->moveToRight(0, 0);
|
||||
shadow->setGeometry(
|
||||
0,
|
||||
st.height - st::lineWidth,
|
||||
newWidth,
|
||||
st::lineWidth);
|
||||
}, result->lifetime());
|
||||
result->paintRequest()
|
||||
| rpl::start_with_next([=, &st] {
|
||||
Painter p(_view.wrap);
|
||||
st.fieldIcon.paint(
|
||||
p,
|
||||
st.padding.left(),
|
||||
st.padding.top(),
|
||||
_view.wrap->width());
|
||||
}, result->lifetime());
|
||||
|
||||
_view.wrap.reset(result);
|
||||
_view.cancel = cancel;
|
||||
_view.field = field;
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
void SearchFieldController::setQueryFromField(const QString &query) {
|
||||
_query = query;
|
||||
if (_view.cancel) {
|
||||
_view.cancel->toggleAnimated(!query.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void SearchFieldController::clearQuery() {
|
||||
if (_view.field) {
|
||||
_view.field->setText(QString());
|
||||
} else {
|
||||
setQueryFromField(QString());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui
|
66
Telegram/SourceFiles/ui/search_field_controller.h
Normal file
66
Telegram/SourceFiles/ui/search_field_controller.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <rpl/variable.h>
|
||||
#include "ui/rp_widget.h"
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace style {
|
||||
struct SearchFieldRow;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class CrossButton;
|
||||
class InputField;
|
||||
|
||||
class SearchFieldController {
|
||||
public:
|
||||
object_ptr<Ui::RpWidget> createView(
|
||||
QWidget *parent,
|
||||
const style::SearchFieldRow &st);
|
||||
|
||||
rpl::producer<QString> queryValue() const {
|
||||
return _query.value();
|
||||
}
|
||||
|
||||
rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
void setQueryFromField(const QString &query);
|
||||
void clearQuery();
|
||||
|
||||
struct View {
|
||||
base::unique_qptr<Ui::RpWidget> wrap;
|
||||
Ui::InputField *field = nullptr;
|
||||
Ui::CrossButton *cancel = nullptr;
|
||||
};
|
||||
View _view;
|
||||
rpl::variable<QString> _query;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -237,7 +237,11 @@ class FlatInput : public TWidgetHelper<QLineEdit>, private base::Subscriber {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FlatInput(QWidget *parent, const style::FlatInput &st, base::lambda<QString()> placeholderFactory = base::lambda<QString()>(), const QString &val = QString());
|
||||
FlatInput(
|
||||
QWidget *parent,
|
||||
const style::FlatInput &st,
|
||||
base::lambda<QString()> placeholderFactory = nullptr,
|
||||
const QString &val = QString());
|
||||
|
||||
void updatePlaceholder();
|
||||
void setPlaceholder(base::lambda<QString()> placeholderFactory);
|
||||
|
|
|
@ -1122,3 +1122,12 @@ InfoTopBar {
|
|||
mediaForward: IconButton;
|
||||
mediaDelete: IconButton;
|
||||
}
|
||||
SearchFieldRow {
|
||||
height: pixels;
|
||||
padding: margins;
|
||||
field: InputField;
|
||||
fieldIcon: icon;
|
||||
fieldIconSkip: pixels;
|
||||
fieldCancel: CrossButton;
|
||||
fieldCancelSkip: pixels;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<(src_loc)/base/type_traits.h
|
||||
<(src_loc)/base/unique_any.h
|
||||
<(src_loc)/base/unique_function.h
|
||||
<(src_loc)/base/unique_qptr.h
|
||||
<(src_loc)/base/variant.h
|
||||
<(src_loc)/base/virtual_method.h
|
||||
<(src_loc)/base/weak_unique_ptr.h
|
||||
|
@ -197,6 +198,8 @@
|
|||
<(src_loc)/history/history_media_types.h
|
||||
<(src_loc)/history/history_message.cpp
|
||||
<(src_loc)/history/history_message.h
|
||||
<(src_loc)/history/history_search_controller.cpp
|
||||
<(src_loc)/history/history_search_controller.h
|
||||
<(src_loc)/history/history_service.cpp
|
||||
<(src_loc)/history/history_service.h
|
||||
<(src_loc)/history/history_service_layout.cpp
|
||||
|
@ -598,6 +601,8 @@
|
|||
<(src_loc)/ui/images.cpp
|
||||
<(src_loc)/ui/images.h
|
||||
<(src_loc)/ui/rp_widget.h
|
||||
<(src_loc)/ui/search_field_controller.cpp
|
||||
<(src_loc)/ui/search_field_controller.h
|
||||
<(src_loc)/ui/special_buttons.cpp
|
||||
<(src_loc)/ui/special_buttons.h
|
||||
<(src_loc)/ui/twidget.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue