Support mark as read/unread in sublists.

This commit is contained in:
John Preston 2025-05-30 15:00:33 +04:00
parent abe1962002
commit 3278de9ba1
35 changed files with 497 additions and 91 deletions

View file

@ -1373,6 +1373,32 @@ void ApiWrap::deleteAllFromParticipantSend(
}).send();
}
void ApiWrap::deleteSublistHistory(
not_null<ChannelData*> channel,
not_null<PeerData*> sublistPeer) {
deleteSublistHistorySend(channel, sublistPeer);
}
void ApiWrap::deleteSublistHistorySend(
not_null<ChannelData*> parentChat,
not_null<PeerData*> sublistPeer) {
request(MTPmessages_DeleteSavedHistory(
MTP_flags(MTPmessages_DeleteSavedHistory::Flag::f_parent_peer),
parentChat->input,
sublistPeer->input,
MTP_int(0), // max_id
MTP_int(0), // min_date
MTP_int(0) // max_date
)).done([=](const MTPmessages_AffectedHistory &result) {
const auto offset = applyAffectedHistory(parentChat, result);
if (offset > 0) {
deleteSublistHistorySend(parentChat, sublistPeer);
} else if (const auto monoforum = parentChat->monoforum()) {
monoforum->applySublistDeleted(sublistPeer);
}
}).send();
}
void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
if (!_stickerSetRequests.contains(setId)) {
_stickerSetRequests.emplace(setId, StickerSetRequest{ access });

View file

@ -231,6 +231,9 @@ public:
void deleteAllFromParticipant(
not_null<ChannelData*> channel,
not_null<PeerData*> from);
void deleteSublistHistory(
not_null<ChannelData*> parentChat,
not_null<PeerData*> sublistPeer);
void requestWebPageDelayed(not_null<WebPageData*> page);
void clearWebPageRequest(not_null<WebPageData*> page);
@ -539,6 +542,9 @@ private:
void deleteAllFromParticipantSend(
not_null<ChannelData*> channel,
not_null<PeerData*> from);
void deleteSublistHistorySend(
not_null<ChannelData*> parentChat,
not_null<PeerData*> sublistPeer);
void uploadAlbumMedia(
not_null<HistoryItem*> item,

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_participant_status.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
@ -565,15 +566,7 @@ bool CanCreateModerateMessagesBox(const HistoryItemsList &items) {
&& !options.participants.empty();
}
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto container = box->verticalLayout();
const auto maybeUser = peer->asUser();
const auto isBot = maybeUser && maybeUser->isBot();
Ui::AddSkip(container);
Ui::AddSkip(container);
void SafeSubmitOnEnter(not_null<Ui::GenericBox*> box) {
base::install_event_filter(box, [=](not_null<QEvent*> event) {
if (event->type() == QEvent::KeyPress) {
if (const auto k = static_cast<QKeyEvent*>(event.get())) {
@ -587,12 +580,24 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
},
.confirmText = tr::lng_box_yes(),
.cancelText = tr::lng_box_no(),
}));
}));
}
}
}
return base::EventFilterResult::Continue;
});
}
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
const auto container = box->verticalLayout();
const auto maybeUser = peer->asUser();
const auto isBot = maybeUser && maybeUser->isBot();
Ui::AddSkip(container);
Ui::AddSkip(container);
SafeSubmitOnEnter(box);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
container,
@ -754,3 +759,54 @@ void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer) {
}, st::attentionBoxButton);
box->addButton(tr::lng_cancel(), close);
}
void DeleteSublistBox(
not_null<Ui::GenericBox*> box,
not_null<Data::SavedSublist*> sublist) {
const auto container = box->verticalLayout();
const auto weak = base::make_weak(sublist.get());
const auto peer = sublist->sublistPeer();
Ui::AddSkip(container);
Ui::AddSkip(container);
SafeSubmitOnEnter(box);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
container,
peer,
st::mainMenuUserpic);
Ui::IconWithTitle(
container,
userpic,
Ui::CreateChild<Ui::FlatLabel>(
container,
tr::lng_profile_delete_conversation() | Ui::Text::ToBold(),
box->getDelegate()->style().title));
Ui::AddSkip(container);
Ui::AddSkip(container);
box->addRow(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_sure_delete_history(
lt_contact,
rpl::single(peer->name())),
st::boxLabel));
Ui::AddSkip(container);
const auto close = crl::guard(box, [=] { box->closeBox(); });
box->addButton(tr::lng_box_delete(), [=] {
const auto strong = weak.get();
const auto parentChat = strong ? strong->parentChat() : nullptr;
if (!parentChat) {
return;
}
peer->session().api().deleteSublistHistory(parentChat, peer);
close();
}, st::attentionBoxButton);
box->addButton(tr::lng_cancel(), close);
}

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData;
namespace Data {
class SavedSublist;
} // namespace Data
namespace Ui {
class GenericBox;
} // namespace Ui
@ -21,3 +25,6 @@ void CreateModerateMessagesBox(
[[nodiscard]] bool CanCreateModerateMessagesBox(const HistoryItemsList &);
void DeleteChatBox(not_null<Ui::GenericBox*> box, not_null<PeerData*> peer);
void DeleteSublistBox(
not_null<Ui::GenericBox*> box,
not_null<Data::SavedSublist*> sublist);

View file

@ -714,7 +714,7 @@ void PeerListRow::elementsPaint(
}
QString PeerListRow::generateName() {
return peer()->name();
return peer()->userpicPaintingPeer()->name();
}
QString PeerListRow::generateShortName() {
@ -724,12 +724,12 @@ QString PeerListRow::generateShortName() {
? tr::lng_replies_messages(tr::now)
: _isVerifyCodesChat
? tr::lng_verification_codes(tr::now)
: peer()->shortName();
: peer()->userpicPaintingPeer()->shortName();
}
Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
if (!_userpic.cloud && peer()->hasUserpic()) {
_userpic = peer()->createUserpicView();
if (!_userpic.cloud && peer()->userpicPaintingPeer()->hasUserpic()) {
_userpic = peer()->userpicPaintingPeer()->createUserpicView();
}
return _userpic;
}
@ -738,7 +738,7 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
bool forceRound) {
const auto saved = !_savedMessagesStatus.isEmpty();
const auto replies = _isRepliesMessagesChat;
const auto peer = this->peer();
const auto peer = this->peer()->userpicPaintingPeer();
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
if (forceRound && peer->isForum()) {
return ForceRoundUserpicCallback(peer);

View file

@ -204,6 +204,42 @@ void Changes::topicRemoved(not_null<ForumTopic*> topic) {
_topicChanges.drop(topic);
}
void Changes::sublistUpdated(
not_null<SavedSublist*> sublist,
SublistUpdate::Flags flags) {
const auto drop = (flags & SublistUpdate::Flag::Destroyed);
_sublistChanges.updated(sublist, flags, drop);
if (!drop) {
scheduleNotifications();
}
}
rpl::producer<SublistUpdate> Changes::sublistUpdates(
SublistUpdate::Flags flags) const {
return _sublistChanges.updates(flags);
}
rpl::producer<SublistUpdate> Changes::sublistUpdates(
not_null<SavedSublist*> sublist,
SublistUpdate::Flags flags) const {
return _sublistChanges.updates(sublist, flags);
}
rpl::producer<SublistUpdate> Changes::sublistFlagsValue(
not_null<SavedSublist*> sublist,
SublistUpdate::Flags flags) const {
return _sublistChanges.flagsValue(sublist, flags);
}
rpl::producer<SublistUpdate> Changes::realtimeSublistUpdates(
SublistUpdate::Flag flag) const {
return _sublistChanges.realtimeUpdates(flag);
}
void Changes::sublistRemoved(not_null<SavedSublist*> sublist) {
_sublistChanges.drop(sublist);
}
void Changes::messageUpdated(
not_null<HistoryItem*> item,
MessageUpdate::Flags flags) {
@ -323,6 +359,7 @@ void Changes::sendNotifications() {
_messageChanges.sendNotifications();
_entryChanges.sendNotifications();
_topicChanges.sendNotifications();
_sublistChanges.sendNotifications();
_storyChanges.sendNotifications();
}

View file

@ -38,6 +38,7 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
namespace Data {
class ForumTopic;
class SavedSublist;
class Story;
struct NameUpdate {
@ -184,6 +185,25 @@ struct TopicUpdate {
};
struct SublistUpdate {
enum class Flag : uint32 {
None = 0,
UnreadView = (1U << 1),
UnreadReactions = (1U << 2),
CloudDraft = (1U << 3),
Destroyed = (1U << 4),
LastUsedBit = (1U << 4),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
not_null<SavedSublist*> sublist;
Flags flags = 0;
};
struct MessageUpdate {
enum class Flag : uint32 {
None = 0,
@ -305,6 +325,21 @@ public:
TopicUpdate::Flag flag) const;
void topicRemoved(not_null<ForumTopic*> topic);
void sublistUpdated(
not_null<SavedSublist*> sublist,
SublistUpdate::Flags flags);
[[nodiscard]] rpl::producer<SublistUpdate> sublistUpdates(
SublistUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<SublistUpdate> sublistUpdates(
not_null<SavedSublist*> sublist,
SublistUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<SublistUpdate> sublistFlagsValue(
not_null<SavedSublist*> sublist,
SublistUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<SublistUpdate> realtimeSublistUpdates(
SublistUpdate::Flag flag) const;
void sublistRemoved(not_null<SavedSublist*> sublist);
void messageUpdated(
not_null<HistoryItem*> item,
MessageUpdate::Flags flags);
@ -396,6 +431,7 @@ private:
Manager<PeerData, PeerUpdate> _peerChanges;
Manager<History, HistoryUpdate> _historyChanges;
Manager<ForumTopic, TopicUpdate> _topicChanges;
Manager<SavedSublist, SublistUpdate> _sublistChanges;
Manager<HistoryItem, MessageUpdate> _messageChanges;
Manager<Dialogs::Entry, EntryUpdate> _entryChanges;
Manager<Story, StoryUpdate> _storyChanges;

View file

@ -175,30 +175,31 @@ void Forum::applyTopicDeleted(MsgId rootId) {
_topicsDeleted.emplace(rootId);
const auto i = _topics.find(rootId);
if (i != end(_topics)) {
const auto raw = i->second.get();
Core::App().notifications().clearFromTopic(raw);
owner().removeChatListEntry(raw);
if (ranges::contains(_lastTopics, not_null(raw))) {
reorderLastTopics();
}
_topicDestroyed.fire(raw);
session().changes().topicUpdated(
raw,
Data::TopicUpdate::Flag::Destroyed);
session().changes().entryUpdated(
raw,
Data::EntryUpdate::Flag::Destroyed);
_topics.erase(i);
_history->destroyMessagesByTopic(rootId);
session().storage().unload(Storage::SharedMediaUnloadThread(
_history->peer->id,
rootId));
_history->setForwardDraft(rootId, PeerId(), {});
if (i == end(_topics)) {
return;
}
const auto raw = i->second.get();
Core::App().notifications().clearFromTopic(raw);
owner().removeChatListEntry(raw);
if (ranges::contains(_lastTopics, not_null(raw))) {
reorderLastTopics();
}
_topicDestroyed.fire(raw);
session().changes().topicUpdated(
raw,
Data::TopicUpdate::Flag::Destroyed);
session().changes().entryUpdated(
raw,
Data::EntryUpdate::Flag::Destroyed);
_topics.erase(i);
_history->destroyMessagesByTopic(rootId);
session().storage().unload(Storage::SharedMediaUnloadThread(
_history->peer->id,
rootId));
_history->setForwardDraft(rootId, PeerId(), {});
}
void Forum::reorderLastTopics() {

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "data/business/data_shortcut_messages.h"
#include "data/components/scheduled_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@ -500,6 +501,24 @@ void Histories::changeDialogUnreadMark(
)).send();
}
void Histories::changeSublistUnreadMark(
not_null<Data::SavedSublist*> sublist,
bool unread) {
const auto parent = sublist->parentChat();
if (!parent) {
return;
}
sublist->setUnreadMark(unread);
using Flag = MTPmessages_MarkDialogUnread::Flag;
session().api().request(MTPmessages_MarkDialogUnread(
MTP_flags(Flag::f_parent_peer
| (unread ? Flag::f_unread : Flag(0))),
parent->input,
MTP_inputDialogPeer(sublist->sublistPeer()->input)
)).send();
}
void Histories::requestFakeChatListMessage(
not_null<History*> history) {
if (_fakeChatListRequests.contains(history)) {

View file

@ -26,6 +26,7 @@ namespace Data {
class Session;
class Folder;
struct WebPageDraft;
class SavedSublist;
[[nodiscard]] MTPInputReplyTo ReplyToForMTP(
not_null<History*> history,
@ -71,6 +72,9 @@ public:
Fn<void()> callback = nullptr);
void dialogEntryApplied(not_null<History*> history);
void changeDialogUnreadMark(not_null<History*> history, bool unread);
void changeSublistUnreadMark(
not_null<Data::SavedSublist*> sublist,
bool unread);
void requestFakeChatListMessage(not_null<History*> history);
void requestGroupAround(not_null<HistoryItem*> item);

View file

@ -1174,6 +1174,10 @@ not_null<const PeerData*> PeerData::userpicPaintingPeer() const {
return const_cast<PeerData*>(this)->userpicPaintingPeer();
}
bool PeerData::userpicForceForumShape() const {
return monoforumBroadcast() != nullptr;
}
ChannelData *PeerData::monoforumBroadcast() const {
const auto monoforum = asMonoforum();
return monoforum ? monoforum->monoforumLink() : nullptr;

View file

@ -307,6 +307,7 @@ public:
[[nodiscard]] not_null<const PeerData*> migrateToOrMe() const;
[[nodiscard]] not_null<PeerData*> userpicPaintingPeer();
[[nodiscard]] not_null<const PeerData*> userpicPaintingPeer() const;
[[nodiscard]] bool userpicForceForumShape() const;
// isMonoforum() ? monoforumLink() : nullptr
[[nodiscard]] ChannelData *monoforumBroadcast() const;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_saved_messages.h"
#include "apiwrap.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_user.h"
@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "history/history_unread_things.h"
#include "main/main_session.h"
#include "window/notifications_manager.h"
namespace Data {
namespace {
@ -50,11 +52,13 @@ SavedMessages::SavedMessages(
SavedMessages::~SavedMessages() {
auto &changes = session().changes();
for (const auto &[peer, sublist] : _sublists) {
_owningHistory->setForwardDraft(MsgId(), peer->id, {});
if (_owningHistory) {
for (const auto &[peer, sublist] : _sublists) {
_owningHistory->setForwardDraft(MsgId(), peer->id, {});
const auto raw = sublist.get();
changes.entryRemoved(raw);
const auto raw = sublist.get();
changes.entryRemoved(raw);
}
}
}
@ -308,6 +312,36 @@ void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
});
}
void SavedMessages::applySublistDeleted(not_null<PeerData*> sublistPeer) {
const auto i = _sublists.find(sublistPeer);
if (i == end(_sublists)) {
return;
}
const auto raw = i->second.get();
//Core::App().notifications().clearFromTopic(raw); // #TODO monoforum
owner().removeChatListEntry(raw);
if (ranges::contains(_lastSublists, not_null(raw))) {
reorderLastSublists();
}
_sublistDestroyed.fire(raw);
session().changes().sublistUpdated(
raw,
Data::SublistUpdate::Flag::Destroyed);
session().changes().entryUpdated(
raw,
Data::EntryUpdate::Flag::Destroyed);
_sublists.erase(i);
const auto history = owningHistory();
history->destroyMessagesBySublist(sublistPeer);
//session().storage().unload(Storage::SharedMediaUnloadThread(
// _history->peer->id,
// rootId));
history->setForwardDraft(MsgId(), sublistPeer->id, {});
}
void SavedMessages::reorderLastSublists() {
if (!_parentChat) {
return;

View file

@ -50,6 +50,7 @@ public:
void apply(const MTPDupdatePinnedSavedDialogs &update);
void apply(const MTPDupdateSavedDialogPinned &update);
void applySublistDeleted(not_null<PeerData*> sublistPeer);
void listMessageChanged(HistoryItem *from, HistoryItem *to);
[[nodiscard]] int recentSublistsListVersion() const;

View file

@ -480,6 +480,15 @@ void SavedSublist::setUnreadCount(std::optional<int> count) {
}
}
void SavedSublist::setUnreadMark(bool unread) {
if (unreadMark() == unread) {
return;
}
const auto notifier = unreadStateChangeNotifier(
!unreadCountCurrent());
Thread::setUnreadMarkFlag(unread);
}
bool SavedSublist::unreadCountKnown() const {
return !inMonoforum() || _unreadCount.current().has_value();
}
@ -620,6 +629,9 @@ void SavedSublist::readTill(
if (!IsServerMsgId(tillId)) {
return;
}
if (unreadMark()) {
owner().histories().changeSublistUnreadMark(this, false);
}
const auto was = computeInboxReadTillFull();
const auto now = tillId;
if (now < was) {
@ -713,6 +725,7 @@ void SavedSublist::applyMonoforumDialog(
data.vread_inbox_max_id().v,
data.vunread_count().v);
setOutboxReadTill(data.vread_outbox_max_id().v);
setUnreadMark(data.is_unread_mark());
applyMaybeLast(topItem);
}
@ -1016,10 +1029,16 @@ Dialogs::UnreadState SavedSublist::unreadStateFor(
int count,
bool known) const {
auto result = Dialogs::UnreadState();
const auto mark = !count && unreadMark();
const auto muted = this->muted();
result.messages = count;
result.chats = count ? 1 : 0;
result.marks = mark ? 1 : 0;
result.reactions = unreadReactions().has() ? 1 : 0;
result.messagesMuted = muted ? result.messages : 0;
result.chatsMuted = muted ? result.chats : 0;
result.marksMuted = muted ? result.marks : 0;
result.reactionsMuted = muted ? result.reactions : 0;
result.known = known;
return result;
}

View file

@ -58,13 +58,13 @@ public:
[[nodiscard]] rpl::producer<> changes() const;
[[nodiscard]] std::optional<int> fullCount() const;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
[[nodiscard]] rpl::producer<std::optional<int>> maybeFullCount() const;
void loadFullCount();
[[nodiscard]] bool unreadCountKnown() const;
[[nodiscard]] int unreadCountCurrent() const;
[[nodiscard]] int displayedUnreadCount() const;
[[nodiscard]] rpl::producer<std::optional<int>> unreadCountValue() const;
void setUnreadMark(bool unread);
void applyMonoforumDialog(
const MTPDmonoForumDialog &dialog,

View file

@ -95,6 +95,17 @@ HistoryUnreadThings::ConstProxy Thread::unreadReactions() const {
};
}
bool Thread::canToggleUnread(bool nowUnread) const {
if ((asTopic() || asForum()) && !nowUnread) {
return false;
} else if (asSublist() && owningHistory()->peer->isSelf()) {
return false;
} else if (asHistory() && peer()->amMonoforumAdmin()) {
return false;
}
return true;
}
const base::flat_set<MsgId> &Thread::unreadMentionsIds() const {
if (!_unreadThings) {
static const auto Result = base::flat_set<MsgId>();

View file

@ -80,6 +80,7 @@ public:
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const;
virtual void hasUnreadMentionChanged(bool has) = 0;
virtual void hasUnreadReactionChanged(bool has) = 0;
bool canToggleUnread(bool nowUnread) const;
void removeNotification(not_null<HistoryItem*> item);
void clearNotifications();

View file

@ -287,6 +287,10 @@ void Entry::notifyUnreadStateChange(const UnreadState &wasState) {
}
}
}
} else if (const auto sublist = asSublist()) {
session().changes().sublistUpdated(
sublist,
Data::SublistUpdate::Flag::UnreadView);
}
updateChatListEntryPostponed();
}

View file

@ -398,14 +398,18 @@ void History::applyCloudDraft(MsgId topicRootId, PeerId monoforumPeerId) {
createLocalDraftFromCloud(topicRootId, monoforumPeerId);
if (const auto thread = threadFor(topicRootId, monoforumPeerId)) {
thread->updateChatListSortPosition();
if (!topicRootId) {
session().changes().historyUpdated(
this,
UpdateFlag::CloudDraft);
} else {
if (topicRootId) {
session().changes().topicUpdated(
thread->asTopic(),
Data::TopicUpdate::Flag::CloudDraft);
} else if (monoforumPeerId) {
session().changes().sublistUpdated(
thread->asSublist(),
Data::SublistUpdate::Flag::CloudDraft);
} else {
session().changes().historyUpdated(
this,
UpdateFlag::CloudDraft);
}
}
}
@ -633,6 +637,20 @@ void History::destroyMessagesByTopic(MsgId topicRootId) {
}
}
void History::destroyMessagesBySublist(not_null<PeerData*> sublistPeer) {
auto toDestroy = std::vector<not_null<HistoryItem*>>();
toDestroy.reserve(_items.size());
const auto peerId = sublistPeer->id;
for (const auto &message : _items) {
if (message->sublistPeerId() == peerId) {
toDestroy.push_back(message.get());
}
}
for (const auto item : toDestroy) {
item->destroy();
}
}
void History::unpinMessagesFor(MsgId topicRootId) {
if (!topicRootId) {
session().storage().remove(

View file

@ -139,6 +139,7 @@ public:
void destroyMessage(not_null<HistoryItem*> item);
void destroyMessagesByDates(TimeId minDate, TimeId maxDate);
void destroyMessagesByTopic(MsgId topicRootId);
void destroyMessagesBySublist(not_null<PeerData*> sublistPeer);
void unpinMessagesFor(MsgId topicRootId);

View file

@ -82,6 +82,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "data/components/factchecks.h"
#include "data/components/sponsored_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_channel.h"
@ -4963,9 +4964,17 @@ auto HistoryInner::DelegateMixin()
bool CanSendReply(not_null<const HistoryItem*> item) {
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
if (const auto topic = item->topic()) {
return Data::CanSendAnything(topic);
} else if (!Data::CanSendAnything(peer)) {
return false;
} else if (const auto channel = peer->asChannel()) {
if (const auto sublist = item->savedSublist()) {
if (sublist->sublistPeer() == peer) {
return false;
}
}
return channel->amIn();
}
return true;
}

View file

@ -2151,9 +2151,13 @@ void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
}
}
if (reaction) {
const auto sublist = this->savedSublist();
const auto toHistory = history->unreadReactions().add(id, type);
const auto toTopic = topic && topic->unreadReactions().add(id, type);
if (toHistory || toTopic) {
const auto toSublist = sublist
&& sublist->parentChat()
&& sublist->unreadReactions().add(id, type);
if (toHistory || toTopic || toSublist) {
if (type == HistoryUnreadThings::AddType::New) {
changes->messageUpdated(
this,
@ -2170,6 +2174,11 @@ void HistoryItem::addToUnreadThings(HistoryUnreadThings::AddType type) {
topic,
Data::TopicUpdate::Flag::UnreadReactions);
}
if (toSublist) {
changes->sublistUpdated(
sublist,
Data::SublistUpdate::Flag::UnreadReactions);
}
}
}
}

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/history_unread_things.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@ -38,6 +39,12 @@ template <typename Update>
return UpdateFlag<Data::TopicUpdate>(type);
}
[[nodiscard]] Data::SublistUpdate::Flag SublistUpdateFlag(Type type) {
Expects(type == Type::Reactions);
return Data::SublistUpdate::Flag::UnreadReactions;
}
} // namespace
void Proxy::setCount(int count) {
@ -224,6 +231,10 @@ void Proxy::notifyUpdated() {
topic->session().changes().topicUpdated(
topic,
TopicUpdateFlag(_type));
} else if (const auto sublist = _thread->asSublist()) {
sublist->session().changes().sublistUpdated(
sublist,
SublistUpdateFlag(_type));
}
}

View file

@ -366,8 +366,9 @@ void Item::setupTop() {
? nullptr
: Ui::CreateChild<Ui::UserpicButton>(
_top.get(),
_thread->peer(),
st::previewUserpic);
_thread->peer()->userpicPaintingPeer(),
st::previewUserpic,
_thread->peer()->userpicForceForumShape());
if (userpic) {
userpic->showSavedMessagesOnSelf(true);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);

View file

@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/delete_messages_box.h"
#include "boxes/send_files_box.h"
#include "boxes/premium_limits_box.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "base/call_delayed.h"
@ -58,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "data/components/scheduled_messages.h"
#include "data/data_histories.h"
#include "data/data_saved_messages.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
@ -619,9 +621,7 @@ void ChatWidget::subscribeToTopic() {
_topic->destroyed(
) | rpl::start_with_next([=] {
controller()->showBackFromStack(Window::SectionShow(
anim::type::normal,
anim::activation::background));
closeCurrent();
}, _topicLifetime);
if (!_topic->creating()) {
@ -635,6 +635,17 @@ void ChatWidget::subscribeToTopic() {
_cornerButtons.updateUnreadThingsVisibility();
}
void ChatWidget::closeCurrent() {
const auto thread = controller()->windowId().chat();
if ((_sublist && thread == _sublist) || (_topic && thread == _topic)) {
controller()->window().close();
} else {
controller()->showBackFromStack(Window::SectionShow(
anim::type::normal,
anim::activation::background));
}
}
void ChatWidget::subscribeToPinnedMessages() {
using EntryUpdateFlag = Data::EntryUpdate::Flag;
session().changes().entryUpdates(
@ -2496,9 +2507,7 @@ void ChatWidget::setReplies(std::shared_ptr<Data::RepliesList> replies) {
refreshUnreadCountBadge(count);
}, lifetime());
refreshUnreadCountBadge(_replies->unreadCountKnown()
? _replies->unreadCountCurrent()
: std::optional<int>());
unreadCountUpdated();
const auto isTopic = (_topic != nullptr);
const auto isTopicCreating = isTopic && _topic->creating();
@ -2533,14 +2542,62 @@ void ChatWidget::setReplies(std::shared_ptr<Data::RepliesList> replies) {
void ChatWidget::subscribeToSublist() {
Expects(_sublist != nullptr);
// Must be done before unreadCountUpdated(), or we auto-close.
if (_sublist->unreadMark()) {
_sublist->owner().histories().changeSublistUnreadMark(
_sublist,
false);
}
_sublist->unreadCountValue(
) | rpl::start_with_next([=](std::optional<int> count) {
refreshUnreadCountBadge(count);
}, lifetime());
refreshUnreadCountBadge(_sublist->unreadCountKnown()
? _sublist->unreadCountCurrent()
: std::optional<int>());
using Flag = Data::SublistUpdate::Flag;
session().changes().sublistUpdates(
_sublist,
Flag::UnreadView | Flag::UnreadReactions | Flag::CloudDraft
) | rpl::start_with_next([=](const Data::SublistUpdate &update) {
if (update.flags & Flag::UnreadView) {
unreadCountUpdated();
}
if (update.flags & Flag::UnreadReactions) {
_cornerButtons.updateUnreadThingsVisibility();
}
if (update.flags & Flag::CloudDraft) {
_composeControls->applyCloudDraft();
}
}, lifetime());
_sublist->destroyed(
) | rpl::start_with_next([=] {
closeCurrent();
}, lifetime());
unreadCountUpdated();
}
void ChatWidget::unreadCountUpdated() {
if (_sublist && _sublist->unreadMark()) {
crl::on_main(this, [=] {
const auto guard = Ui::MakeWeak(this);
controller()->showPeerHistory(_sublist->owningHistory());
if (guard) {
closeCurrent();
}
});
} else {
refreshUnreadCountBadge(_replies
? (_replies->unreadCountKnown()
? _replies->unreadCountCurrent()
: std::optional<int>())
: _sublist
? (_sublist->unreadCountKnown()
? _sublist->unreadCountCurrent()
: std::optional<int>())
: std::optional<int>());
}
}
void ChatWidget::restoreState(not_null<ChatMemento*> memento) {

View file

@ -241,6 +241,8 @@ private:
int limitAfter);
void onScroll();
void closeCurrent();
void unreadCountUpdated();
void updateInnerVisibleArea();
void updateControlsGeometry();
void updateAdaptiveLayout();

View file

@ -590,9 +590,12 @@ void SubsectionTabs::refreshSlice() {
});
const auto push = [&](not_null<Data::Thread*> thread) {
const auto topic = thread->asTopic();
const auto sublist = thread->asSublist();
slice.push_back({
.thread = thread,
.badges = thread->chatListBadgesState(),
.badges = ((topic || sublist)
? thread->chatListBadgesState()
: Dialogs::BadgesState()),
.iconId = topic ? topic->iconId() : DocumentId(),
.name = thread->chatListName(),
});

View file

@ -940,7 +940,7 @@ void TopBarWidget::refreshInfoButton() {
Ui::UserpicButton::Role::Custom,
Ui::UserpicButton::Source::PeerPhoto,
st::topBarInfoButton,
infoPeer->monoforumBroadcast() != nullptr);
infoPeer->userpicForceForumShape());
info->showSavedMessagesOnSelf(true);
_info.destroy();
_info = std::move(info);

View file

@ -632,7 +632,7 @@ Cover::Cover(
Ui::UserpicButton::Role::OpenPhoto,
Ui::UserpicButton::Source::PeerPhoto,
_st.photo,
_peer->monoforumBroadcast() != nullptr))
_peer->userpicForceForumShape()))
, _changePersonal((role == Role::Info
|| topic
|| !_peer->isUser()

View file

@ -202,10 +202,12 @@ UserpicButton::UserpicButton(
UserpicButton::UserpicButton(
QWidget *parent,
not_null<PeerData*> peer,
const style::UserpicButton &st)
const style::UserpicButton &st,
bool forceForumShape)
: RippleButton(parent, st.changeButton.ripple)
, _st(st)
, _peer(peer)
, _forceForumShape(forceForumShape)
, _role(Role::Custom)
, _source(Source::PeerPhoto) {
Expects(_role != Role::OpenPhoto);

View file

@ -74,7 +74,8 @@ public:
UserpicButton(
QWidget *parent,
not_null<PeerData*> peer, // Role::Custom, Source::PeerPhoto
const style::UserpicButton &st);
const style::UserpicButton &st,
bool forceForumShape = false);
~UserpicButton();
enum class ChosenType {

View file

@ -499,6 +499,8 @@ void Filler::addTogglePin() {
&& !_sublist
&& !_topic) {
return;
} else if (_sublist && !_peer->isSelf()) {
return;
}
const auto controller = _controller;
const auto filterId = _request.filterId;
@ -526,7 +528,7 @@ void Filler::addTogglePin() {
}
void Filler::addToggleMuteSubmenu(bool addSeparator) {
if (!_thread || _thread->peer()->isSelf()) {
if (!_thread || _thread->peer()->isSelf() || _thread->asSublist()) {
return;
}
PeerMenuAddMuteSubmenuAction(_controller, _thread, _addAction);
@ -550,16 +552,18 @@ void Filler::addSupportInfo() {
}
void Filler::addInfo() {
if (_peer
&& (_peer->isSelf()
|| _peer->isRepliesChat()
|| _peer->isVerifyCodes())) {
const auto sublist = _thread ? _thread->asSublist() : nullptr;
const auto infoPeer = sublist ? sublist->sublistPeer().get() : _peer;
if (infoPeer
&& (infoPeer->isSelf()
|| infoPeer->isRepliesChat()
|| infoPeer->isVerifyCodes())) {
return;
} else if (!_thread) {
return;
} else if (_controller->adaptive().isThreeColumn()) {
const auto thread = _controller->activeChatCurrent().thread();
if (thread && thread == _thread) {
if (thread && !thread->asSublist() && thread == _thread) {
if (Core::App().settings().thirdSectionInfoEnabled()
|| Core::App().settings().tabbedReplacedWithInfo()) {
return;
@ -570,16 +574,16 @@ void Filler::addInfo() {
const auto weak = base::make_weak(_thread);
const auto text = _thread->asTopic()
? tr::lng_context_view_topic(tr::now)
: (_peer->isChat() || _peer->isMegagroup())
: (infoPeer->isChat() || infoPeer->isMegagroup())
? tr::lng_context_view_group(tr::now)
: _peer->isUser()
: infoPeer->isUser()
? tr::lng_context_view_profile(tr::now)
: tr::lng_context_view_channel(tr::now);
_addAction(text, [=] {
if (const auto strong = weak.get()) {
controller->showPeerInfo(strong);
}
}, _peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo);
}, infoPeer->isUser() ? &st::menuIconProfile : &st::menuIconInfo);
}
void Filler::addStoryArchive() {
@ -624,12 +628,9 @@ void Filler::addToggleFolder() {
void Filler::addToggleUnreadMark() {
const auto peer = _peer;
const auto history = _request.key.history();
if (!_thread) {
return;
}
const auto unread = IsUnreadThread(_thread);
if ((_thread->asTopic() || peer->isForum()) && !unread) {
const auto history = _request.key.history();
if (!_thread || !_thread->canToggleUnread(unread)) {
return;
}
const auto weak = base::make_weak(_thread);
@ -643,6 +644,8 @@ void Filler::addToggleUnreadMark() {
}
if (unread) {
MarkAsReadThread(thread);
} else if (const auto sublist = thread->asSublist()) {
peer->owner().histories().changeSublistUnreadMark(sublist, true);
} else if (history) {
peer->owner().histories().changeDialogUnreadMark(history, true);
}
@ -751,14 +754,16 @@ void Filler::addClearHistory() {
}
void Filler::addDeleteChat() {
if (_topic || _peer->isChannel()) {
if (_topic || (!_sublist && _peer->isChannel())) {
return;
}
_addAction({
.text = (_peer->isUser()
.text = ((_peer->isUser() || _sublist)
? tr::lng_profile_delete_conversation(tr::now)
: tr::lng_profile_clear_and_exit(tr::now)),
.handler = DeleteAndLeaveHandler(_controller, _peer),
.handler = (_sublist
? DeleteSublistHandler(_controller, _sublist)
: DeleteAndLeaveHandler(_controller, _peer)),
.icon = &st::menuIconDeleteAttention,
.isAttention = true,
});
@ -766,7 +771,7 @@ void Filler::addDeleteChat() {
void Filler::addLeaveChat() {
const auto channel = _peer->asChannel();
if (_topic || !channel || !channel->amIn()) {
if (_topic || _sublist || !channel || !channel->amIn()) {
return;
}
_addAction({
@ -1263,7 +1268,7 @@ void Filler::addSendGift() {
void Filler::fill() {
if (_folder) {
fillArchiveActions();
} else if (_sublist) {
} else if (_sublist && _peer->isSelf()) {
fillSavedSublistActions();
} else switch (_request.section) {
case Section::ChatsList: fillChatsListActions(); break;
@ -3232,6 +3237,19 @@ Fn<void()> DeleteAndLeaveHandler(
};
}
Fn<void()> DeleteSublistHandler(
not_null<Window::SessionController*> controller,
not_null<Data::SavedSublist*> sublist) {
const auto weak = base::make_weak(sublist.get());
return [=] {
if (const auto strong = weak.get()) {
if (!controller->showFrozenError()) {
controller->show(Box(DeleteSublistBox, strong));
}
}
};
}
void FillDialogsEntryMenu(
not_null<SessionController*> controller,
Dialogs::EntryState request,
@ -3345,8 +3363,7 @@ void MarkAsReadThread(not_null<Data::Thread*> thread) {
if (!IsUnreadThread(thread)) {
return;
} else if (const auto forum = thread->asForum()) {
forum->enumerateTopics([](
not_null<Data::ForumTopic*> topic) {
forum->enumerateTopics([](not_null<Data::ForumTopic*> topic) {
MarkAsReadThread(topic);
});
} else if (const auto history = thread->asHistory()) {
@ -3356,6 +3373,8 @@ void MarkAsReadThread(not_null<Data::Thread*> thread) {
}
} else if (const auto topic = thread->asTopic()) {
topic->readTillEnd();
} else if (const auto sublist = thread->asSublist()) {
sublist->readTillEnd();
}
}

View file

@ -148,6 +148,9 @@ Fn<void()> ClearHistoryHandler(
Fn<void()> DeleteAndLeaveHandler(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
Fn<void()> DeleteSublistHandler(
not_null<Window::SessionController*> controller,
not_null<Data::SavedSublist*> sublist);
object_ptr<Ui::BoxContent> PrepareChooseRecipientBox(
not_null<Main::Session*> session,

View file

@ -1309,6 +1309,9 @@ void SessionNavigation::showPeerInfo(
const SectionShow &params) {
if (const auto topic = thread->asTopic()) {
showSection(std::make_shared<Info::Memento>(topic), params);
} else if (const auto sublist = thread->asSublist()
; sublist && sublist->parentChat()) {
showPeerInfo(sublist->sublistPeer()->id, params);
} else {
showPeerInfo(thread->peer()->id, params);
}