Added search to files and links shared media.

This commit is contained in:
John Preston 2017-10-30 23:24:20 +04:00
parent a27edcad1c
commit eb2719fad1
25 changed files with 1110 additions and 220 deletions

View file

@ -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
));
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View 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

View file

@ -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) {

View file

@ -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) {

View 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

View 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

View file

@ -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)

View file

@ -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,

View file

@ -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)));
});
};
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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();

View file

@ -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;
};

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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>,

View 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

View 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

View file

@ -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);

View file

@ -1122,3 +1122,12 @@ InfoTopBar {
mediaForward: IconButton;
mediaDelete: IconButton;
}
SearchFieldRow {
height: pixels;
padding: margins;
field: InputField;
fieldIcon: icon;
fieldIconSkip: pixels;
fieldCancel: CrossButton;
fieldCancelSkip: pixels;
}

View file

@ -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