diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 5f4a5fda8b..873fc58b3a 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1373,6 +1373,32 @@ void ApiWrap::deleteAllFromParticipantSend( }).send(); } +void ApiWrap::deleteSublistHistory( + not_null channel, + not_null sublistPeer) { + deleteSublistHistorySend(channel, sublistPeer); +} + +void ApiWrap::deleteSublistHistorySend( + not_null parentChat, + not_null 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 }); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 529114b0a1..a4835adbae 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -231,6 +231,9 @@ public: void deleteAllFromParticipant( not_null channel, not_null from); + void deleteSublistHistory( + not_null parentChat, + not_null sublistPeer); void requestWebPageDelayed(not_null page); void clearWebPageRequest(not_null page); @@ -539,6 +542,9 @@ private: void deleteAllFromParticipantSend( not_null channel, not_null from); + void deleteSublistHistorySend( + not_null parentChat, + not_null sublistPeer); void uploadAlbumMedia( not_null item, diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index fbe6ee72b4..ae25f7a687 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -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 box, not_null 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 box) { base::install_event_filter(box, [=](not_null event) { if (event->type() == QEvent::KeyPress) { if (const auto k = static_cast(event.get())) { @@ -587,12 +580,24 @@ void DeleteChatBox(not_null box, not_null peer) { }, .confirmText = tr::lng_box_yes(), .cancelText = tr::lng_box_no(), - })); + })); } } } return base::EventFilterResult::Continue; }); +} + +void DeleteChatBox(not_null box, not_null 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( container, @@ -754,3 +759,54 @@ void DeleteChatBox(not_null box, not_null peer) { }, st::attentionBoxButton); box->addButton(tr::lng_cancel(), close); } + +void DeleteSublistBox( + not_null box, + not_null 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( + container, + peer, + st::mainMenuUserpic); + Ui::IconWithTitle( + container, + userpic, + Ui::CreateChild( + container, + tr::lng_profile_delete_conversation() | Ui::Text::ToBold(), + box->getDelegate()->style().title)); + + Ui::AddSkip(container); + Ui::AddSkip(container); + + box->addRow( + object_ptr( + 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); +} diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.h b/Telegram/SourceFiles/boxes/moderate_messages_box.h index eb24026125..64e544e4b3 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.h +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.h @@ -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 box, not_null peer); +void DeleteSublistBox( + not_null box, + not_null sublist); diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 5ff2323d27..d57ba16954 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -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); diff --git a/Telegram/SourceFiles/data/data_changes.cpp b/Telegram/SourceFiles/data/data_changes.cpp index 773c50d5d4..6f2a554da9 100644 --- a/Telegram/SourceFiles/data/data_changes.cpp +++ b/Telegram/SourceFiles/data/data_changes.cpp @@ -204,6 +204,42 @@ void Changes::topicRemoved(not_null topic) { _topicChanges.drop(topic); } +void Changes::sublistUpdated( + not_null sublist, + SublistUpdate::Flags flags) { + const auto drop = (flags & SublistUpdate::Flag::Destroyed); + _sublistChanges.updated(sublist, flags, drop); + if (!drop) { + scheduleNotifications(); + } +} + +rpl::producer Changes::sublistUpdates( + SublistUpdate::Flags flags) const { + return _sublistChanges.updates(flags); +} + +rpl::producer Changes::sublistUpdates( + not_null sublist, + SublistUpdate::Flags flags) const { + return _sublistChanges.updates(sublist, flags); +} + +rpl::producer Changes::sublistFlagsValue( + not_null sublist, + SublistUpdate::Flags flags) const { + return _sublistChanges.flagsValue(sublist, flags); +} + +rpl::producer Changes::realtimeSublistUpdates( + SublistUpdate::Flag flag) const { + return _sublistChanges.realtimeUpdates(flag); +} + +void Changes::sublistRemoved(not_null sublist) { + _sublistChanges.drop(sublist); +} + void Changes::messageUpdated( not_null item, MessageUpdate::Flags flags) { @@ -323,6 +359,7 @@ void Changes::sendNotifications() { _messageChanges.sendNotifications(); _entryChanges.sendNotifications(); _topicChanges.sendNotifications(); + _sublistChanges.sendNotifications(); _storyChanges.sendNotifications(); } diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 22cfec1e66..f3215d2599 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; + friend inline constexpr auto is_flag_type(Flag) { return true; } + + not_null 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 topic); + void sublistUpdated( + not_null sublist, + SublistUpdate::Flags flags); + [[nodiscard]] rpl::producer sublistUpdates( + SublistUpdate::Flags flags) const; + [[nodiscard]] rpl::producer sublistUpdates( + not_null sublist, + SublistUpdate::Flags flags) const; + [[nodiscard]] rpl::producer sublistFlagsValue( + not_null sublist, + SublistUpdate::Flags flags) const; + [[nodiscard]] rpl::producer realtimeSublistUpdates( + SublistUpdate::Flag flag) const; + void sublistRemoved(not_null sublist); + void messageUpdated( not_null item, MessageUpdate::Flags flags); @@ -396,6 +431,7 @@ private: Manager _peerChanges; Manager _historyChanges; Manager _topicChanges; + Manager _sublistChanges; Manager _messageChanges; Manager _entryChanges; Manager _storyChanges; diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index d422b01947..921cd0ca15 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -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() { diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 44dd7b86f7..47804bba96 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -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 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) { if (_fakeChatListRequests.contains(history)) { diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index eb8645c5a6..fd6ee38108 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -26,6 +26,7 @@ namespace Data { class Session; class Folder; struct WebPageDraft; +class SavedSublist; [[nodiscard]] MTPInputReplyTo ReplyToForMTP( not_null history, @@ -71,6 +72,9 @@ public: Fn callback = nullptr); void dialogEntryApplied(not_null history); void changeDialogUnreadMark(not_null history, bool unread); + void changeSublistUnreadMark( + not_null sublist, + bool unread); void requestFakeChatListMessage(not_null history); void requestGroupAround(not_null item); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 1f8e0ad696..4556022257 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1174,6 +1174,10 @@ not_null PeerData::userpicPaintingPeer() const { return const_cast(this)->userpicPaintingPeer(); } +bool PeerData::userpicForceForumShape() const { + return monoforumBroadcast() != nullptr; +} + ChannelData *PeerData::monoforumBroadcast() const { const auto monoforum = asMonoforum(); return monoforum ? monoforum->monoforumLink() : nullptr; diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 397965e476..8de2297985 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -307,6 +307,7 @@ public: [[nodiscard]] not_null migrateToOrMe() const; [[nodiscard]] not_null userpicPaintingPeer(); [[nodiscard]] not_null userpicPaintingPeer() const; + [[nodiscard]] bool userpicForceForumShape() const; // isMonoforum() ? monoforumLink() : nullptr [[nodiscard]] ChannelData *monoforumBroadcast() const; diff --git a/Telegram/SourceFiles/data/data_saved_messages.cpp b/Telegram/SourceFiles/data/data_saved_messages.cpp index ef67795445..94d02148c4 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.cpp +++ b/Telegram/SourceFiles/data/data_saved_messages.cpp @@ -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 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; diff --git a/Telegram/SourceFiles/data/data_saved_messages.h b/Telegram/SourceFiles/data/data_saved_messages.h index 206b905f21..8b5530db79 100644 --- a/Telegram/SourceFiles/data/data_saved_messages.h +++ b/Telegram/SourceFiles/data/data_saved_messages.h @@ -50,6 +50,7 @@ public: void apply(const MTPDupdatePinnedSavedDialogs &update); void apply(const MTPDupdateSavedDialogPinned &update); + void applySublistDeleted(not_null sublistPeer); void listMessageChanged(HistoryItem *from, HistoryItem *to); [[nodiscard]] int recentSublistsListVersion() const; diff --git a/Telegram/SourceFiles/data/data_saved_sublist.cpp b/Telegram/SourceFiles/data/data_saved_sublist.cpp index e4e420a75e..29bf6d5a13 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.cpp +++ b/Telegram/SourceFiles/data/data_saved_sublist.cpp @@ -480,6 +480,15 @@ void SavedSublist::setUnreadCount(std::optional 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; } diff --git a/Telegram/SourceFiles/data/data_saved_sublist.h b/Telegram/SourceFiles/data/data_saved_sublist.h index 0708e1a7a0..095fdacf1e 100644 --- a/Telegram/SourceFiles/data/data_saved_sublist.h +++ b/Telegram/SourceFiles/data/data_saved_sublist.h @@ -58,13 +58,13 @@ public: [[nodiscard]] rpl::producer<> changes() const; [[nodiscard]] std::optional fullCount() const; [[nodiscard]] rpl::producer fullCountValue() const; - [[nodiscard]] rpl::producer> maybeFullCount() const; void loadFullCount(); [[nodiscard]] bool unreadCountKnown() const; [[nodiscard]] int unreadCountCurrent() const; [[nodiscard]] int displayedUnreadCount() const; [[nodiscard]] rpl::producer> unreadCountValue() const; + void setUnreadMark(bool unread); void applyMonoforumDialog( const MTPDmonoForumDialog &dialog, diff --git a/Telegram/SourceFiles/data/data_thread.cpp b/Telegram/SourceFiles/data/data_thread.cpp index dcf9b85f51..702aed3639 100644 --- a/Telegram/SourceFiles/data/data_thread.cpp +++ b/Telegram/SourceFiles/data/data_thread.cpp @@ -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 &Thread::unreadMentionsIds() const { if (!_unreadThings) { static const auto Result = base::flat_set(); diff --git a/Telegram/SourceFiles/data/data_thread.h b/Telegram/SourceFiles/data/data_thread.h index 74462a1944..09a33f27da 100644 --- a/Telegram/SourceFiles/data/data_thread.h +++ b/Telegram/SourceFiles/data/data_thread.h @@ -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 item); void clearNotifications(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 747cb519af..f863378136 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -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(); } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 3f4540c60c..fc99f35daf 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -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 sublistPeer) { + auto toDestroy = std::vector>(); + 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( diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 4a009a4d20..e58ed77a1a 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -139,6 +139,7 @@ public: void destroyMessage(not_null item); void destroyMessagesByDates(TimeId minDate, TimeId maxDate); void destroyMessagesByTopic(MsgId topicRootId); + void destroyMessagesBySublist(not_null sublistPeer); void unpinMessagesFor(MsgId topicRootId); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 933de0d20c..89743aa851 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -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 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; } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 672b322412..cc6e4e8bb1 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -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); + } } } } diff --git a/Telegram/SourceFiles/history/history_unread_things.cpp b/Telegram/SourceFiles/history/history_unread_things.cpp index 4cca97437e..1ba1678f3a 100644 --- a/Telegram/SourceFiles/history/history_unread_things.cpp +++ b/Telegram/SourceFiles/history/history_unread_things.cpp @@ -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 return UpdateFlag(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)); } } diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 6b62e06a93..05ad41d477 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -366,8 +366,9 @@ void Item::setupTop() { ? nullptr : Ui::CreateChild( _top.get(), - _thread->peer(), - st::previewUserpic); + _thread->peer()->userpicPaintingPeer(), + st::previewUserpic, + _thread->peer()->userpicForceForumShape()); if (userpic) { userpic->showSavedMessagesOnSelf(true); userpic->setAttribute(Qt::WA_TransparentForMouseEvents); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index cbd9d6c8a4..32ef2bf028 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -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 replies) { refreshUnreadCountBadge(count); }, lifetime()); - refreshUnreadCountBadge(_replies->unreadCountKnown() - ? _replies->unreadCountCurrent() - : std::optional()); + unreadCountUpdated(); const auto isTopic = (_topic != nullptr); const auto isTopicCreating = isTopic && _topic->creating(); @@ -2533,14 +2542,62 @@ void ChatWidget::setReplies(std::shared_ptr 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 count) { refreshUnreadCountBadge(count); }, lifetime()); - refreshUnreadCountBadge(_sublist->unreadCountKnown() - ? _sublist->unreadCountCurrent() - : std::optional()); + 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()) + : _sublist + ? (_sublist->unreadCountKnown() + ? _sublist->unreadCountCurrent() + : std::optional()) + : std::optional()); + } } void ChatWidget::restoreState(not_null memento) { diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.h b/Telegram/SourceFiles/history/view/history_view_chat_section.h index f62beb39d5..52bc50f832 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.h +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.h @@ -241,6 +241,8 @@ private: int limitAfter); void onScroll(); + void closeCurrent(); + void unreadCountUpdated(); void updateInnerVisibleArea(); void updateControlsGeometry(); void updateAdaptiveLayout(); diff --git a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp index f8d664352b..dec9438836 100644 --- a/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp +++ b/Telegram/SourceFiles/history/view/history_view_subsection_tabs.cpp @@ -590,9 +590,12 @@ void SubsectionTabs::refreshSlice() { }); const auto push = [&](not_null 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(), }); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index b825a2b19e..8273f9604e 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -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); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 2393e6c370..84786a9ad0 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -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() diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.cpp b/Telegram/SourceFiles/ui/controls/userpic_button.cpp index 967ad72fa6..fa7e2c70f7 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.cpp +++ b/Telegram/SourceFiles/ui/controls/userpic_button.cpp @@ -202,10 +202,12 @@ UserpicButton::UserpicButton( UserpicButton::UserpicButton( QWidget *parent, not_null 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); diff --git a/Telegram/SourceFiles/ui/controls/userpic_button.h b/Telegram/SourceFiles/ui/controls/userpic_button.h index 6bd96f330e..959f980333 100644 --- a/Telegram/SourceFiles/ui/controls/userpic_button.h +++ b/Telegram/SourceFiles/ui/controls/userpic_button.h @@ -74,7 +74,8 @@ public: UserpicButton( QWidget *parent, not_null peer, // Role::Custom, Source::PeerPhoto - const style::UserpicButton &st); + const style::UserpicButton &st, + bool forceForumShape = false); ~UserpicButton(); enum class ChosenType { diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 026b1246dc..84f738b55a 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -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 DeleteAndLeaveHandler( }; } +Fn DeleteSublistHandler( + not_null controller, + not_null 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 controller, Dialogs::EntryState request, @@ -3345,8 +3363,7 @@ void MarkAsReadThread(not_null thread) { if (!IsUnreadThread(thread)) { return; } else if (const auto forum = thread->asForum()) { - forum->enumerateTopics([]( - not_null topic) { + forum->enumerateTopics([](not_null topic) { MarkAsReadThread(topic); }); } else if (const auto history = thread->asHistory()) { @@ -3356,6 +3373,8 @@ void MarkAsReadThread(not_null thread) { } } else if (const auto topic = thread->asTopic()) { topic->readTillEnd(); + } else if (const auto sublist = thread->asSublist()) { + sublist->readTillEnd(); } } diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index fbc677bdb6..06c5ce626c 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -148,6 +148,9 @@ Fn ClearHistoryHandler( Fn DeleteAndLeaveHandler( not_null controller, not_null peer); +Fn DeleteSublistHandler( + not_null controller, + not_null sublist); object_ptr PrepareChooseRecipientBox( not_null session, diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 5e123fd458..6b41f48329 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1309,6 +1309,9 @@ void SessionNavigation::showPeerInfo( const SectionShow ¶ms) { if (const auto topic = thread->asTopic()) { showSection(std::make_shared(topic), params); + } else if (const auto sublist = thread->asSublist() + ; sublist && sublist->parentChat()) { + showPeerInfo(sublist->sublistPeer()->id, params); } else { showPeerInfo(thread->peer()->id, params); }