Implement replies list request.
This commit is contained in:
parent
437c9320cd
commit
00cdae0369
|
@ -467,6 +467,8 @@ PRIVATE
|
|||
data/data_poll.h
|
||||
data/data_pts_waiter.cpp
|
||||
data/data_pts_waiter.h
|
||||
data/data_replies_list.cpp
|
||||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_search_controller.cpp
|
||||
|
@ -588,6 +590,8 @@ PRIVATE
|
|||
history/view/history_view_message.cpp
|
||||
history/view/history_view_message.h
|
||||
history/view/history_view_object.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
history/view/history_view_replies_section.h
|
||||
history/view/history_view_schedule_box.cpp
|
||||
history/view/history_view_schedule_box.h
|
||||
history/view/history_view_scheduled_section.cpp
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 315 B |
Binary file not shown.
After Width: | Height: | Size: 553 B |
Binary file not shown.
After Width: | Height: | Size: 573 B |
|
@ -1344,6 +1344,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
||||
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
||||
|
||||
"lng_replies_view#one" = "View {count} Reply";
|
||||
"lng_replies_view#other" = "View {count} Replies";
|
||||
"lng_replies_view_thread" = "View Thread";
|
||||
"lng_replies_header#one" = "{count} reply";
|
||||
"lng_replies_header#other" = "{count} replies";
|
||||
"lng_replies_header_none" = "No replies";
|
||||
"lng_replies_send_placeholder" = "Reply";
|
||||
|
||||
"lng_archived_name" = "Archived chats";
|
||||
"lng_archived_add" = "Archive";
|
||||
"lng_archived_remove" = "Unarchive";
|
||||
|
|
|
@ -272,8 +272,10 @@ void BoxController::prepare() {
|
|||
}, lifetime());
|
||||
|
||||
session().changes().messageUpdates(
|
||||
Data::MessageUpdate::Flag::CallAdded
|
||||
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
Data::MessageUpdate::Flag::NewAdded
|
||||
) | rpl::filter([=](const Data::MessageUpdate &update) {
|
||||
return (update.item->media()->call() != nullptr);
|
||||
}) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
|
||||
insertRow(update.item, InsertWay::Prepend);
|
||||
}, lifetime());
|
||||
|
||||
|
|
|
@ -133,11 +133,12 @@ struct MessageUpdate {
|
|||
Destroyed = (1 << 1),
|
||||
DialogRowRepaint = (1 << 2),
|
||||
DialogRowRefresh = (1 << 3),
|
||||
CallAdded = (1 << 4),
|
||||
NewAdded = (1 << 4),
|
||||
ReplyMarkup = (1 << 5),
|
||||
BotCallbackSent = (1 << 6),
|
||||
NewMaybeAdded = (1 << 7),
|
||||
|
||||
LastUsedBit = (1 << 6),
|
||||
LastUsedBit = (1 << 7),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -676,6 +676,9 @@ void Histories::checkPostponed(not_null<History*> history, int id) {
|
|||
}
|
||||
|
||||
void Histories::cancelRequest(int id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
const auto history = _historyByRequest.take(id);
|
||||
if (!history) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_replies_list.h"
|
||||
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_messages.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMessagesPerPage = 4; // #TODO replies
|
||||
|
||||
} // namespace
|
||||
|
||||
struct RepliesList::Viewer {
|
||||
MessagesSlice slice;
|
||||
MsgId around = 0;
|
||||
int limitBefore = 0;
|
||||
int limitAfter = 0;
|
||||
};
|
||||
|
||||
RepliesList::RepliesList(not_null<History*> history, MsgId rootId)
|
||||
: _history(history)
|
||||
, _rootId(rootId) {
|
||||
}
|
||||
|
||||
RepliesList::~RepliesList() {
|
||||
histories().cancelRequest(base::take(_beforeId));
|
||||
histories().cancelRequest(base::take(_afterId));
|
||||
}
|
||||
|
||||
rpl::producer<MessagesSlice> RepliesList::source(
|
||||
MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) {
|
||||
const auto around = aroundId.fullId.msg;
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto viewer = lifetime.make_state<Viewer>();
|
||||
const auto push = [=] {
|
||||
if (buildFromData(viewer)) {
|
||||
consumer.put_next_copy(viewer->slice);
|
||||
}
|
||||
};
|
||||
viewer->around = around;
|
||||
viewer->limitBefore = limitBefore;
|
||||
viewer->limitAfter = limitAfter;
|
||||
|
||||
_history->session().changes().messageUpdates(
|
||||
MessageUpdate::Flag::NewAdded
|
||||
| MessageUpdate::Flag::NewMaybeAdded
|
||||
| MessageUpdate::Flag::Destroyed
|
||||
) | rpl::filter([=](const MessageUpdate &update) {
|
||||
return applyUpdate(viewer, update);
|
||||
}) | rpl::start_with_next([=] {
|
||||
crl::on_main(this, push);
|
||||
}, lifetime);
|
||||
|
||||
_partLoaded.events(
|
||||
) | rpl::start_with_next([=] {
|
||||
crl::on_main(this, push);
|
||||
}, lifetime);
|
||||
|
||||
push();
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
rpl::producer<int> RepliesList::fullCount() const {
|
||||
return _fullCount.value() | rpl::filter_optional();
|
||||
}
|
||||
|
||||
bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
|
||||
if (_list.empty() && _skippedBefore == 0 && _skippedAfter == 0) {
|
||||
viewer->slice.ids.clear();
|
||||
viewer->slice.fullCount
|
||||
= viewer->slice.skippedBefore
|
||||
= viewer->slice.skippedAfter
|
||||
= 0;
|
||||
return true;
|
||||
}
|
||||
const auto around = viewer->around;
|
||||
if (_list.empty()
|
||||
|| (!around && _skippedAfter != 0)
|
||||
|| (around > 0 && around < _list.back())
|
||||
|| (around > _list.front())) {
|
||||
loadAround(around);
|
||||
return false;
|
||||
}
|
||||
const auto i = around
|
||||
? ranges::lower_bound(_list, around, std::greater<>())
|
||||
: end(_list);
|
||||
const auto availableBefore = (i - begin(_list));
|
||||
const auto availableAfter = (end(_list) - i);
|
||||
const auto useBefore = std::min(availableBefore, viewer->limitBefore);
|
||||
const auto useAfter = std::min(availableAfter, viewer->limitAfter + 1);
|
||||
const auto slice = &viewer->slice;
|
||||
if (_skippedBefore.has_value()) {
|
||||
slice->skippedBefore
|
||||
= (*_skippedBefore + (availableBefore - useBefore));
|
||||
}
|
||||
if (_skippedAfter.has_value()) {
|
||||
slice->skippedAfter
|
||||
= (*_skippedAfter + (availableAfter - useAfter));
|
||||
}
|
||||
const auto channelId = _history->channelId();
|
||||
slice->ids.clear();
|
||||
slice->ids.reserve(useBefore + useAfter);
|
||||
for (auto j = i - useBefore, e = i + useAfter; j != e; ++j) {
|
||||
slice->ids.emplace_back(channelId, *j);
|
||||
}
|
||||
ranges::reverse(slice->ids);
|
||||
slice->fullCount = _fullCount.current();
|
||||
if (_skippedBefore != 0 && useBefore < viewer->limitBefore) {
|
||||
loadBefore();
|
||||
}
|
||||
if (_skippedAfter != 0 && useAfter < viewer->limitAfter + 1) {
|
||||
loadAfter();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RepliesList::applyUpdate(
|
||||
not_null<Viewer*> viewer,
|
||||
const MessageUpdate &update) {
|
||||
if (update.item->history() != _history
|
||||
|| update.item->replyToTop() != _rootId) {
|
||||
return false;
|
||||
}
|
||||
const auto id = update.item->id;
|
||||
const auto i = ranges::lower_bound(_list, id, std::greater<>());
|
||||
if (update.flags & MessageUpdate::Flag::Destroyed) {
|
||||
if (i == end(_list) || *i != id) {
|
||||
return false;
|
||||
}
|
||||
_list.erase(i);
|
||||
if (_skippedBefore && _skippedAfter) {
|
||||
_fullCount = *_skippedBefore + _list.size() + *_skippedAfter;
|
||||
} else if (const auto known = _fullCount.current()) {
|
||||
if (*known > 0) {
|
||||
_fullCount = (*known - 1);
|
||||
}
|
||||
}
|
||||
} else if (_skippedAfter != 0) {
|
||||
return false;
|
||||
} else {
|
||||
if (i != end(_list) && *i == id) {
|
||||
return false;
|
||||
}
|
||||
_list.insert(i, id);
|
||||
if (_skippedBefore && _skippedAfter) {
|
||||
_fullCount = *_skippedBefore + _list.size() + *_skippedAfter;
|
||||
} else if (const auto known = _fullCount.current()) {
|
||||
_fullCount = *known + 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Histories &RepliesList::histories() {
|
||||
return _history->owner().histories();
|
||||
}
|
||||
|
||||
void RepliesList::loadAround(MsgId id) {
|
||||
if (_loadingAround && *_loadingAround == id) {
|
||||
return;
|
||||
}
|
||||
histories().cancelRequest(base::take(_beforeId));
|
||||
histories().cancelRequest(base::take(_afterId));
|
||||
|
||||
const auto send = [=](Fn<void()> finish) {
|
||||
return _history->session().api().request(MTPmessages_GetReplies(
|
||||
_history->peer->input,
|
||||
MTP_int(_rootId),
|
||||
MTP_int(id),
|
||||
MTP_int(id ? (-kMessagesPerPage / 2) : 0),
|
||||
MTP_int(kMessagesPerPage),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_beforeId = 0;
|
||||
_loadingAround = std::nullopt;
|
||||
finish();
|
||||
|
||||
if (!id) {
|
||||
_skippedAfter = 0;
|
||||
} else {
|
||||
_skippedAfter = std::nullopt;
|
||||
}
|
||||
_skippedBefore = std::nullopt;
|
||||
_list.clear();
|
||||
if (processMessagesIsEmpty(result)) {
|
||||
_fullCount = _skippedBefore = _skippedAfter = 0;
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_beforeId = 0;
|
||||
_loadingAround = std::nullopt;
|
||||
finish();
|
||||
}).send();
|
||||
};
|
||||
_loadingAround = id;
|
||||
_beforeId = histories().sendRequest(
|
||||
_history,
|
||||
Histories::RequestType::History,
|
||||
send);
|
||||
}
|
||||
|
||||
void RepliesList::loadBefore() {
|
||||
Expects(!_list.empty());
|
||||
|
||||
if (_loadingAround) {
|
||||
histories().cancelRequest(base::take(_beforeId));
|
||||
} else if (_beforeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto last = _list.back();
|
||||
const auto send = [=](Fn<void()> finish) {
|
||||
return _history->session().api().request(MTPmessages_GetReplies(
|
||||
_history->peer->input,
|
||||
MTP_int(_rootId),
|
||||
MTP_int(last),
|
||||
MTP_int(0),
|
||||
MTP_int(kMessagesPerPage),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_beforeId = 0;
|
||||
finish();
|
||||
|
||||
if (_list.empty()) {
|
||||
return;
|
||||
} else if (_list.back() != last) {
|
||||
loadBefore();
|
||||
} else if (processMessagesIsEmpty(result)) {
|
||||
_skippedBefore = 0;
|
||||
if (_skippedAfter == 0) {
|
||||
_fullCount = _list.size();
|
||||
}
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_beforeId = 0;
|
||||
finish();
|
||||
}).send();
|
||||
};
|
||||
_beforeId = histories().sendRequest(
|
||||
_history,
|
||||
Histories::RequestType::History,
|
||||
send);
|
||||
}
|
||||
|
||||
void RepliesList::loadAfter() {
|
||||
Expects(!_list.empty());
|
||||
|
||||
if (_afterId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto first = _list.front();
|
||||
const auto send = [=](Fn<void()> finish) {
|
||||
return _history->session().api().request(MTPmessages_GetReplies(
|
||||
_history->peer->input,
|
||||
MTP_int(_rootId),
|
||||
MTP_int(first + 1),
|
||||
MTP_int(-kMessagesPerPage),
|
||||
MTP_int(kMessagesPerPage),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_afterId = 0;
|
||||
finish();
|
||||
|
||||
if (_list.empty()) {
|
||||
return;
|
||||
} else if (_list.front() != first) {
|
||||
loadAfter();
|
||||
} else if (processMessagesIsEmpty(result)) {
|
||||
_skippedAfter = 0;
|
||||
if (_skippedBefore == 0) {
|
||||
_fullCount = _list.size();
|
||||
}
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
_afterId = 0;
|
||||
finish();
|
||||
}).send();
|
||||
};
|
||||
_afterId = histories().sendRequest(
|
||||
_history,
|
||||
Histories::RequestType::History,
|
||||
send);
|
||||
}
|
||||
|
||||
bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
|
||||
const auto guard = gsl::finally([&] { _partLoaded.fire({}); });
|
||||
|
||||
_fullCount = result.match([&](const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(HistoryWidget::messagesReceived)"));
|
||||
return 0;
|
||||
}, [&](const MTPDmessages_messages &data) {
|
||||
return int(data.vmessages().v.size());
|
||||
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||
return data.vcount().v;
|
||||
}, [&](const MTPDmessages_channelMessages &data) {
|
||||
if (_history->peer->isChannel()) {
|
||||
_history->peer->asChannel()->ptsReceived(data.vpts().v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (HistoryWidget::messagesReceived)"));
|
||||
}
|
||||
return data.vcount().v;
|
||||
});
|
||||
|
||||
auto &owner = _history->owner();
|
||||
const auto list = result.match([&](
|
||||
const MTPDmessages_messagesNotModified &) {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(HistoryWidget::messagesReceived)"));
|
||||
return QVector<MTPMessage>();
|
||||
}, [&](const auto &data) {
|
||||
owner.processUsers(data.vusers());
|
||||
owner.processChats(data.vchats());
|
||||
return data.vmessages().v;
|
||||
});
|
||||
if (list.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto id = IdFromMessage(list.front());
|
||||
const auto toFront = !_list.empty() && (id > _list.front());
|
||||
const auto clientFlags = MTPDmessage_ClientFlags();
|
||||
const auto type = NewMessageType::Existing;
|
||||
auto refreshed = std::vector<MsgId>();
|
||||
if (toFront) {
|
||||
refreshed.reserve(_list.size() + list.size());
|
||||
}
|
||||
for (const auto &message : list) {
|
||||
if (const auto item = owner.addNewMessage(message, clientFlags, type)) {
|
||||
if (item->replyToTop() == _rootId) {
|
||||
if (toFront) {
|
||||
refreshed.push_back(item->id);
|
||||
} else {
|
||||
_list.push_back(item->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (toFront) {
|
||||
refreshed.insert(refreshed.end(), _list.begin(), _list.end());
|
||||
_list = std::move(refreshed);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Data
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Histories;
|
||||
struct MessagePosition;
|
||||
struct MessagesSlice;
|
||||
struct MessageUpdate;
|
||||
|
||||
class RepliesList final : public base::has_weak_ptr {
|
||||
public:
|
||||
RepliesList(not_null<History*> history, MsgId rootId);
|
||||
~RepliesList();
|
||||
|
||||
[[nodiscard]] rpl::producer<MessagesSlice> source(
|
||||
MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> fullCount() const;
|
||||
|
||||
private:
|
||||
struct Viewer;
|
||||
|
||||
[[nodiscard]] Histories &histories();
|
||||
|
||||
[[nodiscard]] bool buildFromData(not_null<Viewer*> viewer);
|
||||
[[nodiscard]] bool applyUpdate(
|
||||
not_null<Viewer*> viewer,
|
||||
const MessageUpdate &update);
|
||||
bool processMessagesIsEmpty(const MTPmessages_Messages &result);
|
||||
void loadAround(MsgId id);
|
||||
void loadBefore();
|
||||
void loadAfter();
|
||||
|
||||
const not_null<History*> _history;
|
||||
const MsgId _rootId = 0;
|
||||
std::vector<MsgId> _list;
|
||||
std::optional<int> _skippedBefore;
|
||||
std::optional<int> _skippedAfter;
|
||||
rpl::variable<std::optional<int>> _fullCount;
|
||||
rpl::event_stream<> _partLoaded;
|
||||
std::optional<MsgId> _loadingAround;
|
||||
int _beforeId = 0;
|
||||
int _afterId = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -1685,6 +1685,9 @@ bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
|
|||
if (result) {
|
||||
stickers().checkSavedGif(existing);
|
||||
}
|
||||
session().changes().messageUpdated(
|
||||
existing,
|
||||
Data::MessageUpdate::Flag::NewMaybeAdded);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -1060,6 +1060,9 @@ void History::applyMessageChanges(
|
|||
applyServiceChanges(item, data.c_messageService());
|
||||
}
|
||||
owner().stickers().checkSavedGif(item);
|
||||
session().changes().messageUpdated(
|
||||
item,
|
||||
Data::MessageUpdate::Flag::NewAdded);
|
||||
}
|
||||
|
||||
void History::applyServiceChanges(
|
||||
|
@ -1247,12 +1250,6 @@ void History::applyServiceChanges(
|
|||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_messageActionPhoneCall: {
|
||||
item->history()->session().changes().messageUpdated(
|
||||
item,
|
||||
Data::MessageUpdate::Flag::CallAdded);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -159,6 +159,11 @@ historyViewsInSelectedIcon: icon {{ "history_views", msgInDateFgSelected }};
|
|||
historyViewsOutIcon: icon {{ "history_views", historyOutIconFg }};
|
||||
historyViewsOutSelectedIcon: icon {{ "history_views", historyOutIconFgSelected }};
|
||||
historyViewsInvertedIcon: icon {{ "history_views", historySendingInvertedIconFg }};
|
||||
historyRepliesInIcon: icon {{ "history_replies", msgInDateFg }};
|
||||
historyRepliesInSelectedIcon: icon {{ "history_replies", msgInDateFgSelected }};
|
||||
historyRepliesOutIcon: icon {{ "history_replies", historyOutIconFg }};
|
||||
historyRepliesOutSelectedIcon: icon {{ "history_replies", historyOutIconFgSelected }};
|
||||
historyRepliesInvertedIcon: icon {{ "history_replies", historySendingInvertedIconFg }};
|
||||
|
||||
historyComposeField: InputField(defaultInputField) {
|
||||
font: msgFont;
|
||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_service_message.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_replies_section.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/toast/toast.h"
|
||||
|
@ -1540,6 +1541,17 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
_widget->replyToMessage(itemId);
|
||||
});
|
||||
}
|
||||
if (IsServerMsgId(item->id) && item->repliesCount() > 0) {
|
||||
_menu->addAction(tr::lng_replies_view(tr::now, lt_count, item->repliesCount()), [=] {
|
||||
controller->showSection(
|
||||
HistoryView::RepliesMemento(_history, itemId.msg));
|
||||
});
|
||||
} else if (const auto replyToTop = item->replyToTop()) {
|
||||
_menu->addAction(tr::lng_replies_view_thread(tr::now), [=] {
|
||||
controller->showSection(
|
||||
HistoryView::RepliesMemento(_history, replyToTop));
|
||||
});
|
||||
}
|
||||
if (item->allowsEdit(base::unixtime::now())) {
|
||||
_menu->addAction(tr::lng_context_edit_msg(tr::now), [=] {
|
||||
_widget->editMessage(itemId);
|
||||
|
|
|
@ -1343,14 +1343,25 @@ void Message::drawInfo(
|
|||
}
|
||||
|
||||
if (auto views = item->Get<HistoryMessageViews>()) {
|
||||
const auto showReplies = (views->views < 0) && (views->replies > 0);
|
||||
auto icon = [&] {
|
||||
if (item->id > 0) {
|
||||
if (outbg) {
|
||||
return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsOutSelectedIcon : st::historyViewsOutIcon));
|
||||
return &(invertedsprites
|
||||
? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon)
|
||||
: selected
|
||||
? (showReplies ? st::historyRepliesOutSelectedIcon : st::historyViewsOutSelectedIcon)
|
||||
: (showReplies ? st::historyRepliesOutIcon : st::historyViewsOutIcon));
|
||||
}
|
||||
return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsInSelectedIcon : st::historyViewsInIcon));
|
||||
return &(invertedsprites
|
||||
? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon)
|
||||
: selected
|
||||
? (showReplies ? st::historyRepliesInSelectedIcon : st::historyViewsInSelectedIcon)
|
||||
: (showReplies ? st::historyRepliesInIcon : st::historyViewsInIcon));
|
||||
}
|
||||
return &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon);
|
||||
return &(invertedsprites
|
||||
? st::historyViewsSendingInvertedIcon
|
||||
: st::historyViewsSendingIcon);
|
||||
}();
|
||||
if (item->id > 0) {
|
||||
icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "window/section_widget.h"
|
||||
#include "window/section_memento.h"
|
||||
#include "history/view/history_view_list_widget.h"
|
||||
#include "data/data_messages.h"
|
||||
|
||||
class History;
|
||||
enum class CompressConfirm;
|
||||
enum class SendMediaType;
|
||||
struct SendingAlbum;
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace Api {
|
||||
struct SendOptions;
|
||||
} // namespace Api
|
||||
|
||||
namespace Storage {
|
||||
struct PreparedList;
|
||||
} // namespace Storage
|
||||
|
||||
namespace Ui {
|
||||
class ScrollArea;
|
||||
class PlainShadow;
|
||||
class FlatButton;
|
||||
class HistoryDownButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Profile {
|
||||
class BackButton;
|
||||
} // namespace Profile
|
||||
|
||||
namespace InlineBots {
|
||||
class Result;
|
||||
} // namespace InlineBots
|
||||
|
||||
namespace Data {
|
||||
class RepliesList;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class Element;
|
||||
class TopBarWidget;
|
||||
class RepliesMemento;
|
||||
class ComposeControls;
|
||||
|
||||
class RepliesWidget final
|
||||
: public Window::SectionWidget
|
||||
, private ListDelegate {
|
||||
public:
|
||||
RepliesWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> history,
|
||||
MsgId rootId);
|
||||
~RepliesWidget();
|
||||
|
||||
[[nodiscard]] not_null<History*> history() const;
|
||||
Dialogs::RowDescriptor activeChat() const override;
|
||||
|
||||
bool hasTopBarShadow() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
QPixmap grabForShowAnimation(
|
||||
const Window::SectionSlideParams ¶ms) override;
|
||||
|
||||
bool showInternal(
|
||||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms) override;
|
||||
std::unique_ptr<Window::SectionMemento> createMemento() override;
|
||||
|
||||
void setInternalState(
|
||||
const QRect &geometry,
|
||||
not_null<RepliesMemento*> memento);
|
||||
|
||||
// Tabbed selector management.
|
||||
bool pushTabbedSelectorToThirdSection(
|
||||
not_null<PeerData*> peer,
|
||||
const Window::SectionShow ¶ms) override;
|
||||
bool returnTabbedSelector() override;
|
||||
|
||||
// Float player interface.
|
||||
bool floatPlayerHandleWheelEvent(QEvent *e) override;
|
||||
QRect floatPlayerAvailableRect() override;
|
||||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
void listScrollTo(int top) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
Data::MessagePosition aroundId,
|
||||
int limitBefore,
|
||||
int limitAfter) override;
|
||||
bool listAllowsMultiSelect() override;
|
||||
bool listIsItemGoodForSelection(not_null<HistoryItem*> item) override;
|
||||
bool listIsLessInOrder(
|
||||
not_null<HistoryItem*> first,
|
||||
not_null<HistoryItem*> second) override;
|
||||
void listSelectionChanged(SelectedItems &&items) override;
|
||||
void listVisibleItemsChanged(HistoryItemsList &&items) override;
|
||||
std::optional<int> listUnreadBarView(
|
||||
const std::vector<not_null<Element*>> &elements) override;
|
||||
void listContentRefreshed() override;
|
||||
ClickHandlerPtr listDateLink(not_null<Element*> view) override;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void showAnimatedHook(
|
||||
const Window::SectionSlideParams ¶ms) override;
|
||||
void showFinishedHook() override;
|
||||
void doSetInnerFocus() override;
|
||||
|
||||
private:
|
||||
void onScroll();
|
||||
void updateInnerVisibleArea();
|
||||
void updateControlsGeometry();
|
||||
void updateAdaptiveLayout();
|
||||
void saveState(not_null<RepliesMemento*> memento);
|
||||
void restoreState(not_null<RepliesMemento*> memento);
|
||||
void showAtPosition(Data::MessagePosition position);
|
||||
bool showAtPositionNow(Data::MessagePosition position);
|
||||
|
||||
void setupComposeControls();
|
||||
|
||||
void setupDragArea();
|
||||
|
||||
void setupScrollDownButton();
|
||||
void scrollDownClicked();
|
||||
void scrollDownAnimationFinish();
|
||||
void updateScrollDownVisibility();
|
||||
void updateScrollDownPosition();
|
||||
|
||||
void confirmSendNowSelected();
|
||||
void confirmDeleteSelected();
|
||||
void clearSelected();
|
||||
|
||||
void send();
|
||||
void send(Api::SendOptions options);
|
||||
void edit(
|
||||
not_null<HistoryItem*> item,
|
||||
Api::SendOptions options,
|
||||
mtpRequestId *const saveEditMsgRequestId);
|
||||
void highlightSingleNewMessage(const Data::MessagesSlice &slice);
|
||||
void chooseAttach();
|
||||
[[nodiscard]] SendMenu::Type sendMenuType() const;
|
||||
|
||||
void uploadFile(const QByteArray &fileContent, SendMediaType type);
|
||||
bool confirmSendingFiles(
|
||||
QImage &&image,
|
||||
QByteArray &&content,
|
||||
CompressConfirm compressed,
|
||||
const QString &insertTextOnCancel = QString());
|
||||
bool confirmSendingFiles(
|
||||
Storage::PreparedList &&list,
|
||||
CompressConfirm compressed,
|
||||
const QString &insertTextOnCancel = QString());
|
||||
bool confirmSendingFiles(
|
||||
not_null<const QMimeData*> data,
|
||||
CompressConfirm compressed,
|
||||
const QString &insertTextOnCancel = QString());
|
||||
bool showSendingFilesError(const Storage::PreparedList &list) const;
|
||||
void uploadFilesAfterConfirmation(
|
||||
Storage::PreparedList &&list,
|
||||
SendMediaType type,
|
||||
TextWithTags &&caption,
|
||||
MsgId replyTo,
|
||||
Api::SendOptions options,
|
||||
std::shared_ptr<SendingAlbum> album);
|
||||
|
||||
void sendExistingDocument(not_null<DocumentData*> document);
|
||||
bool sendExistingDocument(
|
||||
not_null<DocumentData*> document,
|
||||
Api::SendOptions options);
|
||||
void sendExistingPhoto(not_null<PhotoData*> photo);
|
||||
bool sendExistingPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
Api::SendOptions options);
|
||||
void sendInlineResult(
|
||||
not_null<InlineBots::Result*> result,
|
||||
not_null<UserData*> bot);
|
||||
void sendInlineResult(
|
||||
not_null<InlineBots::Result*> result,
|
||||
not_null<UserData*> bot,
|
||||
Api::SendOptions options);
|
||||
|
||||
const not_null<History*> _history;
|
||||
const MsgId _rootId = 0;
|
||||
std::shared_ptr<Data::RepliesList> _replies;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
QPointer<ListWidget> _inner;
|
||||
object_ptr<TopBarWidget> _topBar;
|
||||
object_ptr<Ui::PlainShadow> _topBarShadow;
|
||||
std::unique_ptr<ComposeControls> _composeControls;
|
||||
bool _skipScrollEvent = false;
|
||||
|
||||
FullMsgId _highlightMessageId;
|
||||
std::optional<Data::MessagePosition> _nextAnimatedScrollPosition;
|
||||
int _nextAnimatedScrollDelta = 0;
|
||||
|
||||
Ui::Animations::Simple _scrollDownShown;
|
||||
bool _scrollDownIsShown = false;
|
||||
object_ptr<Ui::HistoryDownButton> _scrollDown;
|
||||
|
||||
Data::MessagesSlice _lastSlice;
|
||||
bool _choosingAttach = false;
|
||||
|
||||
};
|
||||
|
||||
class RepliesMemento : public Window::SectionMemento {
|
||||
public:
|
||||
RepliesMemento(not_null<History*> history, MsgId rootId)
|
||||
: _history(history)
|
||||
, _rootId(rootId) {
|
||||
}
|
||||
|
||||
object_ptr<Window::SectionWidget> createWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
Window::Column column,
|
||||
const QRect &geometry) override;
|
||||
|
||||
[[nodiscard]] not_null<History*> getHistory() const {
|
||||
return _history;
|
||||
}
|
||||
[[nodiscard]] MsgId getRootId() const {
|
||||
return _rootId;
|
||||
}
|
||||
|
||||
void setReplies(std::shared_ptr<Data::RepliesList> replies) {
|
||||
_replies = std::move(replies);
|
||||
}
|
||||
[[nodiscard]] std::shared_ptr<Data::RepliesList> getReplies() const {
|
||||
return _replies;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<ListMemento*> list() {
|
||||
return &_list;
|
||||
}
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
const MsgId _rootId = 0;
|
||||
ListMemento _list;
|
||||
std::shared_ptr<Data::RepliesList> _replies;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
|
@ -240,4 +240,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace HistoryScheduled
|
||||
} // namespace HistoryView
|
||||
|
|
|
@ -330,9 +330,12 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
|||
const auto folder = _activeChat.folder();
|
||||
if (folder
|
||||
|| history->peer->isSelf()
|
||||
|| (_section == Section::Scheduled)) {
|
||||
|| (_section == Section::Scheduled)
|
||||
|| !_customTitleText.isEmpty()) {
|
||||
// #TODO feed name emoji.
|
||||
auto text = (_section == Section::Scheduled)
|
||||
auto text = !_customTitleText.isEmpty()
|
||||
? _customTitleText
|
||||
: (_section == Section::Scheduled)
|
||||
? ((history && history->peer->isSelf())
|
||||
? tr::lng_reminder_messages(tr::now)
|
||||
: tr::lng_scheduled_messages(tr::now))
|
||||
|
@ -497,6 +500,13 @@ void TopBarWidget::setActiveChat(Dialogs::Key chat, Section section) {
|
|||
refreshUnreadBadge();
|
||||
}
|
||||
|
||||
void TopBarWidget::setCustomTitle(const QString &title) {
|
||||
if (_customTitleText != title) {
|
||||
_customTitleText = title;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void TopBarWidget::refreshInfoButton() {
|
||||
if (const auto peer = _activeChat.peer()) {
|
||||
auto info = object_ptr<Ui::UserpicButton>(
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
enum class Section {
|
||||
History,
|
||||
Scheduled,
|
||||
Replies,
|
||||
};
|
||||
|
||||
TopBarWidget(
|
||||
|
@ -62,6 +63,7 @@ public:
|
|||
void setAnimatingMode(bool enabled);
|
||||
|
||||
void setActiveChat(Dialogs::Key chat, Section section);
|
||||
void setCustomTitle(const QString &title);
|
||||
|
||||
rpl::producer<> forwardSelectionRequest() const {
|
||||
return _forwardSelection.events();
|
||||
|
@ -125,6 +127,7 @@ private:
|
|||
const not_null<Window::SessionController*> _controller;
|
||||
Dialogs::Key _activeChat;
|
||||
Section _section = Section::History;
|
||||
QString _customTitleText;
|
||||
|
||||
int _selectedCount = 0;
|
||||
bool _canDelete = false;
|
||||
|
|
|
@ -1137,7 +1137,7 @@ bool Gif::needsBubble() const {
|
|||
}
|
||||
const auto item = _parent->data();
|
||||
return item->viaBot()
|
||||
|| item->Has<HistoryMessageReply>()
|
||||
|| _parent->displayedReply()
|
||||
|| _parent->displayForwardedFrom()
|
||||
|| _parent->displayFromName();
|
||||
return false;
|
||||
|
|
|
@ -320,7 +320,7 @@ bool Location::needsBubble() const {
|
|||
}
|
||||
const auto item = _parent->data();
|
||||
return item->viaBot()
|
||||
|| item->Has<HistoryMessageReply>()
|
||||
|| _parent->displayedReply()
|
||||
|| _parent->displayForwardedFrom()
|
||||
|| _parent->displayFromName();
|
||||
return false;
|
||||
|
|
|
@ -454,7 +454,7 @@ bool GroupedMedia::computeNeedBubble() const {
|
|||
}
|
||||
if (const auto item = _parent->data()) {
|
||||
if (item->viaBot()
|
||||
|| item->Has<HistoryMessageReply>()
|
||||
|| _parent->displayedReply()
|
||||
|| _parent->displayForwardedFrom()
|
||||
|| _parent->displayFromName()
|
||||
) {
|
||||
|
|
|
@ -802,7 +802,7 @@ bool Photo::needsBubble() const {
|
|||
const auto item = _parent->data();
|
||||
if (item->toHistoryMessage()) {
|
||||
return item->viaBot()
|
||||
|| item->Has<HistoryMessageReply>()
|
||||
|| _parent->displayedReply()
|
||||
|| _parent->displayForwardedFrom()
|
||||
|| _parent->displayFromName();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue