mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 21:27:07 +02:00
Support pinned messages bar in topics.
This commit is contained in:
parent
da1e784803
commit
8dc27339b4
40 changed files with 811 additions and 206 deletions
|
@ -1511,9 +1511,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_topic_created_inside" = "Topic created";
|
||||
"lng_action_topic_closed_inside" = "Topic closed";
|
||||
"lng_action_topic_reopened_inside" = "Topic reopened";
|
||||
"lng_action_topic_created" = "{topic} — was created";
|
||||
"lng_action_topic_closed" = "{topic} — was closed";
|
||||
"lng_action_topic_reopened" = "{topic} — was reopened";
|
||||
"lng_action_topic_created" = "«{topic}» was created";
|
||||
"lng_action_topic_closed" = "«{topic}» was closed";
|
||||
"lng_action_topic_reopened" = "«{topic}» was reopened";
|
||||
"lng_action_topic_placeholder" = "topic";
|
||||
"lng_action_topic_renamed" = "{from} renamed the {link} to «{title}»";
|
||||
"lng_action_topic_icon_changed" = "{from} changed the {link} icon to {emoji}";
|
||||
|
|
|
@ -3011,6 +3011,10 @@ void ApiWrap::sharedMediaDone(
|
|||
MsgId topicRootId,
|
||||
SharedMediaType type,
|
||||
Api::SearchResult &&parsed) {
|
||||
const auto topic = peer->forumTopicFor(topicRootId);
|
||||
if (topicRootId && !topic) {
|
||||
return;
|
||||
}
|
||||
_session->storage().add(Storage::SharedMediaAddSlice(
|
||||
peer->id,
|
||||
topicRootId,
|
||||
|
@ -3021,6 +3025,9 @@ void ApiWrap::sharedMediaDone(
|
|||
));
|
||||
if (type == SharedMediaType::Pinned && !parsed.messageIds.empty()) {
|
||||
peer->owner().history(peer)->setHasPinnedMessages(true);
|
||||
if (topic) {
|
||||
topic->setHasPinnedMessages(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3451,11 +3458,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
|||
action.generateLocal = true;
|
||||
sendAction(action);
|
||||
|
||||
const auto replyToId = message.action.replyTo;
|
||||
const auto replyToId = action.replyTo;
|
||||
const auto replyTo = replyToId
|
||||
? peer->owner().message(peer, replyToId)
|
||||
: 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())
|
||||
|| Api::SendDice(message)) {
|
||||
return;
|
||||
|
|
|
@ -130,9 +130,8 @@ struct HistoryUpdate {
|
|||
BotKeyboard = (1U << 12),
|
||||
CloudDraft = (1U << 13),
|
||||
LocalDraftSet = (1U << 14),
|
||||
PinnedMessages = (1U << 15),
|
||||
|
||||
LastUsedBit = (1U << 15),
|
||||
LastUsedBit = (1U << 14),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
@ -156,7 +155,7 @@ struct TopicUpdate {
|
|||
CloudDraft = (1U << 8),
|
||||
Closed = (1U << 9),
|
||||
|
||||
LastUsedBit = (1U << 8),
|
||||
LastUsedBit = (1U << 9),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
@ -196,8 +195,9 @@ struct EntryUpdate {
|
|||
None = 0,
|
||||
|
||||
Repaint = (1U << 0),
|
||||
HasPinnedMessages = (1U << 1),
|
||||
|
||||
LastUsedBit = (1U << 0),
|
||||
LastUsedBit = (1U << 1),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -994,7 +994,7 @@ void ApplyChannelUpdate(
|
|||
}
|
||||
}
|
||||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(channel, MsgId(0), pinned->v);
|
||||
SetTopPinnedMessageId(channel, pinned->v);
|
||||
}
|
||||
if (channel->isMegagroup()) {
|
||||
auto commands = ranges::views::all(
|
||||
|
|
|
@ -475,7 +475,7 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
|||
chat->session().api().inviteLinks().clearMyPermanent(chat);
|
||||
}
|
||||
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->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||
|
|
|
@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/application.h"
|
||||
#include "ui/layers/generic_box.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/notifications_manager.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
@ -63,6 +65,12 @@ Forum::~Forum() {
|
|||
if (_requestId) {
|
||||
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 {
|
||||
|
@ -170,6 +178,9 @@ void Forum::applyTopicDeleted(MsgId rootId) {
|
|||
_topics.erase(i);
|
||||
|
||||
_history->destroyMessagesByTopic(rootId);
|
||||
session().storage().unload(Storage::SharedMediaUnloadThread(
|
||||
_history->peer->id,
|
||||
rootId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -657,9 +657,7 @@ void ForumTopic::setMuted(bool muted) {
|
|||
const auto notify = state.unread || state.reaction;
|
||||
const auto notifier = unreadStateChangeNotifier(notify);
|
||||
Thread::setMuted(muted);
|
||||
session().changes().topicUpdated(
|
||||
this,
|
||||
Data::TopicUpdate::Flag::Notifications);
|
||||
session().changes().topicUpdated(this, UpdateFlag::Notifications);
|
||||
}
|
||||
|
||||
not_null<HistoryView::SendActionPainter*> ForumTopic::sendActionPainter() {
|
||||
|
|
|
@ -139,6 +139,7 @@ private:
|
|||
enum class Flag : uchar {
|
||||
Closed = (1 << 0),
|
||||
My = (1 << 1),
|
||||
HasPinnedMessages = (1 << 2),
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; }
|
||||
using Flags = base::flags<Flag>;
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_photo.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_histories.h"
|
||||
|
@ -1255,7 +1256,6 @@ std::optional<QString> RestrictionError(
|
|||
|
||||
void SetTopPinnedMessageId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
MsgId messageId) {
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (messageId <= channel->availableMinId()) {
|
||||
|
@ -1265,12 +1265,15 @@ void SetTopPinnedMessageId(
|
|||
auto &session = peer->session();
|
||||
const auto hiddenId = session.settings().hiddenPinnedMessageId(peer->id);
|
||||
if (hiddenId != 0 && hiddenId != messageId) {
|
||||
session.settings().setHiddenPinnedMessageId(peer->id, 0);
|
||||
session.settings().setHiddenPinnedMessageId(
|
||||
peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
0);
|
||||
session.saveSettingsDelayed();
|
||||
}
|
||||
session.storage().add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
topicRootId,
|
||||
MsgId(0), // topicRootId
|
||||
Storage::SharedMediaType::Pinned,
|
||||
messageId,
|
||||
{ messageId, ServerMaxMsgId }));
|
||||
|
|
|
@ -482,15 +482,14 @@ std::optional<QString> RestrictionError(
|
|||
|
||||
void SetTopPinnedMessageId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
MsgId messageId);
|
||||
[[nodiscard]] FullMsgId ResolveTopPinnedId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerData *migrated);
|
||||
PeerData *migrated = nullptr);
|
||||
[[nodiscard]] FullMsgId ResolveMinPinnedId(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
PeerData *migrated);
|
||||
PeerData *migrated = nullptr);
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -8,15 +8,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_thread.h"
|
||||
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_unread_things.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
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 {
|
||||
if (const auto topic = asTopic()) {
|
||||
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
|
||||
|
|
|
@ -58,6 +58,7 @@ public:
|
|||
|
||||
[[nodiscard]] virtual not_null<History*> owningHistory() = 0;
|
||||
|
||||
[[nodiscard]] not_null<Thread*> migrateToOrMe() const;
|
||||
[[nodiscard]] not_null<const History*> owningHistory() const {
|
||||
return const_cast<Thread*>(this)->owningHistory();
|
||||
}
|
||||
|
@ -110,6 +111,9 @@ public:
|
|||
[[nodiscard]] virtual auto sendActionPainter()
|
||||
-> not_null<HistoryView::SendActionPainter*> = 0;
|
||||
|
||||
[[nodiscard]] bool hasPinnedMessages() const;
|
||||
void setHasPinnedMessages(bool has);
|
||||
|
||||
protected:
|
||||
void setUnreadMarkFlag(bool unread);
|
||||
|
||||
|
@ -118,6 +122,7 @@ private:
|
|||
UnreadMark = (1 << 0),
|
||||
Muted = (1 << 1),
|
||||
UnreadThingsKnown = (1 << 2),
|
||||
HasPinnedMessages = (1 << 3),
|
||||
};
|
||||
friend inline constexpr bool is_flag_type(Flag) { return true; }
|
||||
|
||||
|
|
|
@ -363,7 +363,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
|||
user->setBotInfoVersion(-1);
|
||||
}
|
||||
if (const auto pinned = update.vpinned_msg_id()) {
|
||||
SetTopPinnedMessageId(user, MsgId(0), pinned->v);
|
||||
SetTopPinnedMessageId(user, pinned->v);
|
||||
}
|
||||
const auto canReceiveGifts = (update.vflags().v
|
||||
& MTPDuserFull::Flag::f_premium_gifts)
|
||||
|
|
|
@ -508,16 +508,25 @@ void History::destroyMessagesByTopic(MsgId topicRootId) {
|
|||
}
|
||||
}
|
||||
|
||||
void History::unpinAllMessages() {
|
||||
session().storage().remove(
|
||||
Storage::SharedMediaRemoveAll(
|
||||
peer->id,
|
||||
Storage::SharedMediaType::Pinned));
|
||||
setHasPinnedMessages(false);
|
||||
for (const auto &message : _messages) {
|
||||
if (message->isPinned()) {
|
||||
message->setIsPinned(false);
|
||||
void History::unpinMessagesFor(MsgId topicRootId) {
|
||||
if (!topicRootId) {
|
||||
session().storage().remove(
|
||||
Storage::SharedMediaRemoveAll(
|
||||
peer->id,
|
||||
Storage::SharedMediaType::Pinned));
|
||||
setHasPinnedMessages(false);
|
||||
if (const auto forum = peer->forum()) {
|
||||
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()) {
|
||||
auto from = loadedAtTop() ? 0 : minMsgId();
|
||||
auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
|
||||
session().storage().add(Storage::SharedMediaAddExisting(
|
||||
auto &storage = session().storage();
|
||||
storage.add(Storage::SharedMediaAddExisting(
|
||||
peer->id,
|
||||
MsgId(0),
|
||||
MsgId(0), // topicRootId
|
||||
sharedMediaTypes,
|
||||
item->id,
|
||||
{ from, till }));
|
||||
if (sharedMediaTypes.test(Storage::SharedMediaType::Pinned)) {
|
||||
const auto pinned = sharedMediaTypes.test(
|
||||
Storage::SharedMediaType::Pinned);
|
||||
if (pinned) {
|
||||
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) {
|
||||
|
@ -1064,11 +1087,20 @@ void History::applyServiceChanges(
|
|||
if (item) {
|
||||
session().storage().add(Storage::SharedMediaAddSlice(
|
||||
peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
MsgId(0),
|
||||
Storage::SharedMediaType::Pinned,
|
||||
{ id },
|
||||
{ id, ServerMaxMsgId }));
|
||||
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(
|
||||
const std::vector<not_null<HistoryItem*>> &items) {
|
||||
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
|
||||
auto topicsWithPinned = base::flat_set<not_null<Data::ForumTopic*>>();
|
||||
for (const auto &item : items) {
|
||||
if (const auto types = item->sharedMediaTypes()) {
|
||||
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
|
||||
|
@ -1473,6 +1506,13 @@ void History::addToSharedMedia(
|
|||
medias[i].reserve(items.size());
|
||||
}
|
||||
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() {
|
||||
|
@ -1750,7 +1793,7 @@ void History::setUnreadMark(bool unread) {
|
|||
}
|
||||
|
||||
void History::setFakeUnreadWhileOpened(bool enabled) {
|
||||
if (_fakeUnreadWhileOpened == enabled) {
|
||||
if (fakeUnreadWhileOpened() == enabled) {
|
||||
return;
|
||||
} else if (enabled) {
|
||||
if (!inChatList()) {
|
||||
|
@ -1761,12 +1804,16 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
_fakeUnreadWhileOpened = enabled;
|
||||
if (enabled) {
|
||||
_flags |= Flag::FakeUnreadWhileOpened;
|
||||
} else {
|
||||
_flags &= ~Flag::FakeUnreadWhileOpened;
|
||||
}
|
||||
owner().chatsFilters().refreshHistory(this);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool History::fakeUnreadWhileOpened() const {
|
||||
return _fakeUnreadWhileOpened;
|
||||
return (_flags & Flag::FakeUnreadWhileOpened);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (isTopPromoted() == promoted) {
|
||||
return;
|
||||
|
|
|
@ -140,7 +140,7 @@ public:
|
|||
void destroyMessagesByDates(TimeId minDate, TimeId maxDate);
|
||||
void destroyMessagesByTopic(MsgId topicRootId);
|
||||
|
||||
void unpinAllMessages();
|
||||
void unpinMessagesFor(MsgId topicRootId);
|
||||
|
||||
not_null<HistoryItem*> addNewMessage(
|
||||
MsgId id,
|
||||
|
@ -426,9 +426,6 @@ public:
|
|||
void setInboxReadTill(MsgId upTo);
|
||||
std::optional<int> countStillUnreadLocal(MsgId readTillId) const;
|
||||
|
||||
[[nodiscard]] bool hasPinnedMessages() const;
|
||||
void setHasPinnedMessages(bool has);
|
||||
|
||||
[[nodiscard]] bool isTopPromoted() const;
|
||||
|
||||
const not_null<PeerData*> peer;
|
||||
|
@ -461,6 +458,8 @@ private:
|
|||
HasPendingResizedItems = (1 << 0),
|
||||
IsTopPromoted = (1 << 1),
|
||||
IsForum = (1 << 2),
|
||||
FakeUnreadWhileOpened = (1 << 3),
|
||||
HasPinnedMessages = (1 << 4),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) {
|
||||
|
@ -610,9 +609,6 @@ private:
|
|||
|
||||
QString _chatListNameSortKey;
|
||||
|
||||
bool _fakeUnreadWhileOpened = false;
|
||||
bool _hasPinnedMessages = false;
|
||||
|
||||
// A pointer to the block that is currently being built.
|
||||
// We hold this pointer so we can destroy it while building
|
||||
// and then create a new one if it is necessary.
|
||||
|
|
|
@ -480,13 +480,23 @@ void HistoryItem::setIsPinned(bool pinned) {
|
|||
const auto changed = (isPinned() != pinned);
|
||||
if (pinned) {
|
||||
_flags |= MessageFlag::Pinned;
|
||||
history()->session().storage().add(Storage::SharedMediaAddExisting(
|
||||
auto &storage = history()->session().storage();
|
||||
storage.add(Storage::SharedMediaAddExisting(
|
||||
history()->peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
Storage::SharedMediaType::Pinned,
|
||||
id,
|
||||
{ id, id }));
|
||||
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 {
|
||||
_flags &= ~MessageFlag::Pinned;
|
||||
history()->session().storage().remove(Storage::SharedMediaRemoveOne(
|
||||
|
@ -734,6 +744,9 @@ void HistoryItem::indexAsNewItem() {
|
|||
id));
|
||||
if (types.test(Storage::SharedMediaType::Pinned)) {
|
||||
_history->setHasPinnedMessages(true);
|
||||
if (const auto topic = this->topic()) {
|
||||
topic->setHasPinnedMessages(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1423,7 +1436,12 @@ ClickHandlerPtr goToMessageClickHandler(
|
|||
params.origin = Window::SectionShow::OriginMessage{
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -859,7 +859,8 @@ bool HistoryService::updateDependent(bool force) {
|
|||
(dependent->peerId
|
||||
? history()->owner().peer(dependent->peerId)
|
||||
: history()->peer),
|
||||
dependent->msgId);
|
||||
dependent->msgId,
|
||||
fullId());
|
||||
}
|
||||
auto gotDependencyItem = false;
|
||||
if (!dependent->msg) {
|
||||
|
|
|
@ -73,6 +73,14 @@ float64 ElementHighlighter::progress(
|
|||
void ElementHighlighter::highlight(FullMsgId itemId) {
|
||||
if (const auto item = _data->message(itemId)) {
|
||||
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;
|
||||
_animation.start();
|
||||
|
||||
|
|
|
@ -603,6 +603,18 @@ HistoryWidget::HistoryWidget(
|
|||
}
|
||||
}, 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;
|
||||
session().changes().historyUpdates(
|
||||
HistoryUpdateFlag::MessageSent
|
||||
|
@ -614,14 +626,7 @@ HistoryWidget::HistoryWidget(
|
|||
| HistoryUpdateFlag::UnreadView
|
||||
| HistoryUpdateFlag::TopPromoted
|
||||
| HistoryUpdateFlag::ClientSideMessages
|
||||
| HistoryUpdateFlag::PinnedMessages
|
||||
) | rpl::filter([=](const Data::HistoryUpdate &update) {
|
||||
if (_migrated && update.history.get() == _migrated) {
|
||||
if (_pinnedTracker
|
||||
&& (update.flags & HistoryUpdateFlag::PinnedMessages)) {
|
||||
checkPinnedBarState();
|
||||
}
|
||||
}
|
||||
return (_history == update.history.get());
|
||||
}) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
|
||||
const auto flags = update.flags;
|
||||
|
@ -647,9 +652,6 @@ HistoryWidget::HistoryWidget(
|
|||
if (flags & HistoryUpdateFlag::UnreadView) {
|
||||
unreadCountUpdated();
|
||||
}
|
||||
if (_pinnedTracker && (flags & HistoryUpdateFlag::PinnedMessages)) {
|
||||
checkPinnedBarState();
|
||||
}
|
||||
if (flags & HistoryUpdateFlag::TopPromoted) {
|
||||
updateHistoryGeometry();
|
||||
updateControlsVisibility();
|
||||
|
@ -3365,15 +3367,16 @@ void HistoryWidget::handleScroll() {
|
|||
if (!_itemsRevealHeight) {
|
||||
updatePinnedViewer();
|
||||
}
|
||||
const auto now = crl::now();
|
||||
if (!_synteticScrollEvent) {
|
||||
_lastUserScrolled = crl::now();
|
||||
_lastUserScrolled = now;
|
||||
}
|
||||
const auto scrollTop = _scroll->scrollTop();
|
||||
if (scrollTop != _lastScrollTop) {
|
||||
if (!_synteticScrollEvent) {
|
||||
checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop);
|
||||
}
|
||||
_lastScrolled = crl::now();
|
||||
_lastScrolled = now;
|
||||
_lastScrollTop = scrollTop;
|
||||
}
|
||||
}
|
||||
|
@ -6892,6 +6895,7 @@ void HistoryWidget::hidePinnedMessage() {
|
|||
Window::HidePinnedBar(
|
||||
controller(),
|
||||
_peer,
|
||||
MsgId(0), // topicRootId
|
||||
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() {
|
||||
auto progress = _a_show.value(1.);
|
||||
if (!_a_show.animating()) {
|
||||
|
|
|
@ -680,11 +680,15 @@ bool AddPinMessageAction(
|
|||
not_null<ListWidget*> list) {
|
||||
const auto context = list->elementContext();
|
||||
const auto item = request.item;
|
||||
if (!item
|
||||
|| !item->isRegular()
|
||||
|| (context != Context::History && context != Context::Pinned)) {
|
||||
if (!item || !item->isRegular()) {
|
||||
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 pinItem = ((item->canPin() && item->isPinned()) || !group)
|
||||
? item
|
||||
|
|
|
@ -411,7 +411,7 @@ ListWidget::ListWidget(
|
|||
|
||||
_selectScroll.scrolls(
|
||||
) | rpl::start_with_next([=](int d) {
|
||||
delegate->listScrollTo(_visibleTop + d);
|
||||
delegate->listScrollTo(_visibleTop + d, false);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
|
@ -1378,6 +1378,37 @@ bool ListWidget::hasSelectRestriction() const {
|
|||
!= 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 {
|
||||
return st::msgMarginTopAttached
|
||||
+ st::msgPhotoSize
|
||||
|
@ -2712,7 +2743,9 @@ void ListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
|
||||
void ListWidget::touchScrollUpdated(const QPoint &screenPos) {
|
||||
_touchPos = screenPos;
|
||||
_delegate->listScrollTo(_visibleTop - (_touchPos - _touchPrevPos).y());
|
||||
_delegate->listScrollTo(
|
||||
_visibleTop - (_touchPos - _touchPrevPos).y(),
|
||||
false);
|
||||
touchUpdateSpeed();
|
||||
}
|
||||
|
||||
|
@ -3083,15 +3116,8 @@ void ListWidget::mouseActionFinish(
|
|||
mouseActionCancel();
|
||||
ActivateClickHandler(window(), activated, {
|
||||
button,
|
||||
QVariant::fromValue(ClickHandlerContext{
|
||||
.itemId = pressState.itemId,
|
||||
.elementDelegate = [weak = Ui::MakeWeak(this)] {
|
||||
return weak
|
||||
? (ElementDelegate*)weak
|
||||
: nullptr;
|
||||
},
|
||||
.sessionWindow = base::make_weak(_controller),
|
||||
})
|
||||
QVariant::fromValue(
|
||||
prepareClickHandlerContext(pressState.itemId))
|
||||
});
|
||||
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() {
|
||||
auto mousePosition = mapFromGlobal(_mousePosition);
|
||||
auto point = QPoint(
|
||||
|
|
|
@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_view_highlight_manager.h"
|
||||
#include "history/history_view_top_toast.h"
|
||||
|
||||
struct ClickHandlerContext;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -87,7 +89,7 @@ using SelectedItems = std::vector<SelectedItem>;
|
|||
class ListDelegate {
|
||||
public:
|
||||
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 listDeleteRequest() = 0;
|
||||
virtual rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
@ -246,6 +248,11 @@ public:
|
|||
[[nodiscard]] bool showCopyRestrictionForSelected();
|
||||
[[nodiscard]] bool hasSelectRestriction() const;
|
||||
|
||||
[[nodiscard]] std::pair<Element*, int> findViewForPinnedTracking(
|
||||
int top) const;
|
||||
[[nodiscard]] ClickHandlerContext prepareClickHandlerContext(
|
||||
FullMsgId id);
|
||||
|
||||
// AbstractTooltipShower interface
|
||||
QString tooltipText() const override;
|
||||
QPoint tooltipPos() const override;
|
||||
|
|
|
@ -59,12 +59,12 @@ namespace {
|
|||
} // namespace
|
||||
|
||||
PinnedMemento::PinnedMemento(
|
||||
not_null<History*> history,
|
||||
not_null<Data::Thread*> thread,
|
||||
UniversalMsgId highlightId)
|
||||
: _history(history)
|
||||
: _thread(thread)
|
||||
, _highlightId(highlightId) {
|
||||
_list.setAroundPosition({
|
||||
.fullId = FullMsgId(history->peer->id, highlightId),
|
||||
.fullId = FullMsgId(_thread->owningHistory()->peer->id, highlightId),
|
||||
.date = TimeId(0),
|
||||
});
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ object_ptr<Window::SectionWidget> PinnedMemento::createWidget(
|
|||
auto result = object_ptr<PinnedWidget>(
|
||||
parent,
|
||||
controller,
|
||||
_history);
|
||||
_thread);
|
||||
result->setInternalState(geometry, this);
|
||||
return result;
|
||||
}
|
||||
|
@ -88,10 +88,13 @@ object_ptr<Window::SectionWidget> PinnedMemento::createWidget(
|
|||
PinnedWidget::PinnedWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> history)
|
||||
: Window::SectionWidget(parent, controller, history->peer)
|
||||
, _history(history->migrateToOrMe())
|
||||
, _migratedPeer(_history->peer->migrateFrom())
|
||||
not_null<Data::Thread*> thread)
|
||||
: Window::SectionWidget(parent, controller, thread->owningHistory()->peer)
|
||||
, _thread(thread->migrateToOrMe())
|
||||
, _history(thread->owningHistory())
|
||||
, _migratedPeer(thread->asHistory()
|
||||
? thread->asHistory()->peer->migrateFrom()
|
||||
: nullptr)
|
||||
, _topBar(this, controller)
|
||||
, _topBarShadow(this)
|
||||
, _scroll(std::make_unique<Ui::ScrollArea>(
|
||||
|
@ -113,7 +116,7 @@ PinnedWidget::PinnedWidget(
|
|||
|
||||
Window::ChatThemeValueFromPeer(
|
||||
controller,
|
||||
history->peer
|
||||
thread->owningHistory()->peer
|
||||
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> &&theme) {
|
||||
_theme = std::move(theme);
|
||||
controller->setChatStyleTheme(_theme);
|
||||
|
@ -121,7 +124,7 @@ PinnedWidget::PinnedWidget(
|
|||
|
||||
_topBar->setActiveChat(
|
||||
TopBarWidget::ActiveChat{
|
||||
.key = _history,
|
||||
.key = _thread,
|
||||
.section = Dialogs::EntryState::Section::Pinned,
|
||||
},
|
||||
nullptr);
|
||||
|
@ -181,9 +184,10 @@ void PinnedWidget::setupClearButton() {
|
|||
Window::HidePinnedBar(
|
||||
controller(),
|
||||
_history->peer,
|
||||
_thread->topicRootId(),
|
||||
crl::guard(this, callback));
|
||||
} else {
|
||||
Window::UnpinAllMessages(controller(), _history);
|
||||
Window::UnpinAllMessages(controller(), _thread);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -194,7 +198,7 @@ void PinnedWidget::cornerButtonsShowAtPosition(
|
|||
}
|
||||
|
||||
Data::Thread *PinnedWidget::cornerButtonsThread() {
|
||||
return _history;
|
||||
return _thread;
|
||||
}
|
||||
|
||||
FullMsgId PinnedWidget::cornerButtonsCurrentId() {
|
||||
|
@ -238,13 +242,13 @@ void PinnedWidget::updateAdaptiveLayout() {
|
|||
_topBar->height());
|
||||
}
|
||||
|
||||
not_null<History*> PinnedWidget::history() const {
|
||||
return _history;
|
||||
not_null<Data::Thread*> PinnedWidget::thread() const {
|
||||
return _thread;
|
||||
}
|
||||
|
||||
Dialogs::RowDescriptor PinnedWidget::activeChat() const {
|
||||
return {
|
||||
_history,
|
||||
_thread,
|
||||
FullMsgId(_history->peer->id, ShowAtUnreadMsgId)
|
||||
};
|
||||
}
|
||||
|
@ -265,8 +269,8 @@ bool PinnedWidget::showInternal(
|
|||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms) {
|
||||
if (auto logMemento = dynamic_cast<PinnedMemento*>(memento.get())) {
|
||||
if (logMemento->getHistory() == history()
|
||||
|| logMemento->getHistory()->migrateToOrMe() == history()) {
|
||||
if (logMemento->getThread() == thread()
|
||||
|| logMemento->getThread()->migrateToOrMe() == thread()) {
|
||||
restoreState(logMemento);
|
||||
return true;
|
||||
}
|
||||
|
@ -283,7 +287,7 @@ void PinnedWidget::setInternalState(
|
|||
}
|
||||
|
||||
std::shared_ptr<Window::SectionMemento> PinnedWidget::createMemento() {
|
||||
auto result = std::make_shared<PinnedMemento>(history());
|
||||
auto result = std::make_shared<PinnedMemento>(thread());
|
||||
saveState(result.get());
|
||||
return result;
|
||||
}
|
||||
|
@ -431,7 +435,7 @@ Context PinnedWidget::listContext() {
|
|||
return Context::Pinned;
|
||||
}
|
||||
|
||||
bool PinnedWidget::listScrollTo(int top) {
|
||||
bool PinnedWidget::listScrollTo(int top, bool syntetic) {
|
||||
top = std::clamp(top, 0, _scroll->scrollTopMax());
|
||||
if (_scroll->scrollTop() == top) {
|
||||
updateInnerVisibleArea();
|
||||
|
@ -462,11 +466,11 @@ rpl::producer<Data::MessagesSlice> PinnedWidget::listSource(
|
|||
: (ServerMaxMsgId - 1);
|
||||
|
||||
return SharedMediaMergedViewer(
|
||||
&_history->session(),
|
||||
&_thread->session(),
|
||||
SharedMediaMergedKey(
|
||||
SparseIdsMergedSlice::Key(
|
||||
_history->peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
_thread->topicRootId(),
|
||||
_migratedPeer ? _migratedPeer->id : 0,
|
||||
messageId),
|
||||
Storage::SharedMediaType::Pinned),
|
||||
|
|
|
@ -42,10 +42,10 @@ public:
|
|||
PinnedWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> history);
|
||||
not_null<Data::Thread*> thread);
|
||||
~PinnedWidget();
|
||||
|
||||
[[nodiscard]] not_null<History*> history() const;
|
||||
[[nodiscard]] not_null<Data::Thread*> thread() const;
|
||||
Dialogs::RowDescriptor activeChat() const override;
|
||||
|
||||
bool hasTopBarShadow() const override {
|
||||
|
@ -79,7 +79,7 @@ public:
|
|||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
bool listScrollTo(int top) override;
|
||||
bool listScrollTo(int top, bool syntetic = true) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
@ -165,6 +165,7 @@ private:
|
|||
void setMessagesCount(int count);
|
||||
void refreshClearButtonText();
|
||||
|
||||
const not_null<Data::Thread*> _thread;
|
||||
const not_null<History*> _history;
|
||||
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||
PeerData *_migratedPeer = nullptr;
|
||||
|
@ -187,7 +188,7 @@ public:
|
|||
using UniversalMsgId = MsgId;
|
||||
|
||||
explicit PinnedMemento(
|
||||
not_null<History*> history,
|
||||
not_null<Data::Thread*> thread,
|
||||
UniversalMsgId highlightId = 0);
|
||||
|
||||
object_ptr<Window::SectionWidget> createWidget(
|
||||
|
@ -196,8 +197,8 @@ public:
|
|||
Window::Column column,
|
||||
const QRect &geometry) override;
|
||||
|
||||
[[nodiscard]] not_null<History*> getHistory() const {
|
||||
return _history;
|
||||
[[nodiscard]] not_null<Data::Thread*> getThread() const {
|
||||
return _thread;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<ListMemento*> list() {
|
||||
|
@ -208,7 +209,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
const not_null<History*> _history;
|
||||
const not_null<Data::Thread*> _thread;
|
||||
const UniversalMsgId _highlightId = 0;
|
||||
ListMemento _list;
|
||||
|
||||
|
|
|
@ -27,24 +27,26 @@ constexpr auto kChangeViewerLimit = 2;
|
|||
|
||||
} // namespace
|
||||
|
||||
PinnedTracker::PinnedTracker(not_null<History*> history)
|
||||
: _history(history->migrateToOrMe())
|
||||
, _migratedPeer(_history->peer->migrateFrom()) {
|
||||
PinnedTracker::PinnedTracker(not_null<Data::Thread*> thread)
|
||||
: _thread(thread->migrateToOrMe())
|
||||
, _migratedPeer(_thread->asHistory()
|
||||
? _thread->asHistory()->peer->migrateFrom()
|
||||
: nullptr) {
|
||||
using namespace rpl::mappers;
|
||||
const auto has = [&](History *history) -> rpl::producer<bool> {
|
||||
auto &changes = _history->session().changes();
|
||||
const auto flag = Data::HistoryUpdate::Flag::PinnedMessages;
|
||||
if (!history) {
|
||||
const auto has = [&](Data::Thread *thread) -> rpl::producer<bool> {
|
||||
auto &changes = _thread->session().changes();
|
||||
const auto flag = Data::EntryUpdate::Flag::HasPinnedMessages;
|
||||
if (!thread) {
|
||||
return rpl::single(false);
|
||||
}
|
||||
return changes.historyFlagsValue(history, flag) | rpl::map([=] {
|
||||
return history->hasPinnedMessages();
|
||||
return changes.entryFlagsValue(thread, flag) | rpl::map([=] {
|
||||
return thread->hasPinnedMessages();
|
||||
});
|
||||
};
|
||||
rpl::combine(
|
||||
has(_history),
|
||||
has(_thread),
|
||||
has(_migratedPeer
|
||||
? _history->owner().history(_migratedPeer).get()
|
||||
? _thread->owner().history(_migratedPeer).get()
|
||||
: nullptr),
|
||||
_1 || _2
|
||||
) | rpl::distinct_until_changed(
|
||||
|
@ -77,12 +79,13 @@ void PinnedTracker::refreshViewer() {
|
|||
}
|
||||
_dataLifetime.destroy();
|
||||
_viewerAroundId = _aroundId;
|
||||
const auto peer = _thread->owningHistory()->peer;
|
||||
SharedMediaMergedViewer(
|
||||
&_history->peer->session(),
|
||||
&peer->session(),
|
||||
SharedMediaMergedKey(
|
||||
SparseIdsMergedSlice::Key(
|
||||
_history->peer->id,
|
||||
MsgId(0), // topicRootId
|
||||
peer->id,
|
||||
_thread->topicRootId(),
|
||||
_migratedPeer ? _migratedPeer->id : 0,
|
||||
_viewerAroundId),
|
||||
Storage::SharedMediaType::Pinned),
|
||||
|
@ -100,9 +103,9 @@ void PinnedTracker::refreshViewer() {
|
|||
}
|
||||
refreshCurrentFromSlice();
|
||||
if (_slice.fullCount == 0) {
|
||||
_history->setHasPinnedMessages(false);
|
||||
_thread->setHasPinnedMessages(false);
|
||||
if (_migratedPeer) {
|
||||
const auto to = _history->owner().history(_migratedPeer);
|
||||
const auto to = _thread->owner().history(_migratedPeer);
|
||||
to->setHasPinnedMessages(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ class History;
|
|||
|
||||
namespace Data {
|
||||
enum class LoadDirection : char;
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
|
@ -21,7 +22,7 @@ class PinnedTracker final {
|
|||
public:
|
||||
using UniversalMsgId = MsgId;
|
||||
|
||||
explicit PinnedTracker(not_null<History*> history);
|
||||
explicit PinnedTracker(not_null<Data::Thread*> thread);
|
||||
~PinnedTracker();
|
||||
|
||||
[[nodiscard]] rpl::producer<PinnedId> shownMessageId() const;
|
||||
|
@ -44,7 +45,7 @@ private:
|
|||
void refreshViewer();
|
||||
void refreshCurrentFromSlice();
|
||||
|
||||
const not_null<History*> _history;
|
||||
const not_null<Data::Thread*> _thread;
|
||||
PeerData *_migratedPeer = nullptr;
|
||||
|
||||
rpl::variable<PinnedId> _current;
|
||||
|
|
|
@ -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_contact_status.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_drag_area.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/widgets/scroll_area.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/layers/generic_box.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/toasts/common_toasts.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "api/api_bot.h"
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_editing.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/application.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "mainwidget.h"
|
||||
#include "data/data_session.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_peer_values.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_shared_media.h"
|
||||
#include "data/data_send_action.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "facades.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -270,6 +279,9 @@ RepliesWidget::RepliesWidget(
|
|||
if (_rootView) {
|
||||
_rootView->raise();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->raise();
|
||||
}
|
||||
if (_topicReopenBar) {
|
||||
_topicReopenBar->bar().raise();
|
||||
}
|
||||
|
@ -358,9 +370,13 @@ RepliesWidget::RepliesWidget(
|
|||
}, lifetime());
|
||||
}
|
||||
|
||||
setupComposeControls();
|
||||
setupTopicViewer();
|
||||
setupComposeControls();
|
||||
orderWidgets();
|
||||
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->finishAnimating();
|
||||
}
|
||||
}
|
||||
|
||||
RepliesWidget::~RepliesWidget() {
|
||||
|
@ -384,6 +400,9 @@ void RepliesWidget::orderWidgets() {
|
|||
if (_rootView) {
|
||||
_rootView->raise();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->raise();
|
||||
}
|
||||
_topBarShadow->raise();
|
||||
_composeControls->raisePanels();
|
||||
}
|
||||
|
@ -483,9 +502,11 @@ void RepliesWidget::subscribeToTopic() {
|
|||
) | rpl::start_with_next([=] {
|
||||
const auto height = _topicReopenBar->bar().height();
|
||||
_scrollTopDelta = (height - _topicReopenBarHeight);
|
||||
_topicReopenBarHeight = height;
|
||||
updateControlsGeometry();
|
||||
_scrollTopDelta = 0;
|
||||
if (_scrollTopDelta) {
|
||||
_topicReopenBarHeight = height;
|
||||
updateControlsGeometry();
|
||||
_scrollTopDelta = 0;
|
||||
}
|
||||
}, _topicReopenBar->bar().lifetime());
|
||||
|
||||
using Flag = Data::TopicUpdate::Flag;
|
||||
|
@ -510,6 +531,21 @@ void RepliesWidget::subscribeToTopic() {
|
|||
anim::activation::background));
|
||||
}, _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();
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
Data::MessagePosition position) {
|
||||
showAtPosition(position);
|
||||
|
@ -1538,6 +1843,9 @@ QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &pa
|
|||
if (_rootView) {
|
||||
_rootView->hide();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->hide();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1734,13 +2042,18 @@ void RepliesWidget::updateControlsGeometry() {
|
|||
if (_rootView) {
|
||||
_rootView->resizeToWidth(contentWidth);
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->move(0, _topBar->height());
|
||||
_pinnedBar->resizeToWidth(contentWidth);
|
||||
}
|
||||
|
||||
const auto bottom = height();
|
||||
const auto controlsHeight = _joinGroup
|
||||
? _joinGroup->height()
|
||||
: _composeControls->heightCurrent();
|
||||
auto top = _topBar->height()
|
||||
+ _rootViewHeight;
|
||||
+ _rootViewHeight
|
||||
+ _pinnedBarHeight;
|
||||
if (_topicReopenBar) {
|
||||
_topicReopenBar->bar().move(0, top);
|
||||
top += _topicReopenBar->bar().height();
|
||||
|
@ -1807,8 +2120,15 @@ void RepliesWidget::updateInnerVisibleArea() {
|
|||
const auto scrollTop = _scroll->scrollTop();
|
||||
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
|
||||
updatePinnedVisibility();
|
||||
updatePinnedViewer();
|
||||
_cornerButtons.updateJumpDownVisibility();
|
||||
_cornerButtons.updateUnreadThingsVisibility();
|
||||
if (_lastScrollTop != scrollTop) {
|
||||
if (!_synteticScrollEvent) {
|
||||
checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop);
|
||||
}
|
||||
_lastScrollTop = scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
void RepliesWidget::updatePinnedVisibility() {
|
||||
|
@ -1875,6 +2195,9 @@ void RepliesWidget::showFinishedHook() {
|
|||
if (_rootView) {
|
||||
_rootView->show();
|
||||
}
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->show();
|
||||
}
|
||||
|
||||
// We should setup the drag area only after
|
||||
// the section animation is finished,
|
||||
|
@ -1895,14 +2218,17 @@ Context RepliesWidget::listContext() {
|
|||
return Context::Replies;
|
||||
}
|
||||
|
||||
bool RepliesWidget::listScrollTo(int top) {
|
||||
bool RepliesWidget::listScrollTo(int top, bool syntetic) {
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
_scroll->scrollToY(top);
|
||||
return true;
|
||||
_synteticScrollEvent = false;
|
||||
return syntetic;
|
||||
}
|
||||
|
||||
void RepliesWidget::listCancelRequest() {
|
||||
|
|
|
@ -67,6 +67,7 @@ class SendActionPainter;
|
|||
class StickerToast;
|
||||
class TopicReopenBar;
|
||||
class EmptyPainter;
|
||||
class PinnedTracker;
|
||||
|
||||
class RepliesWidget final
|
||||
: public Window::SectionWidget
|
||||
|
@ -119,7 +120,7 @@ public:
|
|||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
bool listScrollTo(int top) override;
|
||||
bool listScrollTo(int top, bool syntetic = true) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
@ -242,7 +243,15 @@ private:
|
|||
void replyToMessage(FullMsgId itemId);
|
||||
void refreshTopBarActiveChat();
|
||||
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);
|
||||
bool confirmSendingFiles(
|
||||
|
@ -308,6 +317,13 @@ private:
|
|||
std::unique_ptr<TopicReopenBar> _topicReopenBar;
|
||||
std::unique_ptr<EmptyPainter> _emptyPainter;
|
||||
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;
|
||||
int _rootViewHeight = 0;
|
||||
|
@ -321,6 +337,7 @@ private:
|
|||
HistoryView::CornerButtons _cornerButtons;
|
||||
rpl::lifetime _topicLifetime;
|
||||
|
||||
int _lastScrollTop = 0;
|
||||
int _topicReopenBarHeight = 0;
|
||||
int _scrollTopDelta = 0;
|
||||
|
||||
|
|
|
@ -1050,7 +1050,7 @@ Context ScheduledWidget::listContext() {
|
|||
return Context::History;
|
||||
}
|
||||
|
||||
bool ScheduledWidget::listScrollTo(int top) {
|
||||
bool ScheduledWidget::listScrollTo(int top, bool syntetic) {
|
||||
top = std::clamp(top, 0, _scroll->scrollTopMax());
|
||||
if (_scroll->scrollTop() == top) {
|
||||
updateInnerVisibleArea();
|
||||
|
|
|
@ -101,7 +101,7 @@ public:
|
|||
|
||||
// ListDelegate interface.
|
||||
Context listContext() override;
|
||||
bool listScrollTo(int top) override;
|
||||
bool listScrollTo(int top, bool syntetic = true) override;
|
||||
void listCancelRequest() override;
|
||||
void listDeleteRequest() override;
|
||||
rpl::producer<Data::MessagesSlice> listSource(
|
||||
|
|
|
@ -489,9 +489,10 @@ void TopBarWidget::paintTopBar(Painter &p) {
|
|||
}
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto history = _activeChat.key.history();
|
||||
const auto history = _activeChat.key.owningHistory();
|
||||
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);
|
||||
topic->chatListNameText().drawElided(
|
||||
p,
|
||||
|
|
|
@ -41,48 +41,56 @@ QByteArray SessionSettings::serialize() const {
|
|||
+ sizeof(qint32) * 5
|
||||
+ _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64)
|
||||
+ sizeof(qint32) * 5
|
||||
+ _hiddenPinnedMessages.size() * (sizeof(quint64) + sizeof(qint32))
|
||||
+ sizeof(qint32)
|
||||
+ (_mutePeriods.size() * sizeof(quint64))
|
||||
+ sizeof(qint32);
|
||||
+ sizeof(qint32) * 2
|
||||
+ _hiddenPinnedMessages.size() * (sizeof(quint64) * 3);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
{
|
||||
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream << qint32(kVersionTag) << qint32(kVersion);
|
||||
stream << static_cast<qint32>(_selectorTab);
|
||||
stream << qint32(_groupStickersSectionHidden.size());
|
||||
stream
|
||||
<< qint32(kVersionTag) << qint32(kVersion)
|
||||
<< static_cast<qint32>(_selectorTab)
|
||||
<< qint32(_groupStickersSectionHidden.size());
|
||||
for (const auto &peerId : _groupStickersSectionHidden) {
|
||||
stream << SerializePeerId(peerId);
|
||||
}
|
||||
stream << qint32(_supportSwitch);
|
||||
stream << qint32(_supportFixChatsOrder ? 1 : 0);
|
||||
stream << qint32(_supportTemplatesAutocomplete ? 1 : 0);
|
||||
stream << qint32(_supportChatsTimeSlice.current());
|
||||
stream << autoDownload;
|
||||
stream << qint32(_supportAllSearchResults.current() ? 1 : 0);
|
||||
stream << qint32(_archiveCollapsed.current() ? 1 : 0);
|
||||
stream << qint32(_archiveInMainMenu.current() ? 1 : 0);
|
||||
stream << qint32(_skipArchiveInSearch.current() ? 1 : 0);
|
||||
stream << qint32(_mediaLastPlaybackPosition.size());
|
||||
stream
|
||||
<< qint32(_supportSwitch)
|
||||
<< qint32(_supportFixChatsOrder ? 1 : 0)
|
||||
<< qint32(_supportTemplatesAutocomplete ? 1 : 0)
|
||||
<< qint32(_supportChatsTimeSlice.current())
|
||||
<< autoDownload
|
||||
<< qint32(_supportAllSearchResults.current() ? 1 : 0)
|
||||
<< qint32(_archiveCollapsed.current() ? 1 : 0)
|
||||
<< qint32(_archiveInMainMenu.current() ? 1 : 0)
|
||||
<< qint32(_skipArchiveInSearch.current() ? 1 : 0)
|
||||
<< qint32(_mediaLastPlaybackPosition.size());
|
||||
for (const auto &[id, time] : _mediaLastPlaybackPosition) {
|
||||
stream << quint64(id) << qint64(time);
|
||||
}
|
||||
stream << qint32(0);
|
||||
stream << qint32(_dialogsFiltersEnabled ? 1 : 0);
|
||||
stream << qint32(_supportAllSilent ? 1 : 0);
|
||||
stream << qint32(_photoEditorHintShowsCount);
|
||||
stream << qint32(_hiddenPinnedMessages.size());
|
||||
for (const auto &[key, value] : _hiddenPinnedMessages) {
|
||||
stream << SerializePeerId(key) << qint64(value.bare);
|
||||
}
|
||||
stream << qint32(_mutePeriods.size());
|
||||
stream
|
||||
<< qint32(0) // very old _hiddenPinnedMessages.size());
|
||||
<< qint32(_dialogsFiltersEnabled ? 1 : 0)
|
||||
<< qint32(_supportAllSilent ? 1 : 0)
|
||||
<< qint32(_photoEditorHintShowsCount)
|
||||
<< qint32(0) // old _hiddenPinnedMessages.size());
|
||||
<< qint32(_mutePeriods.size());
|
||||
for (const auto &period : _mutePeriods) {
|
||||
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;
|
||||
}
|
||||
|
@ -139,7 +147,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
|
|||
QByteArray appVideoPipGeometry = app.videoPipGeometry();
|
||||
std::vector<int> appDictionariesEnabled;
|
||||
qint32 appAutoDownloadDictionaries = app.autoDownloadDictionaries() ? 1 : 0;
|
||||
base::flat_map<PeerId, MsgId> hiddenPinnedMessages;
|
||||
base::flat_map<ThreadId, MsgId> hiddenPinnedMessages;
|
||||
qint32 dialogsFiltersEnabled = _dialogsFiltersEnabled ? 1 : 0;
|
||||
qint32 supportAllSilent = _supportAllSilent ? 1 : 0;
|
||||
qint32 photoEditorHintShowsCount = _photoEditorHintShowsCount;
|
||||
|
@ -334,7 +342,9 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
|
|||
"Bad data for SessionSettings::addFromSerialized()"));
|
||||
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()"));
|
||||
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()) {
|
||||
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) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for SessionSettings::addFromSerialized()"));
|
||||
|
@ -561,6 +593,25 @@ rpl::producer<bool> SessionSettings::skipArchiveInSearchChanges() const {
|
|||
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 {
|
||||
return _photoEditorHintShowsCount < kPhotoEditorHintMaxShowsCount;
|
||||
}
|
||||
|
|
|
@ -101,17 +101,13 @@ public:
|
|||
return _hadLegacyCallsPeerToPeerNobody;
|
||||
}
|
||||
|
||||
[[nodiscard]] MsgId hiddenPinnedMessageId(PeerId peerId) const {
|
||||
const auto i = _hiddenPinnedMessages.find(peerId);
|
||||
return (i != end(_hiddenPinnedMessages)) ? i->second : 0;
|
||||
}
|
||||
void setHiddenPinnedMessageId(PeerId peerId, MsgId msgId) {
|
||||
if (msgId) {
|
||||
_hiddenPinnedMessages[peerId] = msgId;
|
||||
} else {
|
||||
_hiddenPinnedMessages.remove(peerId);
|
||||
}
|
||||
}
|
||||
[[nodiscard]] MsgId hiddenPinnedMessageId(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId = 0) const;
|
||||
void setHiddenPinnedMessageId(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId,
|
||||
MsgId msgId);
|
||||
|
||||
[[nodiscard]] bool dialogsFiltersEnabled() const {
|
||||
return _dialogsFiltersEnabled;
|
||||
|
@ -137,6 +133,15 @@ private:
|
|||
static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60;
|
||||
static constexpr auto kPhotoEditorHintMaxShowsCount = 5;
|
||||
|
||||
struct ThreadId {
|
||||
PeerId peerId;
|
||||
MsgId topicRootId;
|
||||
|
||||
friend inline constexpr auto operator<=>(
|
||||
ThreadId,
|
||||
ThreadId) = default;
|
||||
};
|
||||
|
||||
ChatHelpers::SelectorTab _selectorTab; // per-window
|
||||
base::flat_set<PeerId> _groupStickersSectionHidden;
|
||||
bool _hadLegacyCallsPeerToPeerNobody = false;
|
||||
|
@ -145,7 +150,7 @@ private:
|
|||
rpl::variable<bool> _archiveInMainMenu = false;
|
||||
rpl::variable<bool> _skipArchiveInSearch = false;
|
||||
std::vector<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition;
|
||||
base::flat_map<PeerId, MsgId> _hiddenPinnedMessages;
|
||||
base::flat_map<ThreadId, MsgId> _hiddenPinnedMessages;
|
||||
bool _dialogsFiltersEnabled = false;
|
||||
int _photoEditorHintShowsCount = 0;
|
||||
std::vector<TimeId> _mutePeriods;
|
||||
|
|
|
@ -1082,6 +1082,7 @@ bool ReadSetting(
|
|||
for (auto i = v.begin(), e = v.end(); i != e; ++i) {
|
||||
context.sessionSettings().setHiddenPinnedMessageId(
|
||||
DeserializePeerId(i.key()),
|
||||
MsgId(0), // topicRootId
|
||||
MsgId(i.value()));
|
||||
}
|
||||
context.legacyRead = true;
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
void remove(SharedMediaRemoveOne &&query);
|
||||
void remove(SharedMediaRemoveAll &&query);
|
||||
void invalidate(SharedMediaInvalidateBottom &&query);
|
||||
void unload(SharedMediaUnloadThread &&query);
|
||||
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
|
||||
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
|
||||
bool empty(const SharedMediaKey &key) const;
|
||||
|
@ -65,6 +66,10 @@ void Facade::Impl::invalidate(SharedMediaInvalidateBottom &&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 {
|
||||
return _sharedMedia.query(std::move(query));
|
||||
}
|
||||
|
@ -144,6 +149,10 @@ void Facade::invalidate(SharedMediaInvalidateBottom &&query) {
|
|||
_impl->invalidate(std::move(query));
|
||||
}
|
||||
|
||||
void Facade::unload(SharedMediaUnloadThread &&query) {
|
||||
_impl->unload(std::move(query));
|
||||
}
|
||||
|
||||
rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const {
|
||||
return _impl->query(std::move(query));
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ struct SharedMediaAddSlice;
|
|||
struct SharedMediaRemoveOne;
|
||||
struct SharedMediaRemoveAll;
|
||||
struct SharedMediaInvalidateBottom;
|
||||
struct SharedMediaUnloadThread;
|
||||
struct SharedMediaQuery;
|
||||
struct SharedMediaKey;
|
||||
using SharedMediaResult = SparseIdsListResult;
|
||||
|
@ -47,6 +48,7 @@ public:
|
|||
void remove(SharedMediaRemoveOne &&query);
|
||||
void remove(SharedMediaRemoveAll &&query);
|
||||
void invalidate(SharedMediaInvalidateBottom &&query);
|
||||
void unload(SharedMediaUnloadThread &&query);
|
||||
|
||||
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
|
||||
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
|
||||
|
|
|
@ -109,6 +109,10 @@ void SharedMedia::invalidate(SharedMediaInvalidateBottom &&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 {
|
||||
Expects(IsValidSharedMediaType(query.key.type));
|
||||
|
||||
|
|
|
@ -197,6 +197,18 @@ struct SharedMediaSliceUpdate {
|
|||
SparseIdsSliceUpdate data;
|
||||
};
|
||||
|
||||
struct SharedMediaUnloadThread {
|
||||
SharedMediaUnloadThread(
|
||||
PeerId peerId,
|
||||
MsgId topicRootId)
|
||||
: peerId(peerId)
|
||||
, topicRootId(topicRootId) {
|
||||
}
|
||||
|
||||
PeerId peerId = 0;
|
||||
MsgId topicRootId = 0;
|
||||
};
|
||||
|
||||
class SharedMedia {
|
||||
public:
|
||||
using Type = SharedMediaType;
|
||||
|
@ -207,6 +219,7 @@ public:
|
|||
void remove(SharedMediaRemoveOne &&query);
|
||||
void remove(SharedMediaRemoveAll &&query);
|
||||
void invalidate(SharedMediaInvalidateBottom &&query);
|
||||
void unload(SharedMediaUnloadThread &&query);
|
||||
|
||||
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
|
||||
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
|
||||
|
|
|
@ -1651,19 +1651,26 @@ void ToggleMessagePinned(
|
|||
void HidePinnedBar(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
Fn<void()> onHidden) {
|
||||
const auto callback = crl::guard(navigation, [=](Fn<void()> &&close) {
|
||||
close();
|
||||
auto &session = peer->session();
|
||||
const auto migrated = peer->migrateFrom();
|
||||
const auto top = Data::ResolveTopPinnedId(peer, MsgId(0), migrated);
|
||||
const auto migrated = topicRootId ? nullptr : peer->migrateFrom();
|
||||
const auto top = Data::ResolveTopPinnedId(
|
||||
peer,
|
||||
topicRootId,
|
||||
migrated);
|
||||
const auto universal = !top
|
||||
? MsgId(0)
|
||||
: (migrated && !peerIsChannel(top.peer))
|
||||
? (top.msg - ServerMaxMsgId)
|
||||
: top.msg;
|
||||
if (universal) {
|
||||
session.settings().setHiddenPinnedMessageId(peer->id, universal);
|
||||
session.settings().setHiddenPinnedMessageId(
|
||||
peer->id,
|
||||
topicRootId,
|
||||
universal);
|
||||
session.saveSettingsDelayed();
|
||||
if (onHidden) {
|
||||
onHidden();
|
||||
|
@ -1683,11 +1690,18 @@ void HidePinnedBar(
|
|||
|
||||
void UnpinAllMessages(
|
||||
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) {
|
||||
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 history = strong->owningHistory();
|
||||
const auto topicRootId = strong->topicRootId();
|
||||
api->request(MTPmessages_UnpinAllMessages(
|
||||
history->peer->input
|
||||
)).done([=](const MTPmessages_AffectedHistory &result) {
|
||||
|
@ -1696,7 +1710,7 @@ void UnpinAllMessages(
|
|||
if (offset > 0) {
|
||||
self(self);
|
||||
} else {
|
||||
history->unpinAllMessages();
|
||||
history->unpinMessagesFor(topicRootId);
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ class Folder;
|
|||
class Session;
|
||||
struct ForwardDraft;
|
||||
class ForumTopic;
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Dialogs {
|
||||
|
@ -130,9 +131,10 @@ void ToggleMessagePinned(
|
|||
void HidePinnedBar(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
Fn<void()> onHidden);
|
||||
void UnpinAllMessages(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<History*> history);
|
||||
not_null<Data::Thread*> thread);
|
||||
|
||||
} // namespace Window
|
||||
|
|
Loading…
Add table
Reference in a new issue