Implement jump-to-date in topics.

This commit is contained in:
John Preston 2022-10-27 11:14:49 +04:00
parent ee8f997c14
commit 5d76415a5d
7 changed files with 182 additions and 59 deletions

View file

@ -2834,13 +2834,16 @@ void ApiWrap::resolveJumpToDate(
const QDate &date, const QDate &date,
Fn<void(not_null<PeerData*>, MsgId)> callback) { Fn<void(not_null<PeerData*>, MsgId)> callback) {
if (const auto peer = chat.peer()) { if (const auto peer = chat.peer()) {
resolveJumpToHistoryDate(peer, date, std::move(callback)); const auto topic = chat.topic();
const auto rootId = topic ? topic->rootId() : 0;
resolveJumpToHistoryDate(peer, rootId, date, std::move(callback));
} }
} }
template <typename Callback> template <typename Callback>
void ApiWrap::requestMessageAfterDate( void ApiWrap::requestMessageAfterDate(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
const QDate &date, const QDate &date,
Callback &&callback) { Callback &&callback) {
// API returns a message with date <= offset_date. // API returns a message with date <= offset_date.
@ -2853,75 +2856,95 @@ void ApiWrap::requestMessageAfterDate(
const auto maxId = 0; const auto maxId = 0;
const auto minId = 0; const auto minId = 0;
const auto historyHash = uint64(0); const auto historyHash = uint64(0);
request(MTPmessages_GetHistory(
peer->input, auto send = [&](auto &&serialized) {
MTP_int(offsetId), request(std::move(serialized)).done([
MTP_int(offsetDate), =,
MTP_int(addOffset), callback = std::forward<Callback>(callback)
MTP_int(limit), ](const MTPmessages_Messages &result) {
MTP_int(maxId), const auto handleMessages = [&](auto &messages) {
MTP_int(minId),
MTP_long(historyHash)
)).done([
=,
callback = std::forward<Callback>(callback)
](const MTPmessages_Messages &result) {
auto getMessagesList = [&]() -> const QVector<MTPMessage>* {
auto handleMessages = [&](auto &messages) {
_session->data().processUsers(messages.vusers()); _session->data().processUsers(messages.vusers());
_session->data().processChats(messages.vchats()); _session->data().processChats(messages.vchats());
return &messages.vmessages().v; return &messages.vmessages().v;
}; };
switch (result.type()) { const auto list = result.match([&](
case mtpc_messages_messages: const MTPDmessages_messages &data) {
return handleMessages(result.c_messages_messages()); return handleMessages(result.c_messages_messages());
case mtpc_messages_messagesSlice: }, [&](const MTPDmessages_messagesSlice &data) {
return handleMessages(result.c_messages_messagesSlice()); return handleMessages(result.c_messages_messagesSlice());
case mtpc_messages_channelMessages: { }, [&](const MTPDmessages_channelMessages &data) {
auto &messages = result.c_messages_channelMessages(); const auto &messages = result.c_messages_channelMessages();
if (peer && peer->isChannel()) { if (peer && peer->isChannel()) {
peer->asChannel()->ptsReceived(messages.vpts().v); peer->asChannel()->ptsReceived(messages.vpts().v);
} else { } else {
LOG(("API Error: received messages.channelMessages when no channel was passed! (ApiWrap::jumpToDate)")); LOG(("API Error: received messages.channelMessages when "
"no channel was passed! (ApiWrap::jumpToDate)"));
} }
return handleMessages(messages); return handleMessages(messages);
} break; }, [&](const MTPDmessages_messagesNotModified &) {
case mtpc_messages_messagesNotModified: { LOG(("API Error: received messages.messagesNotModified! "
LOG(("API Error: received messages.messagesNotModified! (ApiWrap::jumpToDate)")); "(ApiWrap::jumpToDate)"));
} break; return (const QVector<MTPMessage>*)nullptr;
} });
return nullptr; if (list) {
}; _session->data().processMessages(
*list,
if (const auto list = getMessagesList()) { NewMessageType::Existing);
_session->data().processMessages(*list, NewMessageType::Existing); for (const auto &message : *list) {
for (const auto &message : *list) { if (DateFromMessage(message) >= offsetDate) {
if (DateFromMessage(message) >= offsetDate) { callback(IdFromMessage(message));
callback(IdFromMessage(message)); return;
return; }
} }
} }
} callback(ShowAtUnreadMsgId);
callback(ShowAtUnreadMsgId); }).send();
}).send(); };
if (topicRootId) {
send(MTPmessages_GetReplies(
peer->input,
MTP_int(topicRootId),
MTP_int(offsetId),
MTP_int(offsetDate),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(maxId),
MTP_int(minId),
MTP_long(historyHash)));
} else {
send(MTPmessages_GetHistory(
peer->input,
MTP_int(offsetId),
MTP_int(offsetDate),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(maxId),
MTP_int(minId),
MTP_long(historyHash)));
}
} }
void ApiWrap::resolveJumpToHistoryDate( void ApiWrap::resolveJumpToHistoryDate(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
const QDate &date, const QDate &date,
Fn<void(not_null<PeerData*>, MsgId)> callback) { Fn<void(not_null<PeerData*>, MsgId)> callback) {
if (const auto channel = peer->migrateTo()) { if (const auto channel = peer->migrateTo()) {
return resolveJumpToHistoryDate(channel, date, std::move(callback)); return resolveJumpToHistoryDate(
channel,
topicRootId,
date,
std::move(callback));
} }
const auto jumpToDateInPeer = [=] { const auto jumpToDateInPeer = [=] {
requestMessageAfterDate(peer, date, [=](MsgId resultId) { requestMessageAfterDate(peer, topicRootId, date, [=](MsgId itemId) {
callback(peer, resultId); callback(peer, itemId);
}); });
}; };
if (const auto chat = peer->migrateFrom()) { if (const auto chat = topicRootId ? nullptr : peer->migrateFrom()) {
requestMessageAfterDate(chat, date, [=](MsgId resultId) { requestMessageAfterDate(chat, 0, date, [=](MsgId itemId) {
if (resultId) { if (itemId) {
callback(chat, resultId); callback(chat, itemId);
} else { } else {
jumpToDateInPeer(); jumpToDateInPeer();
} }

View file

@ -454,11 +454,13 @@ private:
void resolveJumpToHistoryDate( void resolveJumpToHistoryDate(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
const QDate &date, const QDate &date,
Fn<void(not_null<PeerData*>, MsgId)> callback); Fn<void(not_null<PeerData*>, MsgId)> callback);
template <typename Callback> template <typename Callback>
void requestMessageAfterDate( void requestMessageAfterDate(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
const QDate &date, const QDate &date,
Callback &&callback); Callback &&callback);

View file

@ -223,7 +223,9 @@ void Forum::applyReceivedTopics(
} }
void Forum::requestSomeStale() { void Forum::requestSomeStale() {
if (_staleRequestId || (!_offset.id && _requestId)) { if (_staleRequestId
|| (!_offset.id && _requestId)
|| _staleRootIds.empty()) {
return; return;
} }
const auto type = Histories::RequestType::History; const auto type = Histories::RequestType::History;
@ -241,6 +243,9 @@ void Forum::requestSomeStale() {
break; break;
} }
} }
if (rootIds.empty()) {
return;
}
const auto call = [=] { const auto call = [=] {
for (const auto &id : rootIds) { for (const auto &id : rootIds) {
finishTopicRequest(id.v); finishTopicRequest(id.v);

View file

@ -779,7 +779,11 @@ void Widget::refreshTopBars() {
_subsectionTopBar->searchQuery( _subsectionTopBar->searchQuery(
) | rpl::start_with_next([=](QString query) { ) | rpl::start_with_next([=](QString query) {
applyFilterUpdate(); applyFilterUpdate();
}, lifetime()); }, _subsectionTopBar->lifetime());
_subsectionTopBar->jumpToDateRequest(
) | rpl::start_with_next([=] {
showCalendar();
}, _subsectionTopBar->lifetime());
updateControlsGeometry(); updateControlsGeometry();
} }
const auto history = _openedForum const auto history = _openedForum
@ -1386,14 +1390,16 @@ void Widget::searchTopics() {
MTP_int(kSearchPerPage) MTP_int(kSearchPerPage)
)).done([=](const MTPmessages_ForumTopics &result) { )).done([=](const MTPmessages_ForumTopics &result) {
_topicSearchRequest = 0; _topicSearchRequest = 0;
const auto savedTopicId = _topicSearchOffsetId; const auto savedTopicId = _topicSearchOffsetTopicId;
const auto byCreation = result.data().is_order_by_create_date(); const auto byCreation = result.data().is_order_by_create_date();
_openedForum->forum()->applyReceivedTopics(result, [&]( _openedForum->forum()->applyReceivedTopics(result, [&](
not_null<Data::ForumTopic*> topic) { not_null<Data::ForumTopic*> topic) {
_topicSearchOffsetTopicId = topic->rootId(); _topicSearchOffsetTopicId = topic->rootId();
if (byCreation) { if (byCreation) {
_topicSearchOffsetId = _topicSearchOffsetTopicId;
_topicSearchOffsetDate = topic->creationDate(); _topicSearchOffsetDate = topic->creationDate();
if (const auto last = topic->lastServerMessage()) {
_topicSearchOffsetId = last->id;
}
} else if (const auto last = topic->lastServerMessage()) { } else if (const auto last = topic->lastServerMessage()) {
_topicSearchOffsetId = last->id; _topicSearchOffsetId = last->id;
_topicSearchOffsetDate = last->date(); _topicSearchOffsetDate = last->date();
@ -1899,6 +1905,10 @@ void Widget::setSearchInChat(Key chat, PeerData *from) {
clearSearchCache(); clearSearchCache();
} }
_inner->searchInChat(_searchInChat, _searchFromAuthor); _inner->searchInChat(_searchInChat, _searchFromAuthor);
if (_subsectionTopBar) {
_subsectionTopBar->searchEnableJumpToDate(
_openedForum && _searchInChat);
}
if (_searchFromAuthor && _lastFilterText == SwitchToChooseFromQuery()) { if (_searchFromAuthor && _lastFilterText == SwitchToChooseFromQuery()) {
cancelSearch(); cancelSearch();
} }

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
@ -880,8 +881,10 @@ int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) {
} }
void TopBarWidget::updateSearchVisibility() { void TopBarWidget::updateSearchVisibility() {
const auto historyMode = (_activeChat.section == Section::History); const auto searchAllowedMode = (_activeChat.section == Section::History)
_search->setVisible(historyMode && !_chooseForReportReason); || (_activeChat.section == Section::Replies
&& _activeChat.key.topic());
_search->setVisible(searchAllowedMode && !_chooseForReportReason);
} }
void TopBarWidget::updateControlsGeometry() { void TopBarWidget::updateControlsGeometry() {
@ -894,6 +897,7 @@ void TopBarWidget::updateControlsGeometry() {
if (!_searchMode && !_searchShown.animating() && _searchField) { if (!_searchMode && !_searchShown.animating() && _searchField) {
_searchField.destroy(); _searchField.destroy();
_searchCancel.destroy(); _searchCancel.destroy();
_jumpToDate.destroy();
} }
auto searchFieldTop = _searchField auto searchFieldTop = _searchField
? countSelectedButtonsTop(_searchShown.value(_searchMode ? 1. : 0.)) ? countSelectedButtonsTop(_searchShown.value(_searchMode ? 1. : 0.))
@ -966,6 +970,11 @@ void TopBarWidget::updateControlsGeometry() {
_searchCancel->moveToLeft( _searchCancel->moveToLeft(
right - _searchCancel->width(), right - _searchCancel->width(),
_searchField->y()); _searchField->y());
if (_jumpToDate) {
_jumpToDate->moveToLeft(
right - _jumpToDate->width(),
_searchField->y());
}
right -= _searchCancel->width(); right -= _searchCancel->width();
} }
@ -1054,6 +1063,19 @@ void TopBarWidget::updateControlsVisibility() {
? (_activeChat.key.topic() != nullptr) ? (_activeChat.key.topic() != nullptr)
: false); : false);
updateSearchVisibility(); updateSearchVisibility();
if (_searchMode) {
const auto hasSearchQuery = _searchField
&& !_searchField->getLastText().isEmpty();
if (!_jumpToDate || hasSearchQuery) {
_searchCancel->show(anim::type::normal);
if (_jumpToDate) {
_jumpToDate->hide(anim::type::normal);
}
} else {
_searchCancel->hide(anim::type::normal);
_jumpToDate->show(anim::type::normal);
}
}
_menuToggle->setVisible(hasMenu _menuToggle->setVisible(hasMenu
&& !_chooseForReportReason && !_chooseForReportReason
&& !_narrowMode); && !_narrowMode);
@ -1201,7 +1223,13 @@ bool TopBarWidget::toggleSearch(bool shown, anim::type animated) {
_searchSubmitted.fire({}); _searchSubmitted.fire({});
}); });
QObject::connect(_searchField, &Ui::InputField::changed, [=] { QObject::connect(_searchField, &Ui::InputField::changed, [=] {
_searchQuery = _searchField->getLastText(); const auto wasEmpty = _searchQuery.current().isEmpty();
const auto query = _searchField->getLastText();
const auto nowEmpty = query.isEmpty();
if (_jumpToDate && nowEmpty != wasEmpty) {
updateControlsVisibility();
}
_searchQuery = query;
}); });
} else { } else {
Assert(_searchField != nullptr); Assert(_searchField != nullptr);
@ -1224,6 +1252,27 @@ bool TopBarWidget::toggleSearch(bool shown, anim::type animated) {
return true; return true;
} }
void TopBarWidget::searchEnableJumpToDate(bool enable) {
if (!_searchMode && !enable) {
return;
} else if (enable) {
_jumpToDate.create(
this,
object_ptr<Ui::IconButton>(this, st::dialogsCalendar));
_jumpToDate->toggle(
_searchField->getLastText().isEmpty(),
anim::type::instant);
_jumpToDate->entity()->clicks(
) | rpl::to_empty | rpl::start_to_stream(
_jumpToDateRequests,
_jumpToDate->lifetime());
} else {
_jumpToDate.destroy();
}
updateControlsVisibility();
updateControlsGeometry();
}
bool TopBarWidget::searchSetFocus() { bool TopBarWidget::searchSetFocus() {
if (!_searchMode) { if (!_searchMode) {
return false; return false;

View file

@ -77,6 +77,7 @@ public:
void clearChooseMessagesForReport(); void clearChooseMessagesForReport();
bool toggleSearch(bool shown, anim::type animated); bool toggleSearch(bool shown, anim::type animated);
void searchEnableJumpToDate(bool enable);
bool searchSetFocus(); bool searchSetFocus();
[[nodiscard]] bool searchHasFocus() const; [[nodiscard]] bool searchHasFocus() const;
[[nodiscard]] rpl::producer<> searchCancelled() const; [[nodiscard]] rpl::producer<> searchCancelled() const;
@ -100,6 +101,9 @@ public:
[[nodiscard]] rpl::producer<> cancelChooseForReportRequest() const { [[nodiscard]] rpl::producer<> cancelChooseForReportRequest() const {
return _cancelChooseForReport.events(); return _cancelChooseForReport.events();
} }
[[nodiscard]] rpl::producer<> jumpToDateRequest() const {
return _jumpToDateRequests.events();
}
[[nodiscard]] rpl::producer<> searchRequest() const; [[nodiscard]] rpl::producer<> searchRequest() const;
protected: protected:
@ -198,6 +202,7 @@ private:
rpl::variable<QString> _searchQuery; rpl::variable<QString> _searchQuery;
rpl::event_stream<> _searchCancelled; rpl::event_stream<> _searchCancelled;
rpl::event_stream<> _searchSubmitted; rpl::event_stream<> _searchSubmitted;
rpl::event_stream<> _jumpToDateRequests;
object_ptr<Ui::IconButton> _back; object_ptr<Ui::IconButton> _back;
object_ptr<Ui::IconButton> _cancelChoose; object_ptr<Ui::IconButton> _cancelChoose;

View file

@ -1396,12 +1396,18 @@ void SessionController::startOrJoinGroupCall(
} }
void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) { void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) {
const auto history = chat.history(); const auto topic = chat.topic();
const auto history = chat.owningHistory();
if (!history) { if (!history) {
return; return;
} }
const auto currentPeerDate = [&] { const auto currentPeerDate = [&] {
if (history->scrollTopItem) { if (topic) {
if (const auto item = topic->lastMessage()) {
return base::unixtime::parse(item->date()).date();
}
return QDate();
} else if (history->scrollTopItem) {
return history->scrollTopItem->dateTime().date(); return history->scrollTopItem->dateTime().date();
} else if (history->loadedAtTop() } else if (history->loadedAtTop()
&& !history->isEmpty() && !history->isEmpty()
@ -1419,6 +1425,12 @@ void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) {
return QDate(); return QDate();
}(); }();
const auto maxPeerDate = [&] { const auto maxPeerDate = [&] {
if (topic) {
if (const auto item = topic->lastMessage()) {
return base::unixtime::parse(item->date()).date();
}
return QDate();
}
const auto check = history->peer->migrateTo() const auto check = history->peer->migrateTo()
? history->owner().historyLoaded(history->peer->migrateTo()) ? history->owner().historyLoaded(history->peer->migrateTo())
: history; : history;
@ -1428,11 +1440,13 @@ void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) {
return QDate(); return QDate();
}(); }();
const auto minPeerDate = [&] { const auto minPeerDate = [&] {
const auto startDate = [] { const auto startDate = [&] {
// Telegram was launched in August 2013 :) // Telegram was launched in August 2013 :)
return QDate(2013, 8, 1); return QDate(2013, 8, 1);
}; };
if (const auto chat = history->peer->migrateFrom()) { if (topic) {
return base::unixtime::parse(topic->creationDate()).date();
} else if (const auto chat = history->peer->migrateFrom()) {
if (const auto history = chat->owner().historyLoaded(chat)) { if (const auto history = chat->owner().historyLoaded(chat)) {
if (history->loadedAtTop()) { if (history->loadedAtTop()) {
if (!history->isEmpty()) { if (!history->isEmpty()) {
@ -1515,13 +1529,28 @@ void SessionController::showCalendar(Dialogs::Key chat, QDate requestedDate) {
} }
}; };
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
const auto weakTopic = base::make_weak(topic);
const auto jump = [=](const QDate &date) { const auto jump = [=](const QDate &date) {
const auto open = [=](not_null<PeerData*> peer, MsgId id) { const auto open = [=](not_null<PeerData*> peer, MsgId id) {
if (const auto strong = weak.get()) { if (const auto strong = weak.get()) {
strong->showPeerHistory(peer, SectionShow::Way::Forward, id); if (!topic) {
strong->showPeerHistory(
peer,
SectionShow::Way::Forward,
id);
} else if (const auto strongTopic = weakTopic.get()) {
strong->showRepliesForMessage(
strongTopic->history(),
strongTopic->rootId(),
id,
SectionShow::Way::Forward);
strong->hideLayer(anim::type::normal);
}
} }
}; };
session().api().resolveJumpToDate(chat, date, open); if (!topic || weakTopic) {
session().api().resolveJumpToDate(chat, date, open);
}
}; };
show(Box<Ui::CalendarBox>(Ui::CalendarBoxArgs{ show(Box<Ui::CalendarBox>(Ui::CalendarBoxArgs{
.month = highlighted, .month = highlighted,