Support pinned messages bar in topics.

This commit is contained in:
John Preston 2022-10-28 09:19:27 +04:00
parent da1e784803
commit 8dc27339b4
40 changed files with 811 additions and 206 deletions

View file

@ -1511,9 +1511,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_topic_created_inside" = "Topic created"; "lng_action_topic_created_inside" = "Topic created";
"lng_action_topic_closed_inside" = "Topic closed"; "lng_action_topic_closed_inside" = "Topic closed";
"lng_action_topic_reopened_inside" = "Topic reopened"; "lng_action_topic_reopened_inside" = "Topic reopened";
"lng_action_topic_created" = "{topic} — was created"; "lng_action_topic_created" = "«{topic}» was created";
"lng_action_topic_closed" = "{topic} — was closed"; "lng_action_topic_closed" = "«{topic}» was closed";
"lng_action_topic_reopened" = "{topic} — was reopened"; "lng_action_topic_reopened" = "«{topic}» was reopened";
"lng_action_topic_placeholder" = "topic"; "lng_action_topic_placeholder" = "topic";
"lng_action_topic_renamed" = "{from} renamed the {link} to «{title}»"; "lng_action_topic_renamed" = "{from} renamed the {link} to «{title}»";
"lng_action_topic_icon_changed" = "{from} changed the {link} icon to {emoji}"; "lng_action_topic_icon_changed" = "{from} changed the {link} icon to {emoji}";

View file

@ -3011,6 +3011,10 @@ void ApiWrap::sharedMediaDone(
MsgId topicRootId, MsgId topicRootId,
SharedMediaType type, SharedMediaType type,
Api::SearchResult &&parsed) { Api::SearchResult &&parsed) {
const auto topic = peer->forumTopicFor(topicRootId);
if (topicRootId && !topic) {
return;
}
_session->storage().add(Storage::SharedMediaAddSlice( _session->storage().add(Storage::SharedMediaAddSlice(
peer->id, peer->id,
topicRootId, topicRootId,
@ -3021,6 +3025,9 @@ void ApiWrap::sharedMediaDone(
)); ));
if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) { if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) {
peer->owner().history(peer)->setHasPinnedMessages(true); peer->owner().history(peer)->setHasPinnedMessages(true);
if (topic) {
topic->setHasPinnedMessages(true);
}
} }
} }
@ -3451,11 +3458,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
action.generateLocal = true; action.generateLocal = true;
sendAction(action); sendAction(action);
const auto replyToId = message.action.replyTo; const auto replyToId = action.replyTo;
const auto replyTo = replyToId const auto replyTo = replyToId
? peer->owner().message(peer, replyToId) ? peer->owner().message(peer, replyToId)
: nullptr; : nullptr;
const auto topic = replyTo ? replyTo->topic() : nullptr; const auto topicRootId = replyTo ? replyTo->topicRootId() : replyToId;
const auto topic = topicRootId
? peer->forumTopicFor(topicRootId)
: nullptr;
if (!(topic ? topic->canWrite() : peer->canWrite()) if (!(topic ? topic->canWrite() : peer->canWrite())
|| Api::SendDice(message)) { || Api::SendDice(message)) {
return; return;

View file

@ -130,9 +130,8 @@ struct HistoryUpdate {
BotKeyboard = (1U << 12), BotKeyboard = (1U << 12),
CloudDraft = (1U << 13), CloudDraft = (1U << 13),
LocalDraftSet = (1U << 14), LocalDraftSet = (1U << 14),
PinnedMessages = (1U << 15),
LastUsedBit = (1U << 15), LastUsedBit = (1U << 14),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -156,7 +155,7 @@ struct TopicUpdate {
CloudDraft = (1U << 8), CloudDraft = (1U << 8),
Closed = (1U << 9), Closed = (1U << 9),
LastUsedBit = (1U << 8), LastUsedBit = (1U << 9),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }
@ -196,8 +195,9 @@ struct EntryUpdate {
None = 0, None = 0,
Repaint = (1U << 0), Repaint = (1U << 0),
HasPinnedMessages = (1U << 1),
LastUsedBit = (1U << 0), LastUsedBit = (1U << 1),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -994,7 +994,7 @@ void ApplyChannelUpdate(
} }
} }
if (const auto pinned = update.vpinned_msg_id()) { if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(channel, MsgId(0), pinned->v); SetTopPinnedMessageId(channel, pinned->v);
} }
if (channel->isMegagroup()) { if (channel->isMegagroup()) {
auto commands = ranges::views::all( auto commands = ranges::views::all(

View file

@ -475,7 +475,7 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
chat->session().api().inviteLinks().clearMyPermanent(chat); chat->session().api().inviteLinks().clearMyPermanent(chat);
} }
if (const auto pinned = update.vpinned_msg_id()) { if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(chat, MsgId(0), pinned->v); SetTopPinnedMessageId(chat, pinned->v);
} }
chat->checkFolder(update.vfolder_id().value_or_empty()); chat->checkFolder(update.vfolder_id().value_or_empty());
chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));

View file

@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h" #include "core/application.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
@ -63,6 +65,12 @@ Forum::~Forum() {
if (_requestId) { if (_requestId) {
session().api().request(_requestId).cancel(); session().api().request(_requestId).cancel();
} }
const auto peerId = _history->peer->id;
for (const auto &[rootId, topic] : _topics) {
session().storage().unload(Storage::SharedMediaUnloadThread(
peerId,
rootId));
}
} }
Session &Forum::owner() const { Session &Forum::owner() const {
@ -170,6 +178,9 @@ void Forum::applyTopicDeleted(MsgId rootId) {
_topics.erase(i); _topics.erase(i);
_history->destroyMessagesByTopic(rootId); _history->destroyMessagesByTopic(rootId);
session().storage().unload(Storage::SharedMediaUnloadThread(
_history->peer->id,
rootId));
} }
} }

View file

@ -657,9 +657,7 @@ void ForumTopic::setMuted(bool muted) {
const auto notify = state.unread || state.reaction; const auto notify = state.unread || state.reaction;
const auto notifier = unreadStateChangeNotifier(notify); const auto notifier = unreadStateChangeNotifier(notify);
Thread::setMuted(muted); Thread::setMuted(muted);
session().changes().topicUpdated( session().changes().topicUpdated(this, UpdateFlag::Notifications);
this,
Data::TopicUpdate::Flag::Notifications);
} }
not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() { not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() {

View file

@ -139,6 +139,7 @@ private:
enum class Flag : uchar { enum class Flag : uchar {
Closed = (1 << 0), Closed = (1 << 0),
My = (1 << 1), My = (1 << 1),
HasPinnedMessages = (1 << 2),
}; };
friend inline constexpr bool is_flag_type(Flag) { return true; } friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_histories.h" #include "data/data_histories.h"
@ -1255,7 +1256,6 @@ std::optional<QString> RestrictionError(
void SetTopPinnedMessageId( void SetTopPinnedMessageId(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
MsgId messageId) { MsgId messageId) {
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
if (messageId <= channel->availableMinId()) { if (messageId <= channel->availableMinId()) {
@ -1265,12 +1265,15 @@ void SetTopPinnedMessageId(
auto &session = peer->session(); auto &session = peer->session();
const auto hiddenId = session.settings().hiddenPinnedMessageId(peer->id); const auto hiddenId = session.settings().hiddenPinnedMessageId(peer->id);
if (hiddenId != 0 && hiddenId != messageId) { if (hiddenId != 0 && hiddenId != messageId) {
session.settings().setHiddenPinnedMessageId(peer->id, 0); session.settings().setHiddenPinnedMessageId(
peer->id,
MsgId(0), // topicRootId
0);
session.saveSettingsDelayed(); session.saveSettingsDelayed();
} }
session.storage().add(Storage::SharedMediaAddExisting( session.storage().add(Storage::SharedMediaAddExisting(
peer->id, peer->id,
topicRootId, MsgId(0), // topicRootId
Storage::SharedMediaType::Pinned, Storage::SharedMediaType::Pinned,
messageId, messageId,
{ messageId, ServerMaxMsgId })); { messageId, ServerMaxMsgId }));

View file

@ -482,15 +482,14 @@ std::optional<QString> RestrictionError(
void SetTopPinnedMessageId( void SetTopPinnedMessageId(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
MsgId messageId); MsgId messageId);
[[nodiscard]] FullMsgId ResolveTopPinnedId( [[nodiscard]] FullMsgId ResolveTopPinnedId(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId, MsgId topicRootId,
PeerData *migrated); PeerData *migrated = nullptr);
[[nodiscard]] FullMsgId ResolveMinPinnedId( [[nodiscard]] FullMsgId ResolveMinPinnedId(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId, MsgId topicRootId,
PeerData *migrated); PeerData *migrated = nullptr);
} // namespace Data } // namespace Data

View file

@ -8,15 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_thread.h" #include "data/data_thread.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_changes.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "main/main_session.h"
namespace Data { namespace Data {
Thread::~Thread() = default; Thread::~Thread() = default;
not_null<Thread*> Thread::migrateToOrMe() const {
const auto history = asHistory();
return history ? history->migrateToOrMe() : const_cast<Thread*>(this);
}
MsgId Thread::topicRootId() const { MsgId Thread::topicRootId() const {
if (const auto topic = asTopic()) { if (const auto topic = asTopic()) {
return topic->rootId(); return topic->rootId();
@ -157,4 +164,21 @@ void Thread::setUnreadMarkFlag(bool unread) {
} }
} }
[[nodiscard]] bool Thread::hasPinnedMessages() const {
return (_flags & Flag::HasPinnedMessages);
}
void Thread::setHasPinnedMessages(bool has) {
if (hasPinnedMessages() == has) {
return;
} else if (has) {
_flags |= Flag::HasPinnedMessages;
} else {
_flags &= ~Flag::HasPinnedMessages;
}
session().changes().entryUpdated(
this,
EntryUpdate::Flag::HasPinnedMessages);
}
} // namespace Data } // namespace Data

View file

@ -58,6 +58,7 @@ public:
[[nodiscard]] virtual not_null<History*> owningHistory() = 0; [[nodiscard]] virtual not_null<History*> owningHistory() = 0;
[[nodiscard]] not_null<Thread*> migrateToOrMe() const;
[[nodiscard]] not_null<const History*> owningHistory() const { [[nodiscard]] not_null<const History*> owningHistory() const {
return const_cast<Thread*>(this)->owningHistory(); return const_cast<Thread*>(this)->owningHistory();
} }
@ -110,6 +111,9 @@ public:
[[nodiscard]] virtual auto sendActionPainter() [[nodiscard]] virtual auto sendActionPainter()
-> not_null<HistoryView::SendActionPainter*> = 0; -> not_null<HistoryView::SendActionPainter*> = 0;
[[nodiscard]] bool hasPinnedMessages() const;
void setHasPinnedMessages(bool has);
protected: protected:
void setUnreadMarkFlag(bool unread); void setUnreadMarkFlag(bool unread);
@ -118,6 +122,7 @@ private:
UnreadMark = (1 << 0), UnreadMark = (1 << 0),
Muted = (1 << 1), Muted = (1 << 1),
UnreadThingsKnown = (1 << 2), UnreadThingsKnown = (1 << 2),
HasPinnedMessages = (1 << 3),
}; };
friend inline constexpr bool is_flag_type(Flag) { return true; } friend inline constexpr bool is_flag_type(Flag) { return true; }

View file

@ -363,7 +363,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->setBotInfoVersion(-1); user->setBotInfoVersion(-1);
} }
if (const auto pinned = update.vpinned_msg_id()) { if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, MsgId(0), pinned->v); SetTopPinnedMessageId(user, pinned->v);
} }
const auto canReceiveGifts = (update.vflags().v const auto canReceiveGifts = (update.vflags().v
& MTPDuserFull::Flag::f_premium_gifts) & MTPDuserFull::Flag::f_premium_gifts)

View file

@ -508,16 +508,25 @@ void History::destroyMessagesByTopic(MsgId topicRootId) {
} }
} }
void History::unpinAllMessages() { void History::unpinMessagesFor(MsgId topicRootId) {
session().storage().remove( if (!topicRootId) {
Storage::SharedMediaRemoveAll( session().storage().remove(
peer->id, Storage::SharedMediaRemoveAll(
Storage::SharedMediaType::Pinned)); peer->id,
setHasPinnedMessages(false); Storage::SharedMediaType::Pinned));
for (const auto &message : _messages) { setHasPinnedMessages(false);
if (message->isPinned()) { if (const auto forum = peer->forum()) {
message->setIsPinned(false); forum->enumerateTopics([](not_null<Data::ForumTopic*> topic) {
topic->setHasPinnedMessages(false);
});
} }
for (const auto &message : _messages) {
if (message->isPinned()) {
message->setIsPinned(false);
}
}
} else {
// #TODO forum pinned
} }
} }
@ -792,15 +801,29 @@ not_null<HistoryItem*> History::addNewToBack(
if (const auto sharedMediaTypes = item->sharedMediaTypes()) { if (const auto sharedMediaTypes = item->sharedMediaTypes()) {
auto from = loadedAtTop() ? 0 : minMsgId(); auto from = loadedAtTop() ? 0 : minMsgId();
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId(); auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
session().storage().add(Storage::SharedMediaAddExisting( auto &storage = session().storage();
storage.add(Storage::SharedMediaAddExisting(
peer->id, peer->id,
MsgId(0), MsgId(0), // topicRootId
sharedMediaTypes, sharedMediaTypes,
item->id, item->id,
{ from, till })); { from, till }));
if (sharedMediaTypes.test(Storage::SharedMediaType::Pinned)) { const auto pinned = sharedMediaTypes.test(
Storage::SharedMediaType::Pinned);
if (pinned) {
setHasPinnedMessages(true); setHasPinnedMessages(true);
} }
if (const auto topic = item->topic()) {
storage.add(Storage::SharedMediaAddExisting(
peer->id,
topic->rootId(),
sharedMediaTypes,
item->id,
{ item->id, item->id}));
if (pinned) {
topic->setHasPinnedMessages(true);
}
}
} }
} }
if (item->from()->id) { if (item->from()->id) {
@ -1064,11 +1087,20 @@ void History::applyServiceChanges(
if (item) { if (item) {
session().storage().add(Storage::SharedMediaAddSlice( session().storage().add(Storage::SharedMediaAddSlice(
peer->id, peer->id,
MsgId(0), // topicRootId MsgId(0),
Storage::SharedMediaType::Pinned, Storage::SharedMediaType::Pinned,
{ id }, { id },
{ id, ServerMaxMsgId })); { id, ServerMaxMsgId }));
setHasPinnedMessages(true); setHasPinnedMessages(true);
if (const auto topic = item->topic()) {
session().storage().add(Storage::SharedMediaAddSlice(
peer->id,
topic->rootId(),
Storage::SharedMediaType::Pinned,
{ id },
{ id, ServerMaxMsgId }));
topic->setHasPinnedMessages(true);
}
} }
}); });
} }
@ -1464,6 +1496,7 @@ void History::checkAddAllToUnreadMentions() {
void History::addToSharedMedia( void History::addToSharedMedia(
const std::vector<not_null<HistoryItem*>> &items) { const std::vector<not_null<HistoryItem*>> &items) {
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount]; std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
auto topicsWithPinned = base::flat_set<not_null<Data::ForumTopic*>>();
for (const auto &item : items) { for (const auto &item : items) {
if (const auto types = item->sharedMediaTypes()) { if (const auto types = item->sharedMediaTypes()) {
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) { for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
@ -1473,6 +1506,13 @@ void History::addToSharedMedia(
medias[i].reserve(items.size()); medias[i].reserve(items.size());
} }
medias[i].push_back(item->id); medias[i].push_back(item->id);
if (type == Storage::SharedMediaType::Pinned) {
if (const auto topic = item->topic()) {
if (!topic->hasPinnedMessages()) {
topicsWithPinned.emplace(topic);
}
}
}
} }
} }
} }
@ -1493,6 +1533,9 @@ void History::addToSharedMedia(
} }
} }
} }
for (const auto &topic : topicsWithPinned) {
topic->setHasPinnedMessages(true);
}
} }
void History::calculateFirstUnreadMessage() { void History::calculateFirstUnreadMessage() {
@ -1750,7 +1793,7 @@ void History::setUnreadMark(bool unread) {
} }
void History::setFakeUnreadWhileOpened(bool enabled) { void History::setFakeUnreadWhileOpened(bool enabled) {
if (_fakeUnreadWhileOpened == enabled) { if (fakeUnreadWhileOpened() == enabled) {
return; return;
} else if (enabled) { } else if (enabled) {
if (!inChatList()) { if (!inChatList()) {
@ -1761,12 +1804,16 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
return; return;
} }
} }
_fakeUnreadWhileOpened = enabled; if (enabled) {
_flags |= Flag::FakeUnreadWhileOpened;
} else {
_flags &= ~Flag::FakeUnreadWhileOpened;
}
owner().chatsFilters().refreshHistory(this); owner().chatsFilters().refreshHistory(this);
} }
[[nodiscard]] bool History::fakeUnreadWhileOpened() const { [[nodiscard]] bool History::fakeUnreadWhileOpened() const {
return _fakeUnreadWhileOpened; return (_flags & Flag::FakeUnreadWhileOpened);
} }
void History::setMuted(bool muted) { void History::setMuted(bool muted) {
@ -3316,15 +3363,6 @@ void History::removeBlock(not_null<HistoryBlock*> block) {
} }
} }
bool History::hasPinnedMessages() const {
return _hasPinnedMessages;
}
void History::setHasPinnedMessages(bool has) {
_hasPinnedMessages = has;
session().changes().historyUpdated(this, UpdateFlag::PinnedMessages);
}
void History::cacheTopPromoted(bool promoted) { void History::cacheTopPromoted(bool promoted) {
if (isTopPromoted() == promoted) { if (isTopPromoted() == promoted) {
return; return;

View file

@ -140,7 +140,7 @@ public:
void destroyMessagesByDates(TimeId minDate, TimeId maxDate); void destroyMessagesByDates(TimeId minDate, TimeId maxDate);
void destroyMessagesByTopic(MsgId topicRootId); void destroyMessagesByTopic(MsgId topicRootId);
void unpinAllMessages(); void unpinMessagesFor(MsgId topicRootId);
not_null<HistoryItem*> addNewMessage( not_null<HistoryItem*> addNewMessage(
MsgId id, MsgId id,
@ -426,9 +426,6 @@ public:
void setInboxReadTill(MsgId upTo); void setInboxReadTill(MsgId upTo);
std::optional<int> countStillUnreadLocal(MsgId readTillId) const; std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
[[nodiscard]] bool hasPinnedMessages() const;
void setHasPinnedMessages(bool has);
[[nodiscard]] bool isTopPromoted() const; [[nodiscard]] bool isTopPromoted() const;
const not_null<PeerData*> peer; const not_null<PeerData*> peer;
@ -461,6 +458,8 @@ private:
HasPendingResizedItems = (1 << 0), HasPendingResizedItems = (1 << 0),
IsTopPromoted = (1 << 1), IsTopPromoted = (1 << 1),
IsForum = (1 << 2), IsForum = (1 << 2),
FakeUnreadWhileOpened = (1 << 3),
HasPinnedMessages = (1 << 4),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { friend inline constexpr auto is_flag_type(Flag) {
@ -610,9 +609,6 @@ private:
QString _chatListNameSortKey; QString _chatListNameSortKey;
bool _fakeUnreadWhileOpened = false;
bool _hasPinnedMessages = false;
// A pointer to the block that is currently being built. // A pointer to the block that is currently being built.
// We hold this pointer so we can destroy it while building // We hold this pointer so we can destroy it while building
// and then create a new one if it is necessary. // and then create a new one if it is necessary.

View file

@ -480,13 +480,23 @@ void HistoryItem::setIsPinned(bool pinned) {
const auto changed = (isPinned() != pinned); const auto changed = (isPinned() != pinned);
if (pinned) { if (pinned) {
_flags |= MessageFlag::Pinned; _flags |= MessageFlag::Pinned;
history()->session().storage().add(Storage::SharedMediaAddExisting( auto &storage = history()->session().storage();
storage.add(Storage::SharedMediaAddExisting(
history()->peer->id, history()->peer->id,
MsgId(0), // topicRootId MsgId(0), // topicRootId
Storage::SharedMediaType::Pinned, Storage::SharedMediaType::Pinned,
id, id,
{ id, id })); { id, id }));
history()->setHasPinnedMessages(true); history()->setHasPinnedMessages(true);
if (const auto topic = this->topic()) {
storage.add(Storage::SharedMediaAddExisting(
history()->peer->id,
topic->rootId(),
Storage::SharedMediaType::Pinned,
id,
{ id, id }));
topic->setHasPinnedMessages(true);
}
} else { } else {
_flags &= ~MessageFlag::Pinned; _flags &= ~MessageFlag::Pinned;
history()->session().storage().remove(Storage::SharedMediaRemoveOne( history()->session().storage().remove(Storage::SharedMediaRemoveOne(
@ -734,6 +744,9 @@ void HistoryItem::indexAsNewItem() {
id)); id));
if (types.test(Storage::SharedMediaType::Pinned)) { if (types.test(Storage::SharedMediaType::Pinned)) {
_history->setHasPinnedMessages(true); _history->setHasPinnedMessages(true);
if (const auto topic = this->topic()) {
topic->setHasPinnedMessages(true);
}
} }
} }
} }
@ -1423,7 +1436,12 @@ ClickHandlerPtr goToMessageClickHandler(
params.origin = Window::SectionShow::OriginMessage{ params.origin = Window::SectionShow::OriginMessage{
returnToId returnToId
}; };
controller->showPeerHistory(peer, params, msgId); const auto item = peer->owner().message(peer, msgId);
if (const auto topic = item ? item->topic() : nullptr) {
controller->showTopic(topic, msgId, params);
} else {
controller->showPeerHistory(peer, params, msgId);
}
} }
}); });
} }

View file

@ -859,7 +859,8 @@ bool HistoryService::updateDependent(bool force) {
(dependent->peerId (dependent->peerId
? history()->owner().peer(dependent->peerId) ? history()->owner().peer(dependent->peerId)
: history()->peer), : history()->peer),
dependent->msgId); dependent->msgId,
fullId());
} }
auto gotDependencyItem = false; auto gotDependencyItem = false;
if (!dependent->msg) { if (!dependent->msg) {

View file

@ -73,6 +73,14 @@ float64 ElementHighlighter::progress(
void ElementHighlighter::highlight(FullMsgId itemId) { void ElementHighlighter::highlight(FullMsgId itemId) {
if (const auto item = _data->message(itemId)) { if (const auto item = _data->message(itemId)) {
if (const auto view = _viewForItem(item)) { if (const auto view = _viewForItem(item)) {
if (_highlightedMessageId
&& _highlightedMessageId != itemId) {
if (const auto was = _data->message(_highlightedMessageId)) {
if (const auto view = _viewForItem(was)) {
repaintHighlightedItem(view);
}
}
}
_highlightedMessageId = itemId; _highlightedMessageId = itemId;
_animation.start(); _animation.start();

View file

@ -603,6 +603,18 @@ HistoryWidget::HistoryWidget(
} }
}, lifetime()); }, lifetime());
using EntryUpdateFlag = Data::EntryUpdate::Flag;
session().changes().entryUpdates(
EntryUpdateFlag::HasPinnedMessages
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
if (_pinnedTracker
&& (update.flags & EntryUpdateFlag::HasPinnedMessages)
&& (_migrated && update.entry.get() == _migrated)
|| (update.entry.get() == _history)) {
checkPinnedBarState();
}
}, lifetime());
using HistoryUpdateFlag = Data::HistoryUpdate::Flag; using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
session().changes().historyUpdates( session().changes().historyUpdates(
HistoryUpdateFlag::MessageSent HistoryUpdateFlag::MessageSent
@ -614,14 +626,7 @@ HistoryWidget::HistoryWidget(
| HistoryUpdateFlag::UnreadView | HistoryUpdateFlag::UnreadView
| HistoryUpdateFlag::TopPromoted | HistoryUpdateFlag::TopPromoted
| HistoryUpdateFlag::ClientSideMessages | HistoryUpdateFlag::ClientSideMessages
| HistoryUpdateFlag::PinnedMessages
) | rpl::filter([=](const Data::HistoryUpdate &update) { ) | rpl::filter([=](const Data::HistoryUpdate &update) {
if (_migrated && update.history.get() == _migrated) {
if (_pinnedTracker
&& (update.flags & HistoryUpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
}
return (_history == update.history.get()); return (_history == update.history.get());
}) | rpl::start_with_next([=](const Data::HistoryUpdate &update) { }) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
const auto flags = update.flags; const auto flags = update.flags;
@ -647,9 +652,6 @@ HistoryWidget::HistoryWidget(
if (flags & HistoryUpdateFlag::UnreadView) { if (flags & HistoryUpdateFlag::UnreadView) {
unreadCountUpdated(); unreadCountUpdated();
} }
if (_pinnedTracker && (flags & HistoryUpdateFlag::PinnedMessages)) {
checkPinnedBarState();
}
if (flags & HistoryUpdateFlag::TopPromoted) { if (flags & HistoryUpdateFlag::TopPromoted) {
updateHistoryGeometry(); updateHistoryGeometry();
updateControlsVisibility(); updateControlsVisibility();
@ -3365,15 +3367,16 @@ void HistoryWidget::handleScroll() {
if (!_itemsRevealHeight) { if (!_itemsRevealHeight) {
updatePinnedViewer(); updatePinnedViewer();
} }
const auto now = crl::now();
if (!_synteticScrollEvent) { if (!_synteticScrollEvent) {
_lastUserScrolled = crl::now(); _lastUserScrolled = now;
} }
const auto scrollTop = _scroll->scrollTop(); const auto scrollTop = _scroll->scrollTop();
if (scrollTop != _lastScrollTop) { if (scrollTop != _lastScrollTop) {
if (!_synteticScrollEvent) { if (!_synteticScrollEvent) {
checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop); checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop);
} }
_lastScrolled = crl::now(); _lastScrolled = now;
_lastScrollTop = scrollTop; _lastScrollTop = scrollTop;
} }
} }
@ -6892,6 +6895,7 @@ void HistoryWidget::hidePinnedMessage() {
Window::HidePinnedBar( Window::HidePinnedBar(
controller(), controller(),
_peer, _peer,
MsgId(0), // topicRootId
crl::guard(this, callback)); crl::guard(this, callback));
} }
} }
@ -7765,20 +7769,6 @@ void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int
} }
} }
//
//void HistoryWidget::drawPinnedBar(Painter &p) {
// //if (_pinnedBar->msg) {
// // const auto media = _pinnedBar->msg->media();
// // if (media && media->hasReplyPreview()) {
// // if (const auto image = media->replyPreview()) {
// // QRect to(left, top, st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
// // p.drawPixmap(to.x(), to.y(), image->pixSingle(image->width() / cIntRetinaFactor(), image->height() / cIntRetinaFactor(), to.width(), to.height(), ImageRoundRadius::Small));
// // }
// // left += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
// // }
// //}
//}
bool HistoryWidget::paintShowAnimationFrame() { bool HistoryWidget::paintShowAnimationFrame() {
auto progress = _a_show.value(1.); auto progress = _a_show.value(1.);
if (!_a_show.animating()) { if (!_a_show.animating()) {

View file

@ -680,11 +680,15 @@ bool AddPinMessageAction(
not_null<ListWidget*> list) { not_null<ListWidget*> list) {
const auto context = list->elementContext(); const auto context = list->elementContext();
const auto item = request.item; const auto item = request.item;
if (!item if (!item || !item->isRegular()) {
|| !item->isRegular()
|| (context != Context::History && context != Context::Pinned)) {
return false; return false;
} }
const auto topic = item->topic();
if (context != Context::History && context != Context::Pinned) {
if (context != Context::Replies || !topic) {
return false;
}
}
const auto group = item->history()->owner().groups().find(item); const auto group = item->history()->owner().groups().find(item);
const auto pinItem = ((item->canPin() && item->isPinned()) || !group) const auto pinItem = ((item->canPin() && item->isPinned()) || !group)
? item ? item

View file

@ -411,7 +411,7 @@ ListWidget::ListWidget(
_selectScroll.scrolls( _selectScroll.scrolls(
) | rpl::start_with_next([=](int d) { ) | rpl::start_with_next([=](int d) {
delegate->listScrollTo(_visibleTop + d); delegate->listScrollTo(_visibleTop + d, false);
}, lifetime()); }, lifetime());
} }
@ -1378,6 +1378,37 @@ bool ListWidget::hasSelectRestriction() const {
!= CopyRestrictionType::None; != CopyRestrictionType::None;
} }
auto ListWidget::findViewForPinnedTracking(int top) const
-> std::pair<Element*, int> {
const auto findScrollTopItem = [&](int top)
-> std::vector<not_null<Element*>>::const_iterator {
if (!width() || _items.empty()) {
return end(_items);
}
const auto first = ranges::lower_bound(
_items,
top,
std::less<>(),
&Element::y);
return (first == end(_items) || (*first)->y() > top)
? first - 1
: first;
};
const auto findView = [&](int top)
-> std::pair<std::vector<not_null<Element*>>::const_iterator, int> {
if (const auto i = findScrollTopItem(top); i != end(_items)) {
return { i, top - (*i)->y() };
}
return { end(_items), 0 };
};
auto [view, offset] = findView(top);
while (view != end(_items) && !(*view)->data()->isRegular()) {
offset -= (*view)->height();
++view;
}
return { (view != end(_items)) ? view->get() : nullptr, offset };
}
int ListWidget::itemMinimalHeight() const { int ListWidget::itemMinimalHeight() const {
return st::msgMarginTopAttached return st::msgMarginTopAttached
+ st::msgPhotoSize + st::msgPhotoSize
@ -2712,7 +2743,9 @@ void ListWidget::mouseReleaseEvent(QMouseEvent *e) {
void ListWidget::touchScrollUpdated(const QPoint &screenPos) { void ListWidget::touchScrollUpdated(const QPoint &screenPos) {
_touchPos = screenPos; _touchPos = screenPos;
_delegate->listScrollTo(_visibleTop - (_touchPos - _touchPrevPos).y()); _delegate->listScrollTo(
_visibleTop - (_touchPos - _touchPrevPos).y(),
false);
touchUpdateSpeed(); touchUpdateSpeed();
} }
@ -3083,15 +3116,8 @@ void ListWidget::mouseActionFinish(
mouseActionCancel(); mouseActionCancel();
ActivateClickHandler(window(), activated, { ActivateClickHandler(window(), activated, {
button, button,
QVariant::fromValue(ClickHandlerContext{ QVariant::fromValue(
.itemId = pressState.itemId, prepareClickHandlerContext(pressState.itemId))
.elementDelegate = [weak = Ui::MakeWeak(this)] {
return weak
? (ElementDelegate*)weak
: nullptr;
},
.sessionWindow = base::make_weak(_controller),
})
}); });
return; return;
} }
@ -3139,6 +3165,18 @@ void ListWidget::mouseActionFinish(
} }
} }
ClickHandlerContext ListWidget::prepareClickHandlerContext(FullMsgId id) {
return {
.itemId = id,
.elementDelegate = [weak = Ui::MakeWeak(this)] {
return weak
? (ElementDelegate*)weak
: nullptr;
},
.sessionWindow = base::make_weak(_controller),
};
}
void ListWidget::mouseActionUpdate() { void ListWidget::mouseActionUpdate() {
auto mousePosition = mapFromGlobal(_mousePosition); auto mousePosition = mapFromGlobal(_mousePosition);
auto point = QPoint( auto point = QPoint(

View file

@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_view_highlight_manager.h" #include "history/history_view_highlight_manager.h"
#include "history/history_view_top_toast.h" #include "history/history_view_top_toast.h"
struct ClickHandlerContext;
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -87,7 +89,7 @@ using SelectedItems = std::vector<SelectedItem>;
class ListDelegate { class ListDelegate {
public: public:
virtual Context listContext() = 0; virtual Context listContext() = 0;
virtual bool listScrollTo(int top) = 0; // true if scroll was changed. virtual bool listScrollTo(int top, bool syntetic = true) = 0;
virtual void listCancelRequest() = 0; virtual void listCancelRequest() = 0;
virtual void listDeleteRequest() = 0; virtual void listDeleteRequest() = 0;
virtual rpl::producer<Data::MessagesSlice> listSource( virtual rpl::producer<Data::MessagesSlice> listSource(
@ -246,6 +248,11 @@ public:
[[nodiscard]] bool showCopyRestrictionForSelected(); [[nodiscard]] bool showCopyRestrictionForSelected();
[[nodiscard]] bool hasSelectRestriction() const; [[nodiscard]] bool hasSelectRestriction() const;
[[nodiscard]] std::pair<Element*, int> findViewForPinnedTracking(
int top) const;
[[nodiscard]] ClickHandlerContext prepareClickHandlerContext(
FullMsgId id);
// AbstractTooltipShower interface // AbstractTooltipShower interface
QString tooltipText() const override; QString tooltipText() const override;
QPoint tooltipPos() const override; QPoint tooltipPos() const override;

View file

@ -59,12 +59,12 @@ namespace {
} // namespace } // namespace
PinnedMemento::PinnedMemento( PinnedMemento::PinnedMemento(
not_null<History*> history, not_null<Data::Thread*> thread,
UniversalMsgId highlightId) UniversalMsgId highlightId)
: _history(history) : _thread(thread)
, _highlightId(highlightId) { , _highlightId(highlightId) {
_list.setAroundPosition({ _list.setAroundPosition({
.fullId = FullMsgId(history->peer->id, highlightId), .fullId = FullMsgId(_thread->owningHistory()->peer->id, highlightId),
.date = TimeId(0), .date = TimeId(0),
}); });
} }
@ -80,7 +80,7 @@ object_ptr<Window::SectionWidget> PinnedMemento::createWidget(
auto result = object_ptr<PinnedWidget>( auto result = object_ptr<PinnedWidget>(
parent, parent,
controller, controller,
_history); _thread);
result->setInternalState(geometry, this); result->setInternalState(geometry, this);
return result; return result;
} }
@ -88,10 +88,13 @@ object_ptr<Window::SectionWidget> PinnedMemento::createWidget(
PinnedWidget::PinnedWidget( PinnedWidget::PinnedWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history) not_null<Data::Thread*> thread)
: Window::SectionWidget(parent, controller, history->peer) : Window::SectionWidget(parent, controller, thread->owningHistory()->peer)
, _history(history->migrateToOrMe()) , _thread(thread->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom()) , _history(thread->owningHistory())
, _migratedPeer(thread->asHistory()
? thread->asHistory()->peer->migrateFrom()
: nullptr)
, _topBar(this, controller) , _topBar(this, controller)
, _topBarShadow(this) , _topBarShadow(this)
, _scroll(std::make_unique<Ui::ScrollArea>( , _scroll(std::make_unique<Ui::ScrollArea>(
@ -113,7 +116,7 @@ PinnedWidget::PinnedWidget(
Window::ChatThemeValueFromPeer( Window::ChatThemeValueFromPeer(
controller, controller,
history->peer thread->owningHistory()->peer
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) { ) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
_theme = std::move(theme); _theme = std::move(theme);
controller->setChatStyleTheme(_theme); controller->setChatStyleTheme(_theme);
@ -121,7 +124,7 @@ PinnedWidget::PinnedWidget(
_topBar->setActiveChat( _topBar->setActiveChat(
TopBarWidget::ActiveChat{ TopBarWidget::ActiveChat{
.key = _history, .key = _thread,
.section = Dialogs::EntryState::Section::Pinned, .section = Dialogs::EntryState::Section::Pinned,
}, },
nullptr); nullptr);
@ -181,9 +184,10 @@ void PinnedWidget::setupClearButton() {
Window::HidePinnedBar( Window::HidePinnedBar(
controller(), controller(),
_history->peer, _history->peer,
_thread->topicRootId(),
crl::guard(this, callback)); crl::guard(this, callback));
} else { } else {
Window::UnpinAllMessages(controller(), _history); Window::UnpinAllMessages(controller(), _thread);
} }
}); });
} }
@ -194,7 +198,7 @@ void PinnedWidget::cornerButtonsShowAtPosition(
} }
Data::Thread *PinnedWidget::cornerButtonsThread() { Data::Thread *PinnedWidget::cornerButtonsThread() {
return _history; return _thread;
} }
FullMsgId PinnedWidget::cornerButtonsCurrentId() { FullMsgId PinnedWidget::cornerButtonsCurrentId() {
@ -238,13 +242,13 @@ void PinnedWidget::updateAdaptiveLayout() {
_topBar->height()); _topBar->height());
} }
not_null<History*> PinnedWidget::history() const { not_null<Data::Thread*> PinnedWidget::thread() const {
return _history; return _thread;
} }
Dialogs::RowDescriptor PinnedWidget::activeChat() const { Dialogs::RowDescriptor PinnedWidget::activeChat() const {
return { return {
_history, _thread,
FullMsgId(_history->peer->id, ShowAtUnreadMsgId) FullMsgId(_history->peer->id, ShowAtUnreadMsgId)
}; };
} }
@ -265,8 +269,8 @@ bool PinnedWidget::showInternal(
not_null<Window::SectionMemento*> memento, not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) { const Window::SectionShow &params) {
if (auto logMemento = dynamic_cast<PinnedMemento*>(memento.get())) { if (auto logMemento = dynamic_cast<PinnedMemento*>(memento.get())) {
if (logMemento->getHistory() == history() if (logMemento->getThread() == thread()
|| logMemento->getHistory()->migrateToOrMe() == history()) { || logMemento->getThread()->migrateToOrMe() == thread()) {
restoreState(logMemento); restoreState(logMemento);
return true; return true;
} }
@ -283,7 +287,7 @@ void PinnedWidget::setInternalState(
} }
std::shared_ptr<Window::SectionMemento> PinnedWidget::createMemento() { std::shared_ptr<Window::SectionMemento> PinnedWidget::createMemento() {
auto result = std::make_shared<PinnedMemento>(history()); auto result = std::make_shared<PinnedMemento>(thread());
saveState(result.get()); saveState(result.get());
return result; return result;
} }
@ -431,7 +435,7 @@ Context PinnedWidget::listContext() {
return Context::Pinned; return Context::Pinned;
} }
bool PinnedWidget::listScrollTo(int top) { bool PinnedWidget::listScrollTo(int top, bool syntetic) {
top = std::clamp(top, 0, _scroll->scrollTopMax()); top = std::clamp(top, 0, _scroll->scrollTopMax());
if (_scroll->scrollTop() == top) { if (_scroll->scrollTop() == top) {
updateInnerVisibleArea(); updateInnerVisibleArea();
@ -462,11 +466,11 @@ rpl::producer<Data::MessagesSlice> PinnedWidget::listSource(
: (ServerMaxMsgId - 1); : (ServerMaxMsgId - 1);
return SharedMediaMergedViewer( return SharedMediaMergedViewer(
&_history->session(), &_thread->session(),
SharedMediaMergedKey( SharedMediaMergedKey(
SparseIdsMergedSlice::Key( SparseIdsMergedSlice::Key(
_history->peer->id, _history->peer->id,
MsgId(0), // topicRootId _thread->topicRootId(),
_migratedPeer ? _migratedPeer->id : 0, _migratedPeer ? _migratedPeer->id : 0,
messageId), messageId),
Storage::SharedMediaType::Pinned), Storage::SharedMediaType::Pinned),

View file

@ -42,10 +42,10 @@ public:
PinnedWidget( PinnedWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<History*> history); not_null<Data::Thread*> thread);
~PinnedWidget(); ~PinnedWidget();
[[nodiscard]] not_null<History*> history() const; [[nodiscard]] not_null<Data::Thread*> thread() const;
Dialogs::RowDescriptor activeChat() const override; Dialogs::RowDescriptor activeChat() const override;
bool hasTopBarShadow() const override { bool hasTopBarShadow() const override {
@ -79,7 +79,7 @@ public:
// ListDelegate interface. // ListDelegate interface.
Context listContext() override; Context listContext() override;
bool listScrollTo(int top) override; bool listScrollTo(int top, bool syntetic = true) override;
void listCancelRequest() override; void listCancelRequest() override;
void listDeleteRequest() override; void listDeleteRequest() override;
rpl::producer<Data::MessagesSlice> listSource( rpl::producer<Data::MessagesSlice> listSource(
@ -165,6 +165,7 @@ private:
void setMessagesCount(int count); void setMessagesCount(int count);
void refreshClearButtonText(); void refreshClearButtonText();
const not_null<Data::Thread*> _thread;
const not_null<History*> _history; const not_null<History*> _history;
std::shared_ptr<Ui::ChatTheme> _theme; std::shared_ptr<Ui::ChatTheme> _theme;
PeerData *_migratedPeer = nullptr; PeerData *_migratedPeer = nullptr;
@ -187,7 +188,7 @@ public:
using UniversalMsgId = MsgId; using UniversalMsgId = MsgId;
explicit PinnedMemento( explicit PinnedMemento(
not_null<History*> history, not_null<Data::Thread*> thread,
UniversalMsgId highlightId = 0); UniversalMsgId highlightId = 0);
object_ptr<Window::SectionWidget> createWidget( object_ptr<Window::SectionWidget> createWidget(
@ -196,8 +197,8 @@ public:
Window::Column column, Window::Column column,
const QRect &geometry) override; const QRect &geometry) override;
[[nodiscard]] not_null<History*> getHistory() const { [[nodiscard]] not_null<Data::Thread*> getThread() const {
return _history; return _thread;
} }
[[nodiscard]] not_null<ListMemento*> list() { [[nodiscard]] not_null<ListMemento*> list() {
@ -208,7 +209,7 @@ public:
} }
private: private:
const not_null<History*> _history; const not_null<Data::Thread*> _thread;
const UniversalMsgId _highlightId = 0; const UniversalMsgId _highlightId = 0;
ListMemento _list; ListMemento _list;

View file

@ -27,24 +27,26 @@ constexpr auto kChangeViewerLimit = 2;
} // namespace } // namespace
PinnedTracker::PinnedTracker(not_null<History*> history) PinnedTracker::PinnedTracker(not_null<Data::Thread*> thread)
: _history(history->migrateToOrMe()) : _thread(thread->migrateToOrMe())
, _migratedPeer(_history->peer->migrateFrom()) { , _migratedPeer(_thread->asHistory()
? _thread->asHistory()->peer->migrateFrom()
: nullptr) {
using namespace rpl::mappers; using namespace rpl::mappers;
const auto has = [&](History *history) -> rpl::producer<bool> { const auto has = [&](Data::Thread *thread) -> rpl::producer<bool> {
auto &changes = _history->session().changes(); auto &changes = _thread->session().changes();
const auto flag = Data::HistoryUpdate::Flag::PinnedMessages; const auto flag = Data::EntryUpdate::Flag::HasPinnedMessages;
if (!history) { if (!thread) {
return rpl::single(false); return rpl::single(false);
} }
return changes.historyFlagsValue(history, flag) | rpl::map([=] { return changes.entryFlagsValue(thread, flag) | rpl::map([=] {
return history->hasPinnedMessages(); return thread->hasPinnedMessages();
}); });
}; };
rpl::combine( rpl::combine(
has(_history), has(_thread),
has(_migratedPeer has(_migratedPeer
? _history->owner().history(_migratedPeer).get() ? _thread->owner().history(_migratedPeer).get()
: nullptr), : nullptr),
_1 || _2 _1 || _2
) | rpl::distinct_until_changed( ) | rpl::distinct_until_changed(
@ -77,12 +79,13 @@ void PinnedTracker::refreshViewer() {
} }
_dataLifetime.destroy(); _dataLifetime.destroy();
_viewerAroundId = _aroundId; _viewerAroundId = _aroundId;
const auto peer = _thread->owningHistory()->peer;
SharedMediaMergedViewer( SharedMediaMergedViewer(
&_history->peer->session(), &peer->session(),
SharedMediaMergedKey( SharedMediaMergedKey(
SparseIdsMergedSlice::Key( SparseIdsMergedSlice::Key(
_history->peer->id, peer->id,
MsgId(0), // topicRootId _thread->topicRootId(),
_migratedPeer ? _migratedPeer->id : 0, _migratedPeer ? _migratedPeer->id : 0,
_viewerAroundId), _viewerAroundId),
Storage::SharedMediaType::Pinned), Storage::SharedMediaType::Pinned),
@ -100,9 +103,9 @@ void PinnedTracker::refreshViewer() {
} }
refreshCurrentFromSlice(); refreshCurrentFromSlice();
if (_slice.fullCount == 0) { if (_slice.fullCount == 0) {
_history->setHasPinnedMessages(false); _thread->setHasPinnedMessages(false);
if (_migratedPeer) { if (_migratedPeer) {
const auto to = _history->owner().history(_migratedPeer); const auto to = _thread->owner().history(_migratedPeer);
to->setHasPinnedMessages(false); to->setHasPinnedMessages(false);
} }
} }

View file

@ -13,6 +13,7 @@ class History;
namespace Data { namespace Data {
enum class LoadDirection : char; enum class LoadDirection : char;
class Thread;
} // namespace Data } // namespace Data
namespace HistoryView { namespace HistoryView {
@ -21,7 +22,7 @@ class PinnedTracker final {
public: public:
using UniversalMsgId = MsgId; using UniversalMsgId = MsgId;
explicit PinnedTracker(not_null<History*> history); explicit PinnedTracker(not_null<Data::Thread*> thread);
~PinnedTracker(); ~PinnedTracker();
[[nodiscard]] rpl::producer<PinnedId> shownMessageId() const; [[nodiscard]] rpl::producer<PinnedId> shownMessageId() const;
@ -44,7 +45,7 @@ private:
void refreshViewer(); void refreshViewer();
void refreshCurrentFromSlice(); void refreshCurrentFromSlice();
const not_null<History*> _history; const not_null<Data::Thread*> _thread;
PeerData *_migratedPeer = nullptr; PeerData *_migratedPeer = nullptr;
rpl::variable<PinnedId> _current; rpl::variable<PinnedId> _current;

View file

@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_contact_status.h" #include "history/view/history_view_contact_status.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_pinned_tracker.h"
#include "history/view/history_view_pinned_section.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
@ -27,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/item_text_options.h" #include "ui/item_text_options.h"
@ -38,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "ui/toasts/common_toasts.h" #include "ui/toasts/common_toasts.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "api/api_bot.h"
#include "api/api_common.h" #include "api/api_common.h"
#include "api/api_editing.h" #include "api/api_editing.h"
#include "api/api_sending.h" #include "api/api_sending.h"
@ -57,7 +61,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/application.h" #include "core/application.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "core/click_handler_types.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -68,10 +74,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_replies_list.h" #include "data/data_replies_list.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_shared_media.h"
#include "data/data_send_action.h" #include "data/data_send_action.h"
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
#include "storage/storage_shared_media.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_result.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "facades.h" #include "facades.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -270,6 +279,9 @@ RepliesWidget::RepliesWidget(
if (_rootView) { if (_rootView) {
_rootView->raise(); _rootView->raise();
} }
if (_pinnedBar) {
_pinnedBar->raise();
}
if (_topicReopenBar) { if (_topicReopenBar) {
_topicReopenBar->bar().raise(); _topicReopenBar->bar().raise();
} }
@ -358,9 +370,13 @@ RepliesWidget::RepliesWidget(
}, lifetime()); }, lifetime());
} }
setupComposeControls();
setupTopicViewer(); setupTopicViewer();
setupComposeControls();
orderWidgets(); orderWidgets();
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
} }
RepliesWidget::~RepliesWidget() { RepliesWidget::~RepliesWidget() {
@ -384,6 +400,9 @@ void RepliesWidget::orderWidgets() {
if (_rootView) { if (_rootView) {
_rootView->raise(); _rootView->raise();
} }
if (_pinnedBar) {
_pinnedBar->raise();
}
_topBarShadow->raise(); _topBarShadow->raise();
_composeControls->raisePanels(); _composeControls->raisePanels();
} }
@ -483,9 +502,11 @@ void RepliesWidget::subscribeToTopic() {
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto height = _topicReopenBar->bar().height(); const auto height = _topicReopenBar->bar().height();
_scrollTopDelta = (height - _topicReopenBarHeight); _scrollTopDelta = (height - _topicReopenBarHeight);
_topicReopenBarHeight = height; if (_scrollTopDelta) {
updateControlsGeometry(); _topicReopenBarHeight = height;
_scrollTopDelta = 0; updateControlsGeometry();
_scrollTopDelta = 0;
}
}, _topicReopenBar->bar().lifetime()); }, _topicReopenBar->bar().lifetime());
using Flag = Data::TopicUpdate::Flag; using Flag = Data::TopicUpdate::Flag;
@ -510,6 +531,21 @@ void RepliesWidget::subscribeToTopic() {
anim::activation::background)); anim::activation::background));
}, _topicLifetime); }, _topicLifetime);
if (!_topic->creating()) {
using EntryUpdateFlag = Data::EntryUpdate::Flag;
session().changes().entryUpdates(
EntryUpdateFlag::HasPinnedMessages
) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
if (_pinnedTracker
&& (update.flags & EntryUpdateFlag::HasPinnedMessages)
&& (_topic == update.entry.get())) {
checkPinnedBarState();
}
}, lifetime());
setupPinnedTracker();
}
_cornerButtons.updateUnreadThingsVisibility(); _cornerButtons.updateUnreadThingsVisibility();
} }
@ -1408,6 +1444,275 @@ void RepliesWidget::refreshUnreadCountBadge(std::optional<int> count) {
} }
} }
void RepliesWidget::updatePinnedViewer() {
if (_scroll->isHidden() || !_topic) {
return;
}
const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
auto [view, offset] = _inner->findViewForPinnedTracking(visibleBottom);
const auto lessThanId = !view
? (ServerMaxMsgId - 1)
: (view->data()->id + (offset > 0 ? 1 : 0));
const auto lastClickedId = !_pinnedClickedId
? (ServerMaxMsgId - 1)
: _pinnedClickedId.msg;
if (_pinnedClickedId
&& lessThanId <= lastClickedId
&& !_inner->animatedScrolling()) {
_pinnedClickedId = FullMsgId();
}
if (_pinnedClickedId && !_minPinnedId) {
_minPinnedId = Data::ResolveMinPinnedId(_history->peer, _rootId);
}
if (_pinnedClickedId && _minPinnedId && _minPinnedId >= _pinnedClickedId) {
// After click on the last pinned message we should the top one.
_pinnedTracker->trackAround(ServerMaxMsgId - 1);
} else {
_pinnedTracker->trackAround(std::min(lessThanId, lastClickedId));
}
}
void RepliesWidget::checkLastPinnedClickedIdReset(
int wasScrollTop,
int nowScrollTop) {
if (_scroll->isHidden() || !_topic) {
return;
}
if (wasScrollTop < nowScrollTop && _pinnedClickedId) {
// User scrolled down.
_pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt;
updatePinnedViewer();
}
}
void RepliesWidget::setupPinnedTracker() {
Expects(_topic != nullptr);
_pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_topic);
_pinnedBar = nullptr;
SharedMediaViewer(
&_topic->session(),
Storage::SharedMediaKey(
_topic->channel()->id,
_rootId,
Storage::SharedMediaType::Pinned,
ServerMaxMsgId - 1),
1,
1
) | rpl::filter([=](const SparseIdsSlice &result) {
return result.fullCount().has_value();
}) | rpl::start_with_next([=](const SparseIdsSlice &result) {
_topic->setHasPinnedMessages(*result.fullCount() != 0);
checkPinnedBarState();
}, _topicLifetime);
}
void RepliesWidget::checkPinnedBarState() {
Expects(_pinnedTracker != nullptr);
Expects(_inner != nullptr);
const auto peer = _history->peer;
const auto hiddenId = peer->canPinMessages()
? MsgId(0)
: peer->session().settings().hiddenPinnedMessageId(
peer->id,
_rootId);
const auto currentPinnedId = Data::ResolveTopPinnedId(peer, _rootId);
const auto universalPinnedId = !currentPinnedId
? int32(0)
: currentPinnedId.msg;
if (universalPinnedId == hiddenId) {
if (_pinnedBar) {
_pinnedTracker->reset();
auto qobject = base::unique_qptr{
Ui::WrapAsQObject(this, std::move(_pinnedBar)).get()
};
auto destroyer = [this, object = std::move(qobject)]() mutable {
object = nullptr;
updateControlsGeometry();
};
base::call_delayed(
st::defaultMessageBar.duration,
this,
std::move(destroyer));
}
return;
}
if (_pinnedBar || !universalPinnedId) {
return;
}
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
return controller()->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);
});
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
_history->peer,
_rootId,
nullptr,
Storage::SharedMediaType::Pinned
) | rpl::distinct_until_changed(
) | rpl::map([=](int count) {
if (_pinnedClickedId) {
_pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt;
updatePinnedViewer();
}
return (count > 1);
}) | rpl::distinct_until_changed();
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup(
&session(),
_pinnedTracker->shownMessageId());
rpl::combine(
rpl::duplicate(pinnedRefreshed),
rpl::duplicate(markupRefreshed)
) | rpl::start_with_next([=](bool many, HistoryItem *item) {
refreshPinnedBarButton(many, item);
}, _pinnedBar->lifetime());
_pinnedBar->setContent(rpl::combine(
HistoryView::PinnedBarContent(
&session(),
_pinnedTracker->shownMessageId(),
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
std::move(pinnedRefreshed),
std::move(markupRefreshed)
) | rpl::map([](Ui::MessageBarContent &&content, bool, HistoryItem*) {
return std::move(content);
}));
controller()->adaptive().oneColumnValue(
) | rpl::start_with_next([=](bool one) {
_pinnedBar->setShadowGeometryPostprocess([=](QRect geometry) {
if (!one) {
geometry.setLeft(geometry.left() + st::lineWidth);
}
return geometry;
});
}, _pinnedBar->lifetime());
_pinnedBar->barClicks(
) | rpl::start_with_next([=] {
const auto id = _pinnedTracker->currentMessageId();
if (const auto item = session().data().message(id.message)) {
showAtPosition(item->position());
if (const auto group = session().data().groups().find(item)) {
// Hack for the case when a non-first item of an album
// is pinned and we still want the 'show last after first'.
_pinnedClickedId = group->items.front()->fullId();
} else {
_pinnedClickedId = id.message;
}
_minPinnedId = std::nullopt;
updatePinnedViewer();
}
}, _pinnedBar->lifetime());
_pinnedBarHeight = 0;
_pinnedBar->heightValue(
) | rpl::start_with_next([=](int height) {
if (const auto delta = height - _pinnedBarHeight) {
_pinnedBarHeight = height;
setGeometryWithTopMoved(geometry(), delta);
}
}, _pinnedBar->lifetime());
orderWidgets();
if (animatingShow()) {
_pinnedBar->hide();
}
}
void RepliesWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
const auto openSection = [=] {
const auto id = _pinnedTracker
? _pinnedTracker->currentMessageId()
: HistoryView::PinnedId();
if (!id.message) {
return;
}
controller()->showSection(
std::make_shared<PinnedMemento>(_topic, id.message.msg));
};
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) {
const auto &rows = replyMarkup->data.rows;
if ((rows.size() == 1) && (rows.front().size() == 1)) {
const auto text = rows.front().front().text;
if (!text.isEmpty()) {
auto button = object_ptr<Ui::RoundButton>(
this,
rpl::single(text),
st::historyPinnedBotButton);
button->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
button->setFullRadius(true);
button->setClickedCallback([=] {
Api::ActivateBotCommand(
_inner->prepareClickHandlerContext(item->fullId()),
0,
0);
});
if (button->width() > st::historyPinnedBotButtonMaxWidth) {
button->setFullWidth(st::historyPinnedBotButtonMaxWidth);
}
struct State {
base::unique_qptr<Ui::PopupMenu> menu;
};
const auto state = button->lifetime().make_state<State>();
_pinnedBar->contextMenuRequested(
) | rpl::start_with_next([=, raw = button.data()] {
state->menu = base::make_unique_q<Ui::PopupMenu>(raw);
state->menu->addAction(
tr::lng_settings_events_pinned(tr::now),
openSection);
state->menu->popup(QCursor::pos());
}, button->lifetime());
_pinnedBar->setRightButton(std::move(button));
return;
}
}
}
const auto close = !many;
auto button = object_ptr<Ui::IconButton>(
this,
close ? st::historyReplyCancel : st::historyPinnedShowAll);
button->clicks(
) | rpl::start_with_next([=] {
if (close) {
hidePinnedMessage();
} else {
openSection();
}
}, button->lifetime());
_pinnedBar->setRightButton(std::move(button));
}
void RepliesWidget::hidePinnedMessage() {
Expects(_pinnedBar != nullptr);
const auto id = _pinnedTracker->currentMessageId();
if (!id.message) {
return;
}
if (_history->peer->canPinMessages()) {
Window::ToggleMessagePinned(controller(), id.message, false);
} else {
const auto callback = [=] {
if (_pinnedTracker) {
checkPinnedBarState();
}
};
Window::HidePinnedBar(
controller(),
_history->peer,
_rootId,
crl::guard(this, callback));
}
}
void RepliesWidget::cornerButtonsShowAtPosition( void RepliesWidget::cornerButtonsShowAtPosition(
Data::MessagePosition position) { Data::MessagePosition position) {
showAtPosition(position); showAtPosition(position);
@ -1538,6 +1843,9 @@ QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &pa
if (_rootView) { if (_rootView) {
_rootView->hide(); _rootView->hide();
} }
if (_pinnedBar) {
_pinnedBar->hide();
}
return result; return result;
} }
@ -1734,13 +2042,18 @@ void RepliesWidget::updateControlsGeometry() {
if (_rootView) { if (_rootView) {
_rootView->resizeToWidth(contentWidth); _rootView->resizeToWidth(contentWidth);
} }
if (_pinnedBar) {
_pinnedBar->move(0, _topBar->height());
_pinnedBar->resizeToWidth(contentWidth);
}
const auto bottom = height(); const auto bottom = height();
const auto controlsHeight = _joinGroup const auto controlsHeight = _joinGroup
? _joinGroup->height() ? _joinGroup->height()
: _composeControls->heightCurrent(); : _composeControls->heightCurrent();
auto top = _topBar->height() auto top = _topBar->height()
+ _rootViewHeight; + _rootViewHeight
+ _pinnedBarHeight;
if (_topicReopenBar) { if (_topicReopenBar) {
_topicReopenBar->bar().move(0, top); _topicReopenBar->bar().move(0, top);
top += _topicReopenBar->bar().height(); top += _topicReopenBar->bar().height();
@ -1807,8 +2120,15 @@ void RepliesWidget::updateInnerVisibleArea() {
const auto scrollTop = _scroll->scrollTop(); const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
updatePinnedVisibility(); updatePinnedVisibility();
updatePinnedViewer();
_cornerButtons.updateJumpDownVisibility(); _cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility(); _cornerButtons.updateUnreadThingsVisibility();
if (_lastScrollTop != scrollTop) {
if (!_synteticScrollEvent) {
checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop);
}
_lastScrollTop = scrollTop;
}
} }
void RepliesWidget::updatePinnedVisibility() { void RepliesWidget::updatePinnedVisibility() {
@ -1875,6 +2195,9 @@ void RepliesWidget::showFinishedHook() {
if (_rootView) { if (_rootView) {
_rootView->show(); _rootView->show();
} }
if (_pinnedBar) {
_pinnedBar->show();
}
// We should setup the drag area only after // We should setup the drag area only after
// the section animation is finished, // the section animation is finished,
@ -1895,14 +2218,17 @@ Context RepliesWidget::listContext() {
return Context::Replies; return Context::Replies;
} }
bool RepliesWidget::listScrollTo(int top) { bool RepliesWidget::listScrollTo(int top, bool syntetic) {
top = std::clamp(top, 0, _scroll->scrollTopMax()); top = std::clamp(top, 0, _scroll->scrollTopMax());
if (_scroll->scrollTop() == top) { const auto scrolled = (_scroll->scrollTop() != top);
_synteticScrollEvent = syntetic;
if (scrolled) {
_scroll->scrollToY(top);
} else if (syntetic) {
updateInnerVisibleArea(); updateInnerVisibleArea();
return false;
} }
_scroll->scrollToY(top); _synteticScrollEvent = false;
return true; return syntetic;
} }
void RepliesWidget::listCancelRequest() { void RepliesWidget::listCancelRequest() {

View file

@ -67,6 +67,7 @@ class SendActionPainter;
class StickerToast; class StickerToast;
class TopicReopenBar; class TopicReopenBar;
class EmptyPainter; class EmptyPainter;
class PinnedTracker;
class RepliesWidget final class RepliesWidget final
: public Window::SectionWidget : public Window::SectionWidget
@ -119,7 +120,7 @@ public:
// ListDelegate interface. // ListDelegate interface.
Context listContext() override; Context listContext() override;
bool listScrollTo(int top) override; bool listScrollTo(int top, bool syntetic = true) override;
void listCancelRequest() override; void listCancelRequest() override;
void listDeleteRequest() override; void listDeleteRequest() override;
rpl::producer<Data::MessagesSlice> listSource( rpl::producer<Data::MessagesSlice> listSource(
@ -242,7 +243,15 @@ private:
void replyToMessage(FullMsgId itemId); void replyToMessage(FullMsgId itemId);
void refreshTopBarActiveChat(); void refreshTopBarActiveChat();
void refreshUnreadCountBadge(std::optional<int> count); void refreshUnreadCountBadge(std::optional<int> count);
void reloadUnreadCountIfNeeded();
void hidePinnedMessage();
void updatePinnedViewer();
void setupPinnedTracker();
void checkPinnedBarState();
void refreshPinnedBarButton(bool many, HistoryItem *item);
void checkLastPinnedClickedIdReset(
int wasScrollTop,
int nowScrollTop);
void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles( bool confirmSendingFiles(
@ -308,6 +317,13 @@ private:
std::unique_ptr<TopicReopenBar> _topicReopenBar; std::unique_ptr<TopicReopenBar> _topicReopenBar;
std::unique_ptr<EmptyPainter> _emptyPainter; std::unique_ptr<EmptyPainter> _emptyPainter;
bool _skipScrollEvent = false; bool _skipScrollEvent = false;
bool _synteticScrollEvent = false;
std::unique_ptr<PinnedTracker> _pinnedTracker;
std::unique_ptr<Ui::PinnedBar> _pinnedBar;
int _pinnedBarHeight = 0;
FullMsgId _pinnedClickedId;
std::optional<FullMsgId> _minPinnedId;
std::unique_ptr<Ui::PinnedBar> _rootView; std::unique_ptr<Ui::PinnedBar> _rootView;
int _rootViewHeight = 0; int _rootViewHeight = 0;
@ -321,6 +337,7 @@ private:
HistoryView::CornerButtons _cornerButtons; HistoryView::CornerButtons _cornerButtons;
rpl::lifetime _topicLifetime; rpl::lifetime _topicLifetime;
int _lastScrollTop = 0;
int _topicReopenBarHeight = 0; int _topicReopenBarHeight = 0;
int _scrollTopDelta = 0; int _scrollTopDelta = 0;

View file

@ -1050,7 +1050,7 @@ Context ScheduledWidget::listContext() {
return Context::History; return Context::History;
} }
bool ScheduledWidget::listScrollTo(int top) { bool ScheduledWidget::listScrollTo(int top, bool syntetic) {
top = std::clamp(top, 0, _scroll->scrollTopMax()); top = std::clamp(top, 0, _scroll->scrollTopMax());
if (_scroll->scrollTop() == top) { if (_scroll->scrollTop() == top) {
updateInnerVisibleArea(); updateInnerVisibleArea();

View file

@ -101,7 +101,7 @@ public:
// ListDelegate interface. // ListDelegate interface.
Context listContext() override; Context listContext() override;
bool listScrollTo(int top) override; bool listScrollTo(int top, bool syntetic = true) override;
void listCancelRequest() override; void listCancelRequest() override;
void listDeleteRequest() override; void listDeleteRequest() override;
rpl::producer<Data::MessagesSlice> listSource( rpl::producer<Data::MessagesSlice> listSource(

View file

@ -489,9 +489,10 @@ void TopBarWidget::paintTopBar(Painter &p) {
} }
const auto now = crl::now(); const auto now = crl::now();
const auto history = _activeChat.key.history(); const auto history = _activeChat.key.owningHistory();
const auto folder = _activeChat.key.folder(); const auto folder = _activeChat.key.folder();
if (const auto topic = _activeChat.key.topic()) { const auto topic = _activeChat.key.topic();
if (topic && _activeChat.section == Section::Replies) {
p.setPen(st::dialogsNameFg); p.setPen(st::dialogsNameFg);
topic->chatListNameText().drawElided( topic->chatListNameText().drawElided(
p, p,

View file

@ -41,48 +41,56 @@ QByteArray SessionSettings::serialize() const {
+ sizeof(qint32) * 5 + sizeof(qint32) * 5
+ _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64) + _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64)
+ sizeof(qint32) * 5 + sizeof(qint32) * 5
+ _hiddenPinnedMessages.size() * (sizeof(quint64) + sizeof(qint32))
+ sizeof(qint32) + sizeof(qint32)
+ (_mutePeriods.size() * sizeof(quint64)) + (_mutePeriods.size() * sizeof(quint64))
+ sizeof(qint32); + sizeof(qint32) * 2
+ _hiddenPinnedMessages.size() * (sizeof(quint64) * 3);
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
{ {
QDataStream stream(&result, QIODevice::WriteOnly); QDataStream stream(&result, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_1); stream.setVersion(QDataStream::Qt_5_1);
stream << qint32(kVersionTag) << qint32(kVersion); stream
stream << static_cast<qint32>(_selectorTab); << qint32(kVersionTag) << qint32(kVersion)
stream << qint32(_groupStickersSectionHidden.size()); << static_cast<qint32>(_selectorTab)
<< qint32(_groupStickersSectionHidden.size());
for (const auto &peerId : _groupStickersSectionHidden) { for (const auto &peerId : _groupStickersSectionHidden) {
stream << SerializePeerId(peerId); stream << SerializePeerId(peerId);
} }
stream << qint32(_supportSwitch); stream
stream << qint32(_supportFixChatsOrder ? 1 : 0); << qint32(_supportSwitch)
stream << qint32(_supportTemplatesAutocomplete ? 1 : 0); << qint32(_supportFixChatsOrder ? 1 : 0)
stream << qint32(_supportChatsTimeSlice.current()); << qint32(_supportTemplatesAutocomplete ? 1 : 0)
stream << autoDownload; << qint32(_supportChatsTimeSlice.current())
stream << qint32(_supportAllSearchResults.current() ? 1 : 0); << autoDownload
stream << qint32(_archiveCollapsed.current() ? 1 : 0); << qint32(_supportAllSearchResults.current() ? 1 : 0)
stream << qint32(_archiveInMainMenu.current() ? 1 : 0); << qint32(_archiveCollapsed.current() ? 1 : 0)
stream << qint32(_skipArchiveInSearch.current() ? 1 : 0); << qint32(_archiveInMainMenu.current() ? 1 : 0)
stream << qint32(_mediaLastPlaybackPosition.size()); << qint32(_skipArchiveInSearch.current() ? 1 : 0)
<< qint32(_mediaLastPlaybackPosition.size());
for (const auto &[id, time] : _mediaLastPlaybackPosition) { for (const auto &[id, time] : _mediaLastPlaybackPosition) {
stream << quint64(id) << qint64(time); stream << quint64(id) << qint64(time);
} }
stream << qint32(0); stream
stream << qint32(_dialogsFiltersEnabled ? 1 : 0); << qint32(0) // very old _hiddenPinnedMessages.size());
stream << qint32(_supportAllSilent ? 1 : 0); << qint32(_dialogsFiltersEnabled ? 1 : 0)
stream << qint32(_photoEditorHintShowsCount); << qint32(_supportAllSilent ? 1 : 0)
stream << qint32(_hiddenPinnedMessages.size()); << qint32(_photoEditorHintShowsCount)
for (const auto &[key, value] : _hiddenPinnedMessages) { << qint32(0) // old _hiddenPinnedMessages.size());
stream << SerializePeerId(key) << qint64(value.bare); << qint32(_mutePeriods.size());
}
stream << qint32(_mutePeriods.size());
for (const auto &period : _mutePeriods) { for (const auto &period : _mutePeriods) {
stream << quint64(period); stream << quint64(period);
} }
stream << qint32(_skipPremiumStickersSet ? 1 : 0); stream
<< qint32(_skipPremiumStickersSet ? 1 : 0)
<< qint32(_hiddenPinnedMessages.size());
for (const auto &[key, value] : _hiddenPinnedMessages) {
stream
<< SerializePeerId(key.peerId)
<< qint64(key.topicRootId.bare)
<< qint64(value.bare);
}
} }
return result; return result;
} }
@ -139,7 +147,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
QByteArray appVideoPipGeometry = app.videoPipGeometry(); QByteArray appVideoPipGeometry = app.videoPipGeometry();
std::vector<int> appDictionariesEnabled; std::vector<int> appDictionariesEnabled;
qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0; qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0;
base::flat_map<PeerId, MsgId> hiddenPinnedMessages; base::flat_map<ThreadId, MsgId> hiddenPinnedMessages;
qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0; qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0;
qint32 supportAllSilent = _supportAllSilent ? 1 : 0; qint32 supportAllSilent = _supportAllSilent ? 1 : 0;
qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount; qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount;
@ -334,7 +342,9 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
"Bad data for SessionSettings::addFromSerialized()")); "Bad data for SessionSettings::addFromSerialized()"));
return; return;
} }
hiddenPinnedMessages.emplace(DeserializePeerId(key), value); hiddenPinnedMessages.emplace(
ThreadId{ DeserializePeerId(key), MsgId(0) },
value);
} }
} }
} }
@ -360,7 +370,9 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
"Bad data for SessionSettings::addFromSerialized()")); "Bad data for SessionSettings::addFromSerialized()"));
return; return;
} }
hiddenPinnedMessages.emplace(DeserializePeerId(key), value); hiddenPinnedMessages.emplace(
ThreadId{ DeserializePeerId(key), MsgId(0) },
value);
} }
} }
} }
@ -378,6 +390,26 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> skipPremiumStickersSet; stream >> skipPremiumStickersSet;
} }
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() == QDataStream::Ok) {
for (auto i = 0; i != count; ++i) {
auto keyPeerId = quint64();
auto keyTopicRootId = qint64();
auto value = qint64();
stream >> keyPeerId >> keyTopicRootId >> value;
if (stream.status() != QDataStream::Ok) {
LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()"));
return;
}
hiddenPinnedMessages.emplace(
ThreadId{ DeserializePeerId(keyPeerId), keyTopicRootId },
value);
}
}
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for SessionSettings::addFromSerialized()")); "Bad data for SessionSettings::addFromSerialized()"));
@ -561,6 +593,25 @@ rpl::producer<bool> SessionSettings::skipArchiveInSearchChanges() const {
return _skipArchiveInSearch.changes(); return _skipArchiveInSearch.changes();
} }
MsgId SessionSettings::hiddenPinnedMessageId(
PeerId peerId,
MsgId topicRootId) const {
const auto i = _hiddenPinnedMessages.find({ peerId, topicRootId });
return (i != end(_hiddenPinnedMessages)) ? i->second : 0;
}
void SessionSettings::setHiddenPinnedMessageId(
PeerId peerId,
MsgId topicRootId,
MsgId msgId) {
const auto id = ThreadId{ peerId, topicRootId };
if (msgId) {
_hiddenPinnedMessages[id] = msgId;
} else {
_hiddenPinnedMessages.remove(id);
}
}
bool SessionSettings::photoEditorHintShown() const { bool SessionSettings::photoEditorHintShown() const {
return _photoEditorHintShowsCount < kPhotoEditorHintMaxShowsCount; return _photoEditorHintShowsCount < kPhotoEditorHintMaxShowsCount;
} }

View file

@ -101,17 +101,13 @@ public:
return _hadLegacyCallsPeerToPeerNobody; return _hadLegacyCallsPeerToPeerNobody;
} }
[[nodiscard]] MsgId hiddenPinnedMessageId(PeerId peerId) const { [[nodiscard]] MsgId hiddenPinnedMessageId(
const auto i = _hiddenPinnedMessages.find(peerId); PeerId peerId,
return (i != end(_hiddenPinnedMessages)) ? i->second : 0; MsgId topicRootId = 0) const;
} void setHiddenPinnedMessageId(
void setHiddenPinnedMessageId(PeerId peerId, MsgId msgId) { PeerId peerId,
if (msgId) { MsgId topicRootId,
_hiddenPinnedMessages[peerId] = msgId; MsgId msgId);
} else {
_hiddenPinnedMessages.remove(peerId);
}
}
[[nodiscard]] bool dialogsFiltersEnabled() const { [[nodiscard]] bool dialogsFiltersEnabled() const {
return _dialogsFiltersEnabled; return _dialogsFiltersEnabled;
@ -137,6 +133,15 @@ private:
static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60;
static constexpr auto kPhotoEditorHintMaxShowsCount = 5; static constexpr auto kPhotoEditorHintMaxShowsCount = 5;
struct ThreadId {
PeerId peerId;
MsgId topicRootId;
friend inline constexpr auto operator<=>(
ThreadId,
ThreadId) = default;
};
ChatHelpers::SelectorTab _selectorTab; // per-window ChatHelpers::SelectorTab _selectorTab; // per-window
base::flat_set<PeerId> _groupStickersSectionHidden; base::flat_set<PeerId> _groupStickersSectionHidden;
bool _hadLegacyCallsPeerToPeerNobody = false; bool _hadLegacyCallsPeerToPeerNobody = false;
@ -145,7 +150,7 @@ private:
rpl::variable<bool> _archiveInMainMenu = false; rpl::variable<bool> _archiveInMainMenu = false;
rpl::variable<bool> _skipArchiveInSearch = false; rpl::variable<bool> _skipArchiveInSearch = false;
std::vector<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition; std::vector<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition;
base::flat_map<PeerId, MsgId> _hiddenPinnedMessages; base::flat_map<ThreadId, MsgId> _hiddenPinnedMessages;
bool _dialogsFiltersEnabled = false; bool _dialogsFiltersEnabled = false;
int _photoEditorHintShowsCount = 0; int _photoEditorHintShowsCount = 0;
std::vector<TimeId> _mutePeriods; std::vector<TimeId> _mutePeriods;

View file

@ -1082,6 +1082,7 @@ bool ReadSetting(
for (auto i = v.begin(), e = v.end(); i != e; ++i) { for (auto i = v.begin(), e = v.end(); i != e; ++i) {
context.sessionSettings().setHiddenPinnedMessageId( context.sessionSettings().setHiddenPinnedMessageId(
DeserializePeerId(i.key()), DeserializePeerId(i.key()),
MsgId(0), // topicRootId
MsgId(i.value())); MsgId(i.value()));
} }
context.legacyRead = true; context.legacyRead = true;

View file

@ -20,6 +20,7 @@ public:
void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveOne &&query);
void remove(SharedMediaRemoveAll &&query); void remove(SharedMediaRemoveAll &&query);
void invalidate(SharedMediaInvalidateBottom &&query); void invalidate(SharedMediaInvalidateBottom &&query);
void unload(SharedMediaUnloadThread &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
SharedMediaResult snapshot(const SharedMediaQuery &query) const; SharedMediaResult snapshot(const SharedMediaQuery &query) const;
bool empty(const SharedMediaKey &key) const; bool empty(const SharedMediaKey &key) const;
@ -65,6 +66,10 @@ void Facade::Impl::invalidate(SharedMediaInvalidateBottom &&query) {
_sharedMedia.invalidate(std::move(query)); _sharedMedia.invalidate(std::move(query));
} }
void Facade::Impl::unload(SharedMediaUnloadThread &&query) {
_sharedMedia.unload(std::move(query));
}
rpl::producer<SharedMediaResult> Facade::Impl::query(SharedMediaQuery &&query) const { rpl::producer<SharedMediaResult> Facade::Impl::query(SharedMediaQuery &&query) const {
return _sharedMedia.query(std::move(query)); return _sharedMedia.query(std::move(query));
} }
@ -144,6 +149,10 @@ void Facade::invalidate(SharedMediaInvalidateBottom &&query) {
_impl->invalidate(std::move(query)); _impl->invalidate(std::move(query));
} }
void Facade::unload(SharedMediaUnloadThread &&query) {
_impl->unload(std::move(query));
}
rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const { rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const {
return _impl->query(std::move(query)); return _impl->query(std::move(query));
} }

View file

@ -24,6 +24,7 @@ struct SharedMediaAddSlice;
struct SharedMediaRemoveOne; struct SharedMediaRemoveOne;
struct SharedMediaRemoveAll; struct SharedMediaRemoveAll;
struct SharedMediaInvalidateBottom; struct SharedMediaInvalidateBottom;
struct SharedMediaUnloadThread;
struct SharedMediaQuery; struct SharedMediaQuery;
struct SharedMediaKey; struct SharedMediaKey;
using SharedMediaResult = SparseIdsListResult; using SharedMediaResult = SparseIdsListResult;
@ -47,6 +48,7 @@ public:
void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveOne &&query);
void remove(SharedMediaRemoveAll &&query); void remove(SharedMediaRemoveAll &&query);
void invalidate(SharedMediaInvalidateBottom &&query); void invalidate(SharedMediaInvalidateBottom &&query);
void unload(SharedMediaUnloadThread &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
SharedMediaResult snapshot(const SharedMediaQuery &query) const; SharedMediaResult snapshot(const SharedMediaQuery &query) const;

View file

@ -109,6 +109,10 @@ void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) {
_bottomInvalidated.fire(std::move(query)); _bottomInvalidated.fire(std::move(query));
} }
void SharedMedia::unload(SharedMediaUnloadThread &&query) {
_lists.erase({ query.peerId, query.topicRootId });
}
rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const { rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const {
Expects(IsValidSharedMediaType(query.key.type)); Expects(IsValidSharedMediaType(query.key.type));

View file

@ -197,6 +197,18 @@ struct SharedMediaSliceUpdate {
SparseIdsSliceUpdate data; SparseIdsSliceUpdate data;
}; };
struct SharedMediaUnloadThread {
SharedMediaUnloadThread(
PeerId peerId,
MsgId topicRootId)
: peerId(peerId)
, topicRootId(topicRootId) {
}
PeerId peerId = 0;
MsgId topicRootId = 0;
};
class SharedMedia { class SharedMedia {
public: public:
using Type = SharedMediaType; using Type = SharedMediaType;
@ -207,6 +219,7 @@ public:
void remove(SharedMediaRemoveOne &&query); void remove(SharedMediaRemoveOne &&query);
void remove(SharedMediaRemoveAll &&query); void remove(SharedMediaRemoveAll &&query);
void invalidate(SharedMediaInvalidateBottom &&query); void invalidate(SharedMediaInvalidateBottom &&query);
void unload(SharedMediaUnloadThread &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const; rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
SharedMediaResult snapshot(const SharedMediaQuery &query) const; SharedMediaResult snapshot(const SharedMediaQuery &query) const;

View file

@ -1651,19 +1651,26 @@ void ToggleMessagePinned(
void HidePinnedBar( void HidePinnedBar(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
Fn<void()> onHidden) { Fn<void()> onHidden) {
const auto callback = crl::guard(navigation, [=](Fn<void()> &&close) { const auto callback = crl::guard(navigation, [=](Fn<void()> &&close) {
close(); close();
auto &session = peer->session(); auto &session = peer->session();
const auto migrated = peer->migrateFrom(); const auto migrated = topicRootId ? nullptr : peer->migrateFrom();
const auto top = Data::ResolveTopPinnedId(peer, MsgId(0), migrated); const auto top = Data::ResolveTopPinnedId(
peer,
topicRootId,
migrated);
const auto universal = !top const auto universal = !top
? MsgId(0) ? MsgId(0)
: (migrated && !peerIsChannel(top.peer)) : (migrated && !peerIsChannel(top.peer))
? (top.msg - ServerMaxMsgId) ? (top.msg - ServerMaxMsgId)
: top.msg; : top.msg;
if (universal) { if (universal) {
session.settings().setHiddenPinnedMessageId(peer->id, universal); session.settings().setHiddenPinnedMessageId(
peer->id,
topicRootId,
universal);
session.saveSettingsDelayed(); session.saveSettingsDelayed();
if (onHidden) { if (onHidden) {
onHidden(); onHidden();
@ -1683,11 +1690,18 @@ void HidePinnedBar(
void UnpinAllMessages( void UnpinAllMessages(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<History*> history) { not_null<Data::Thread*> thread) {
const auto weak = base::make_weak(thread);
const auto callback = crl::guard(navigation, [=](Fn<void()> &&close) { const auto callback = crl::guard(navigation, [=](Fn<void()> &&close) {
close(); close();
const auto api = &history->session().api(); const auto strong = weak.get();
if (!strong || !strong->asHistory()) { // #TODO forum pinned
return;
}
const auto api = &strong->session().api();
const auto sendRequest = [=](auto self) -> void { const auto sendRequest = [=](auto self) -> void {
const auto history = strong->owningHistory();
const auto topicRootId = strong->topicRootId();
api->request(MTPmessages_UnpinAllMessages( api->request(MTPmessages_UnpinAllMessages(
history->peer->input history->peer->input
)).done([=](const MTPmessages_AffectedHistory &result) { )).done([=](const MTPmessages_AffectedHistory &result) {
@ -1696,7 +1710,7 @@ void UnpinAllMessages(
if (offset > 0) { if (offset > 0) {
self(self); self(self);
} else { } else {
history->unpinAllMessages(); history->unpinMessagesFor(topicRootId);
} }
}).send(); }).send();
}; };

View file

@ -25,6 +25,7 @@ class Folder;
class Session; class Session;
struct ForwardDraft; struct ForwardDraft;
class ForumTopic; class ForumTopic;
class Thread;
} // namespace Data } // namespace Data
namespace Dialogs { namespace Dialogs {
@ -130,9 +131,10 @@ void ToggleMessagePinned(
void HidePinnedBar( void HidePinnedBar(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId topicRootId,
Fn<void()> onHidden); Fn<void()> onHidden);
void UnpinAllMessages( void UnpinAllMessages(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<History*> history); not_null<Data::Thread*> thread);
} // namespace Window } // namespace Window