From 6997e165c6cf235ac894b1dba63fe43065e47a9e Mon Sep 17 00:00:00 2001 From: John Preston <johnprestonmail@gmail.com> Date: Fri, 14 Oct 2022 17:25:05 +0400 Subject: [PATCH] Forum three-dot menu, except search. --- Telegram/Resources/langs/lang.strings | 16 ++ .../boxes/peers/edit_forum_topic_box.cpp | 7 +- .../boxes/peers/edit_peer_info_box.cpp | 4 +- .../SourceFiles/data/data_forum_topic.cpp | 19 +- Telegram/SourceFiles/data/data_forum_topic.h | 3 + Telegram/SourceFiles/data/data_peer.cpp | 17 +- Telegram/SourceFiles/data/data_peer.h | 2 +- .../SourceFiles/data/data_replies_list.cpp | 79 +++++++- Telegram/SourceFiles/data/data_replies_list.h | 10 + .../dialogs/dialogs_inner_widget.cpp | 23 +-- Telegram/SourceFiles/dialogs/dialogs_key.h | 1 + .../SourceFiles/history/history_service.cpp | 63 ++++--- .../view/history_view_replies_section.cpp | 69 +------ .../view/history_view_replies_section.h | 5 - .../view/history_view_top_bar_widget.cpp | 10 +- .../info/profile/info_profile_widget.cpp | 2 +- Telegram/SourceFiles/ui/empty_userpic.cpp | 19 +- Telegram/SourceFiles/ui/empty_userpic.h | 3 +- Telegram/SourceFiles/ui/menu_icons.style | 1 + .../SourceFiles/window/window_peer_menu.cpp | 173 +++++++++++++----- 20 files changed, 354 insertions(+), 172 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 62d2ca41d..f4399e84f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1136,6 +1136,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_bot_title" = "Bot Info"; "lng_info_group_title" = "Group Info"; "lng_info_channel_title" = "Channel Info"; +"lng_info_topic_title" = "Topic Info"; "lng_profile_enable_notifications" = "Notifications"; "lng_profile_send_message" = "Send Message"; "lng_info_add_as_contact" = "Add to contacts"; @@ -1484,6 +1485,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot."; "lng_action_gift_received" = "{user} sent you a gift for {cost}"; "lng_action_gift_received_me" = "You sent to {user} a gift for {cost}"; +"lng_action_topic_created" = "Topic created"; +"lng_action_topic_renamed" = "Topic renamed to «{title}»"; +"lng_action_topic_icon_changed" = "Topic icon changed to {emoji}"; +"lng_action_topic_icon_removed" = "Topic icon removed"; +"lng_action_topic_closed" = "Topic closed"; +"lng_action_topic_reopened" = "Topic reopened"; "lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#other" = "for {count} months"; @@ -2116,6 +2123,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_send_message" = "Send message"; "lng_context_view_group" = "View group info"; "lng_context_view_channel" = "View channel info"; +"lng_context_view_topic" = "View topic info"; "lng_context_hide_psa" = "Hide this announcement"; "lng_context_pin_to_top" = "Pin to top"; "lng_context_unpin_from_top" = "Unpin from top"; @@ -3470,6 +3478,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_ringtones_error_max_size" = "Sorry, but your file is too big. The maximum size for ringtones is {size}."; "lng_ringtones_error_max_duration" = "Sorry, but your file is too long. The maximum duration for ringtones is {duration}."; +"lng_forum_topic_new" = "New Topic"; +"lng_forum_topic_edit" = "Edit Topic"; +"lng_forum_topic_title" = "Topic Title"; +"lng_forum_topics_switch" = "Topics"; +"lng_forum_no_topics" = "No topics currently created in this forum."; +"lng_forum_create_topic" = "Create topic"; +"lng_forum_discard_sure" = "Discard sure?"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index 24cd4c736..422476289 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -337,8 +337,9 @@ void EditForumTopicBox( const auto topic = (!creating && forum->peer->forum()) ? forum->peer->forum()->topicFor(rootId) : nullptr; - // #TODO lang-forum - box->setTitle(rpl::single(creating ? u"New topic"_q : u"Edit topic"_q)); + box->setTitle(creating + ? tr::lng_forum_topic_new() + : tr::lng_forum_topic_edit()); box->setMaxHeight(st::editTopicMaxHeight); @@ -365,7 +366,7 @@ void EditForumTopicBox( object_ptr<Ui::InputField>( box, st::defaultInputField, - rpl::single(u"Topic Title"_q), // #TODO lang-forum + tr::lng_forum_topic_title(), topic ? topic->title() : QString()), st::editTopicTitleMargin); box->setFocusCallback([=] { diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index 0f8a54872..60ee64f10 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -812,13 +812,13 @@ void Controller::fillForumButton() { Expects(_controls.buttonsLayout != nullptr); const auto channel = _peer->asChannel(); - if (!channel) { + if (!channel || !channel->amCreator()) { return; } AddButtonWithText( _controls.buttonsLayout, - rpl::single(u"Topics"_q), // #TODO lang-forum + tr::lng_forum_topics_switch(), rpl::single(QString()), [] {}, { &st::settingsIconGroup, Settings::kIconPurple } diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 49be44b6e..bfb06ce5f 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -142,7 +142,8 @@ ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId) , _forum(forum) , _list(_forum->topicsList()) , _replies(std::make_shared<RepliesList>(history(), rootId)) -, _rootId(rootId) { +, _rootId(rootId) +, _lastKnownServerMessageId(rootId) { Thread::setMuted(owner().notifySettings().isMuted(this)); _replies->unreadCountValue( @@ -193,6 +194,7 @@ MsgId ForumTopic::rootId() const { void ForumTopic::setRealRootId(MsgId realId) { if (_rootId != realId) { _rootId = realId; + _lastKnownServerMessageId = realId; _replies = std::make_shared<RepliesList>(history(), _rootId); } } @@ -263,6 +265,7 @@ int ForumTopic::chatListNameVersion() const { void ForumTopic::applyTopicTopMessage(MsgId topMessageId) { if (topMessageId) { + growLastKnownServerMessageId(topMessageId); const auto itemId = FullMsgId(channel()->id, topMessageId); if (const auto item = owner().message(itemId)) { setLastServerMessage(item); @@ -285,7 +288,14 @@ void ForumTopic::applyTopicTopMessage(MsgId topMessageId) { } } +void ForumTopic::growLastKnownServerMessageId(MsgId id) { + _lastKnownServerMessageId = std::max(_lastKnownServerMessageId, id); +} + void ForumTopic::setLastServerMessage(HistoryItem *item) { + if (item) { + growLastKnownServerMessageId(item->id); + } _lastServerMessage = item; if (_lastMessage && *_lastMessage @@ -303,6 +313,9 @@ void ForumTopic::setLastMessage(HistoryItem *item) { _lastMessage = item; if (!item || item->isRegular()) { _lastServerMessage = item; + if (item) { + growLastKnownServerMessageId(item->id); + } } setChatListMessage(item); } @@ -413,6 +426,10 @@ bool ForumTopic::lastServerMessageKnown() const { return _lastServerMessage.has_value(); } +MsgId ForumTopic::lastKnownServerMessageId() const { + return _lastKnownServerMessageId; +} + QString ForumTopic::title() const { return _title; } diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 26aa4e32f..c687abef7 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -81,6 +81,7 @@ public: [[nodiscard]] HistoryItem *lastServerMessage() const; [[nodiscard]] bool lastMessageKnown() const; [[nodiscard]] bool lastServerMessageKnown() const; + [[nodiscard]] MsgId lastKnownServerMessageId() const; [[nodiscard]] QString title() const; void applyTitle(const QString &title); @@ -116,6 +117,7 @@ private: void indexTitleParts(); void validateDefaultIcon() const; void applyTopicTopMessage(MsgId topMessageId); + void growLastKnownServerMessageId(MsgId id); void setLastMessage(HistoryItem *item); void setLastServerMessage(HistoryItem *item); @@ -131,6 +133,7 @@ private: const not_null<Dialogs::MainList*> _list; std::shared_ptr<RepliesList> _replies; MsgId _rootId = 0; + MsgId _lastKnownServerMessageId = 0; PeerNotifySettings _notify; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 47cb364e1..ea8139e62 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -314,7 +314,15 @@ void PeerData::paintUserpic( x, y, userpic->pix(size, size, { .options = rounding })); - } else { + } else if (isForum()) { + ensureEmptyUserpic()->paintRounded( + p, + x, + y, + x + size + x, + size, + st::roundRadiusLarge); + } else{ ensureEmptyUserpic()->paint(p, x, y, x + size + x, size); } } @@ -426,6 +434,9 @@ QImage PeerData::generateUserpicImage( ensureEmptyUserpic()->paint(p, 0, 0, size, size); } else if (radius == ImageRoundRadius::None) { ensureEmptyUserpic()->paintSquare(p, 0, 0, size, size); + } else if (radius == ImageRoundRadius::Large) { + const auto radius = st::roundRadiusLarge; + ensureEmptyUserpic()->paintRounded(p, 0, 0, size, size, radius); } else { ensureEmptyUserpic()->paintRounded(p, 0, 0, size, size); } @@ -519,6 +530,10 @@ bool PeerData::canPinMessages() const { Unexpected("Peer type in PeerData::canPinMessages."); } +bool PeerData::canCreateTopics() const { + return isForum() && canPinMessages(); +} + bool PeerData::canEditMessagesIndefinitely() const { if (const auto user = asUser()) { return user->isSelf(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index cf4bef19c..2c31944f9 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -339,7 +339,7 @@ public: [[nodiscard]] bool canPinMessages() const; [[nodiscard]] bool canEditMessagesIndefinitely() const; - + [[nodiscard]] bool canCreateTopics() const; [[nodiscard]] bool canExportChatHistory() const; // Returns true if about text was changed. diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index 5863e59ee..292190b5e 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_messages.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" +#include "window/notifications_manager.h" +#include "core/application.h" #include "lang/lang_keys.h" #include "apiwrap.h" @@ -25,6 +27,7 @@ namespace Data { namespace { constexpr auto kMessagesPerPage = 50; +constexpr auto kReadRequestTimeout = 3 * crl::time(1000); [[nodiscard]] HistoryService *GenerateDivider( not_null<History*> history, @@ -59,7 +62,8 @@ struct RepliesList::Viewer { RepliesList::RepliesList(not_null<History*> history, MsgId rootId) : _history(history) , _rootId(rootId) -, _creating(IsCreating(history, rootId)) { +, _creating(IsCreating(history, rootId)) +, _readRequestTimer([=] { sendReadTillRequest(); }) { _history->owner().repliesReadTillUpdates( ) | rpl::filter([=](const RepliesReadTillUpdate &update) { return (update.id.msg == _rootId) @@ -92,6 +96,9 @@ RepliesList::RepliesList(not_null<History*> history, MsgId rootId) RepliesList::~RepliesList() { histories().cancelRequest(base::take(_beforeId)); histories().cancelRequest(base::take(_afterId)); + if (_readRequestTimer.isActive()) { + sendReadTillRequest(); + } if (_divider) { _divider->destroy(); } @@ -753,6 +760,9 @@ MsgId RepliesList::computeOutboxReadTillFull() const { void RepliesList::setUnreadCount(std::optional<int> count) { _unreadCount = count; + if (!count && !_readRequestTimer.isActive() && !_readRequestId) { + reloadUnreadCountIfNeeded(); + } } void RepliesList::checkReadTillEnd() { @@ -858,4 +868,71 @@ void RepliesList::requestUnreadCount() { }).send(); } +void RepliesList::readTill(not_null<HistoryItem*> item) { + readTill(item->id, item); +} + +void RepliesList::readTill(MsgId tillId) { + if (!IsServerMsgId(tillId)) { + return; + } + readTill(tillId, _history->owner().message(_history->peer->id, tillId)); +} + +void RepliesList::readTill( + MsgId tillId, + HistoryItem *tillIdItem) { + const auto was = computeInboxReadTillFull(); + const auto now = tillId; + if (now < was) { + return; + } + const auto unreadCount = computeUnreadCountLocally(now); + const auto fast = (tillIdItem && tillIdItem->out()) || !unreadCount.has_value(); + if (was < now || (fast && now == was)) { + setInboxReadTill(now, unreadCount); + const auto rootFullId = FullMsgId(_history->peer->id, _rootId); + if (const auto root = _history->owner().message(rootFullId)) { + if (const auto post = root->lookupDiscussionPostOriginal()) { + post->setCommentsInboxReadTill(now); + } + } + if (!_readRequestTimer.isActive()) { + _readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout); + } else if (fast && _readRequestTimer.remainingTime() > 0) { + _readRequestTimer.callOnce(0); + } + } + if (const auto topic = _history->peer->forumTopicFor(_rootId)) { + Core::App().notifications().clearIncomingFromTopic(topic); + } +} + +void RepliesList::sendReadTillRequest() { + if (_readRequestTimer.isActive()) { + _readRequestTimer.cancel(); + } + const auto api = &_history->session().api(); + api->request(base::take(_readRequestId)).cancel(); + + _readRequestId = api->request(MTPmessages_ReadDiscussion( + _history->peer->input, + MTP_int(_rootId), + MTP_int(computeInboxReadTillFull()) + )).done(crl::guard(this, [=] { + _readRequestId = 0; + reloadUnreadCountIfNeeded(); + })).send(); +} + +void RepliesList::reloadUnreadCountIfNeeded() { + if (unreadCountKnown()) { + return; + } else if (inboxReadTillId() < computeInboxReadTillFull()) { + _readRequestTimer.callOnce(0); + } else { + requestUnreadCount(); + } +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_replies_list.h b/Telegram/SourceFiles/data/data_replies_list.h index 4fcb5d4e1..8c30c912d 100644 --- a/Telegram/SourceFiles/data/data_replies_list.h +++ b/Telegram/SourceFiles/data/data_replies_list.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/weak_ptr.h" +#include "base/timer.h" class History; class HistoryService; @@ -46,6 +47,9 @@ public: MsgId afterId) const; void requestUnreadCount(); + void readTill(not_null<HistoryItem*> item); + void readTill(MsgId tillId); + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -81,7 +85,10 @@ private: void changeUnreadCountByPost(MsgId id, int delta); void setUnreadCount(std::optional<int> count); + void readTill(MsgId tillId, HistoryItem *tillIdItem); void checkReadTillEnd(); + void sendReadTillRequest(); + void reloadUnreadCountIfNeeded(); const not_null<History*> _history; const MsgId _rootId = 0; @@ -101,6 +108,9 @@ private: int _beforeId = 0; int _afterId = 0; + base::Timer _readRequestTimer; + mtpRequestId _readRequestId = 0; + mtpRequestId _reloadUnreadCountRequestId = 0; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index b45a1ab39..340267d82 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1935,7 +1935,7 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { _controller, Dialogs::EntryState{ .key = row.key, - .section = Dialogs::EntryState::Section::ChatsList, + .section = Dialogs::EntryState::Section::ContextMenu, .filterId = _filterId, }, addAction); @@ -2394,7 +2394,7 @@ void InnerWidget::refreshEmptyLabel() { const auto data = &session().data(); const auto state = !shownDialogs()->empty() ? EmptyState::None - : _openedForum + : (_openedForum && _openedForum->topicsList()->loaded()) ? EmptyState::EmptyForum : (!_filterId && data->contactsLoaded().current()) ? EmptyState::NoContacts @@ -2415,16 +2415,14 @@ void InnerWidget::refreshEmptyLabel() { : (state == EmptyState::EmptyFolder) ? tr::lng_no_chats_filter() : (state == EmptyState::EmptyForum) - // #TODO lang-forum - ? rpl::single(u"No chats currently created in this forum."_q) + ? tr::lng_forum_no_topics() : tr::lng_contacts_loading(); auto link = (state == EmptyState::NoContacts) ? tr::lng_add_contact_button() : (state == EmptyState::EmptyFolder) ? tr::lng_filters_context_edit() : (state == EmptyState::EmptyForum) - // #TODO lang-forum - ? rpl::single(u"Create topic"_q) + ? tr::lng_forum_create_topic() : rpl::single(QString()); auto full = rpl::combine( std::move(phrase), @@ -3323,16 +3321,9 @@ void InnerWidget::setupShortcuts() { return jumpToDialogRow(last); }); request->check(Command::ChatSelf) && request->handle([=] { - if (_openedForum) { - _controller->show(Box( - NewForumTopicBox, - _controller, - _openedForum->history())); - } else { - _controller->content()->choosePeer( - session().userPeerId(), - ShowAtUnreadMsgId); - } + _controller->content()->choosePeer( + session().userPeerId(), + ShowAtUnreadMsgId); return true; }); request->check(Command::ShowArchive) && request->handle([=] { diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 620b81ddd..b644188b5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -100,6 +100,7 @@ struct EntryState { Scheduled, Pinned, Replies, + ContextMenu, }; Key key; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index e3e42f172..ac209140f 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -639,27 +639,56 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { auto result = PreparedText{}; // #TODO lang-forum - result.text = { "topic created: " + qs(action.vtitle()) }; + result.text = { tr::lng_action_topic_created(tr::now) }; return result; }; auto prepareTopicEdit = [&](const MTPDmessageActionTopicEdit &action) { auto result = PreparedText{}; - // #TODO lang-forum - result.text = { "topic edited: " }; - if (const auto icon = action.vicon_emoji_id()) { - result.text.append(TextWithEntities{ + const auto wrapIcon = [](DocumentId id) { + return TextWithEntities{ "@", { EntityInText( EntityType::CustomEmoji, 0, 1, - Data::SerializeCustomEmojiId({ .id = icon->v })) + Data::SerializeCustomEmojiId({ .id = id })) }, - }); + }; + }; + if (const auto closed = action.vclosed()) { + result.text = { mtpIsTrue(*closed) + ? tr::lng_action_topic_closed(tr::now) + : tr::lng_action_topic_reopened(tr::now) }; + } else if (!action.vtitle()) { + if (const auto icon = action.vicon_emoji_id()) { + if (const auto iconId = icon->v) { + result.text = tr::lng_action_topic_icon_changed( + tr::now, + lt_emoji, + wrapIcon(iconId), + Ui::Text::WithEntities); + } else { + result.text = { + tr::lng_action_topic_icon_removed(tr::now) + }; + } + } + } else { + auto title = TextWithEntities{ + qs(*action.vtitle()) + }; + if (const auto icon = action.vicon_emoji_id().value_or_empty()) { + title = wrapIcon(icon).append(' ').append(std::move(title)); + } + result.text = tr::lng_action_topic_renamed( + tr::now, + lt_title, + std::move(title), + Ui::Text::WithEntities); } - if (const auto &title = action.vtitle()) { - result.text.append(qs(*title)); + if (result.text.empty()) { + result.text = { tr::lng_message_empty(tr::now) }; } return result; }; @@ -709,14 +738,10 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return prepareProximityReached(data); }, [](const MTPDmessageActionPaymentSentMe &) { LOG(("API Error: messageActionPaymentSentMe received.")); - return PreparedText{ - tr::lng_message_empty(tr::now, Ui::Text::WithEntities) - }; + return PreparedText{ { tr::lng_message_empty(tr::now) } }; }, [](const MTPDmessageActionSecureValuesSentMe &) { LOG(("API Error: messageActionSecureValuesSentMe received.")); - return PreparedText{ - tr::lng_message_empty(tr::now, Ui::Text::WithEntities) - }; + return PreparedText{ { tr::lng_message_empty(tr::now) } }; }, [&](const MTPDmessageActionGroupCall &data) { return prepareGroupCall(data); }, [&](const MTPDmessageActionInviteToGroupCall &data) { @@ -739,13 +764,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return prepareTopicEdit(data); }, [&](const MTPDmessageActionWebViewDataSentMe &data) { LOG(("API Error: messageActionWebViewDataSentMe received.")); - return PreparedText{ - tr::lng_message_empty(tr::now, Ui::Text::WithEntities) - }; + return PreparedText{ { tr::lng_message_empty(tr::now) } }; }, [](const MTPDmessageActionEmpty &) { - return PreparedText{ - tr::lng_message_empty(tr::now, Ui::Text::WithEntities) - }; + return PreparedText{ { tr::lng_message_empty(tr::now) } }; })); // Additional information. diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index aaa2cdada..72a7df19e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -53,7 +53,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/call_delayed.h" #include "base/qt/qt_key_modifiers.h" #include "core/file_utilities.h" -#include "core/application.h" #include "main/main_session.h" #include "data/data_session.h" #include "data/data_user.h" @@ -70,7 +69,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "lang/lang_keys.h" #include "facades.h" -#include "window/notifications_manager.h" #include "styles/style_chat.h" #include "styles/style_window.h" #include "styles/style_info.h" @@ -82,7 +80,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -constexpr auto kReadRequestTimeout = 3 * crl::time(1000); constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); bool CanSendFiles(not_null<const QMimeData*> data) { @@ -218,8 +215,7 @@ RepliesWidget::RepliesWidget( , _cornerButtons( _scroll.get(), controller->chatStyle(), - static_cast<HistoryView::CornerButtonsDelegate*>(this)) -, _readRequestTimer([=] { sendReadTillRequest(); }) { + static_cast<HistoryView::CornerButtonsDelegate*>(this)) { controller->chatStyle()->paletteChanged( ) | rpl::start_with_next([=] { _scroll->updateBars(); @@ -359,9 +355,6 @@ RepliesWidget::~RepliesWidget() { _topic->forum()->discardCreatingId(_rootId); _topic = nullptr; } - if (_readRequestTimer.isActive()) { - sendReadTillRequest(); - } base::take(_sendAction); _history->owner().sendActionManager().repliesPainterRemoved( _history, @@ -380,23 +373,6 @@ void RepliesWidget::orderWidgets() { _composeControls->raisePanels(); } -void RepliesWidget::sendReadTillRequest() { - if (_readRequestTimer.isActive()) { - _readRequestTimer.cancel(); - } - const auto api = &_history->session().api(); - api->request(base::take(_readRequestId)).cancel(); - - _readRequestId = api->request(MTPmessages_ReadDiscussion( - _history->peer->input, - MTP_int(_rootId), - MTP_int(_replies->computeInboxReadTillFull()) - )).done(crl::guard(this, [=] { - _readRequestId = 0; - reloadUnreadCountIfNeeded(); - })).send(); -} - void RepliesWidget::setupRoot() { if (!_root) { const auto done = crl::guard(this, [=] { @@ -1366,19 +1342,6 @@ MsgId RepliesWidget::replyToId() const { void RepliesWidget::refreshUnreadCountBadge(std::optional<int> count) { if (count.has_value()) { _cornerButtons.updateJumpDownVisibility(count); - } else if (!_readRequestTimer.isActive() && !_readRequestId) { - reloadUnreadCountIfNeeded(); - } -} - -void RepliesWidget::reloadUnreadCountIfNeeded() { - if (_replies->unreadCountKnown()) { - return; - } else if (_replies->inboxReadTillId() - < _replies->computeInboxReadTillFull()) { - _readRequestTimer.callOnce(0); - } else { - _replies->requestUnreadCount(); } } @@ -1926,35 +1889,9 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) { _topBar->showSelected(state); } -void RepliesWidget::readTill(not_null<HistoryItem*> item) { - const auto was = _replies->computeInboxReadTillFull(); - const auto now = item->id; - if (now < was) { - return; - } - const auto unreadCount = _replies->computeUnreadCountLocally(now); - const auto fast = item->out() || !unreadCount.has_value(); - if (was < now || (fast && now == was)) { - _replies->setInboxReadTill(now, unreadCount); - if (_root) { - if (const auto post = _root->lookupDiscussionPostOriginal()) { - post->setCommentsInboxReadTill(now); - } - } - if (!_readRequestTimer.isActive()) { - _readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout); - } else if (fast && _readRequestTimer.remainingTime() > 0) { - _readRequestTimer.callOnce(0); - } - } - if (_topic) { - Core::App().notifications().clearIncomingFromTopic(_topic); - } -} - void RepliesWidget::listMarkReadTill(not_null<HistoryItem*> item) { if (true/*doWeReadServerHistory()*/) { // #TODO forum active - readTill(item); + _replies->readTill(item); } } @@ -1976,7 +1913,7 @@ MessagesBarData RepliesWidget::listMessagesBar( const auto item = elements[i]->data(); if (item->isRegular() && item->id > till) { if (item->out() || !item->replyToId()) { - readTill(item); + _replies->readTill(item); } else { return { .bar = { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 9000c20d1..e91bf702e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -201,8 +201,6 @@ private: void subscribeToTopic(); void setTopic(Data::ForumTopic *topic); void setupDragArea(); - void sendReadTillRequest(); - void readTill(not_null<HistoryItem*> item); void scrollDownAnimationFinish(); void updatePinnedVisibility(); @@ -312,9 +310,6 @@ private: bool _choosingAttach = false; - base::Timer _readRequestTimer; - mtpRequestId _readRequestId = 0; - bool _loaded = false; }; 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 56ec46ba2..001cb9ed2 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -980,9 +980,15 @@ void TopBarWidget::updateControlsVisibility() { const auto hasPollsMenu = _activeChat.key.peer() && _activeChat.key.peer()->canSendPolls(); const auto hasMenu = !_activeChat.key.folder() - && ((section == Section::Scheduled || section == Section::Replies) + && (section == Section::History + ? true + : (section == Section::Scheduled) ? hasPollsMenu - : historyMode); + : (section == Section::Replies) + ? (hasPollsMenu || _activeChat.key.topic()) + : (section == Section::ChatsList) + ? (_activeChat.key.peer() && _activeChat.key.peer()->isForum()) + : false); updateSearchVisibility(); _menuToggle->setVisible(hasMenu && !_chooseForReportReason); _infoToggle->setVisible(historyMode diff --git a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp index 39bf8491e..f610c2bfe 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_widget.cpp @@ -90,7 +90,7 @@ void Widget::setInnerFocus() { rpl::producer<QString> Widget::title() { if (const auto topic = controller()->key().topic()) { - return rpl::single(u"Topic Info"_q); // #TODO lang-forum + return tr::lng_info_topic_title(); } const auto peer = controller()->key().peer(); if (const auto user = peer->asUser()) { diff --git a/Telegram/SourceFiles/ui/empty_userpic.cpp b/Telegram/SourceFiles/ui/empty_userpic.cpp index 992e18648..e7190c8db 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.cpp +++ b/Telegram/SourceFiles/ui/empty_userpic.cpp @@ -224,19 +224,28 @@ void EmptyUserpic::paint( int y, int outerWidth, int size) const { - paint(p, x, y, outerWidth, size, [&p, x, y, size] { + paint(p, x, y, outerWidth, size, [&] { p.drawEllipse(x, y, size, size); }); } -void EmptyUserpic::paintRounded(QPainter &p, int x, int y, int outerWidth, int size) const { - paint(p, x, y, outerWidth, size, [&p, x, y, size] { - p.drawRoundedRect(x, y, size, size, st::roundRadiusSmall, st::roundRadiusSmall); +void EmptyUserpic::paintRounded( + QPainter &p, + int x, + int y, + int outerWidth, + int size, + int radius) const { + if (!radius) { + radius = st::roundRadiusSmall; + } + paint(p, x, y, outerWidth, size, [&] { + p.drawRoundedRect(x, y, size, size, radius, radius); }); } void EmptyUserpic::paintSquare(QPainter &p, int x, int y, int outerWidth, int size) const { - paint(p, x, y, outerWidth, size, [&p, x, y, size] { + paint(p, x, y, outerWidth, size, [&] { p.fillRect(x, y, size, size, p.brush()); }); } diff --git a/Telegram/SourceFiles/ui/empty_userpic.h b/Telegram/SourceFiles/ui/empty_userpic.h index ea57e4d94..c990a4e44 100644 --- a/Telegram/SourceFiles/ui/empty_userpic.h +++ b/Telegram/SourceFiles/ui/empty_userpic.h @@ -26,7 +26,8 @@ public: int x, int y, int outerWidth, - int size) const; + int size, + int radius = 0) const; void paintSquare( QPainter &p, int x, diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index 10a503c13..a1c421d44 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -88,6 +88,7 @@ menuIconPhoto: icon {{ "menu/image", menuIconColor }}; menuIconAddToFolder: icon {{ "menu/add_to_folder", menuIconColor }}; menuIconLeave: icon {{ "menu/leave", menuIconColor }}; menuIconGiftPremium: icon {{ "menu/gift_premium", menuIconColor }}; +menuIconSearch: icon {{ "menu/search", menuIconColor }}; menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }}; menuIconTTLAnyTextPosition: point(11px, 22px); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 61061e111..1d3181c2a 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -63,7 +63,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_drafts.h" +#include "data/data_forum.h" #include "data/data_forum_topic.h" +#include "data/data_replies_list.h" #include "data/data_user.h" #include "data/data_scheduled_messages.h" #include "data/data_histories.h" @@ -80,6 +82,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include <QAction> namespace Window { +namespace { + +constexpr auto kTopicsSearchMinCount = 10; + +} // namespace const char kOptionViewProfileInChatsListContextMenu[] = "view-profile-in-chats-list-context-menu"; @@ -104,20 +111,31 @@ void SetActionText(not_null<QAction*> action, rpl::producer<QString> &&text) { }, *lifetime); } -[[nodiscard]] bool IsUnreadHistory(not_null<History*> history) { - return (history->chatListUnreadCount() > 0) - || (history->chatListUnreadMark()); +[[nodiscard]] bool IsUnreadThread(not_null<Data::Thread*> thread) { + return (thread->chatListUnreadCount() > 0) + || (thread->chatListUnreadMark()); } -void MarkAsReadHistory(not_null<History*> history) { - const auto read = [&](not_null<History*> history) { - if (IsUnreadHistory(history)) { - history->peer->owner().histories().readInbox(history); - } +void MarkAsReadThread(not_null<Data::Thread*> thread) { + const auto readHistory = [&](not_null<History*> history) { + history->owner().histories().readInbox(history); }; - read(history); - if (const auto migrated = history->migrateSibling()) { - read(migrated); + if (!IsUnreadThread(thread)) { + return; + } else if (const auto history = thread->asHistory()) { + if (const auto forum = history->peer->forum()) { + forum->enumerateTopics([]( + not_null<Data::ForumTopic*> topic) { + MarkAsReadThread(topic); + }); + } else { + readHistory(history); + if (const auto migrated = history->migrateSibling()) { + readHistory(migrated); + } + } + } else if (const auto topic = thread->asTopic()) { + topic->replies()->readTill(topic->lastKnownServerMessageId()); } } @@ -128,7 +146,7 @@ void MarkAsReadChatList(not_null<Dialogs::MainList*> list) { mark.push_back(history); } } - ranges::for_each(mark, MarkAsReadHistory); + ranges::for_each(mark, MarkAsReadThread); } void PeerMenuAddMuteSubmenuAction( @@ -188,6 +206,7 @@ private: void fillRepliesActions(); void fillScheduledActions(); void fillArchiveActions(); + void fillContextMenuActions(); void addHidePromotion(); void addTogglePin(); @@ -200,6 +219,7 @@ private: void addClearHistory(); void addDeleteChat(); void addLeaveChat(); + void addJoinChat(); void addManageTopic(); void addManageChat(); void addCreatePoll(); @@ -216,10 +236,13 @@ private: void addDeleteContact(); void addTTLSubmenu(bool addSeparator); void addGiftPremium(); + void addCreateTopic(); + void addSearchTopics(); not_null<SessionController*> _controller; Dialogs::EntryState _request; Data::Thread *_thread = nullptr; + Data::ForumTopic *_topic = nullptr; PeerData *_peer = nullptr; Data::Folder *_folder = nullptr; const PeerMenuCallback &_addAction; @@ -340,6 +363,7 @@ Filler::Filler( : _controller(controller) , _request(request) , _thread(request.key.thread()) +, _topic(request.key.topic()) , _peer(request.key.peer()) , _folder(request.key.folder()) , _addAction(addAction) { @@ -347,7 +371,8 @@ Filler::Filler( void Filler::addHidePromotion() { const auto history = _request.key.history(); - if (!history + if (_topic + || !history || !history->useTopPromotion() || history->topPromotionType().isEmpty()) { return; @@ -361,14 +386,14 @@ void Filler::addHidePromotion() { } void Filler::addTogglePin() { - if (!_peer) { + if (!_peer || _topic) { // #TODO forum pinned return; } const auto controller = _controller; const auto filterId = _request.filterId; const auto peer = _peer; - const auto history = peer->owner().historyLoaded(peer); + const auto history = _request.key.history(); if (!history || history->fixedOnTopIndex()) { return; } @@ -435,7 +460,7 @@ void Filler::addInfo() { const auto controller = _controller; const auto weak = base::make_weak(_thread); const auto text = _thread->asTopic() - ? u"View Topic Info"_q // #TODO lang-forum + ? tr::lng_context_view_topic(tr::now) : (_peer->isChat() || _peer->isMegagroup()) ? tr::lng_context_view_group(tr::now) : _peer->isUser() @@ -451,7 +476,7 @@ void Filler::addInfo() { void Filler::addToggleFolder() { const auto controller = _controller; const auto history = _request.key.history(); - if (!history || !history->owner().chatsFilters().has()) { + if (_topic || !history || !history->owner().chatsFilters().has()) { return; } _addAction(PeerMenuCallback::Args{ @@ -466,38 +491,37 @@ void Filler::addToggleFolder() { void Filler::addToggleUnreadMark() { const auto peer = _peer; - const auto history = peer->owner().history(peer); - const auto label = [=] { - return IsUnreadHistory(history) - ? tr::lng_context_mark_read(tr::now) - : tr::lng_context_mark_unread(tr::now); - }; - auto action = _addAction(label(), [=] { - const auto markAsRead = IsUnreadHistory(history); - if (markAsRead) { - MarkAsReadHistory(history); - } else { - peer->owner().histories().changeDialogUnreadMark( - history, - !markAsRead); + const auto history = _request.key.history(); + if (!_thread) { + return; + } + const auto unread = IsUnreadThread(_thread); + if (_thread->asTopic() && !unread) { + return; + } + const auto weak = base::make_weak(_thread); + const auto label = unread + ? tr::lng_context_mark_read(tr::now) + : tr::lng_context_mark_unread(tr::now); + _addAction(label, [=] { + const auto thread = weak.get(); + if (!thread) { + return; } - }, (IsUnreadHistory(history) - ? &st::menuIconMarkRead - : &st::menuIconMarkUnread)); - - auto actionText = history->session().changes().historyUpdates( - history, - Data::HistoryUpdate::Flag::UnreadView - ) | rpl::map(label); - SetActionText(action, std::move(actionText)); + if (unread) { + MarkAsReadThread(thread); + } else if (history) { + peer->owner().histories().changeDialogUnreadMark(history, true); + } + }, (unread ? &st::menuIconMarkRead : &st::menuIconMarkUnread)); } void Filler::addToggleArchive() { - if (!_peer) { + if (!_peer || _topic) { return; } const auto peer = _peer; - const auto history = peer->owner().historyLoaded(peer); + const auto history = _request.key.history(); if (history && history->useTopPromotion()) { return; } else if (peer->isNotificationsUser() || peer->isSelf()) { @@ -529,6 +553,9 @@ void Filler::addToggleArchive() { } void Filler::addClearHistory() { + if (_topic) { + return; + } const auto channel = _peer->asChannel(); const auto isGroup = _peer->isChat() || _peer->isMegagroup(); if (channel) { @@ -546,7 +573,7 @@ void Filler::addClearHistory() { } void Filler::addDeleteChat() { - if (_peer->isChannel()) { + if (_topic || _peer->isChannel()) { return; } _addAction({ @@ -561,7 +588,7 @@ void Filler::addDeleteChat() { void Filler::addLeaveChat() { const auto channel = _peer->asChannel(); - if (_thread->asTopic() || !channel || !channel->amIn()) { + if (_topic || !channel || !channel->amIn()) { return; } _addAction({ @@ -574,6 +601,19 @@ void Filler::addLeaveChat() { }); } +void Filler::addJoinChat() { + const auto channel = _peer->asChannel(); + if (_topic || !channel || channel->amIn()) { + return; + } + const auto label = _peer->isMegagroup() + ? tr::lng_profile_join_group(tr::now) + : tr::lng_profile_join_channel(tr::now); + _addAction(label, [=] { + channel->session().api().joinChannel(channel); + }, &st::menuIconAddToFolder); +} + void Filler::addBlockUser() { const auto user = _peer->asUser(); if (!user @@ -766,11 +806,10 @@ void Filler::addManageTopic() { if (!topic) { return; } - // #TODO lang-forum const auto history = topic->history(); const auto rootId = topic->rootId(); const auto navigation = _controller; - _addAction(u"Edit topic"_q, [=] { + _addAction(tr::lng_forum_topic_edit(tr::now), [=] { navigation->show( Box(EditForumTopicBox, navigation, history, rootId)); }, &st::menuIconEdit); @@ -885,11 +924,53 @@ void Filler::fill() { case Section::Profile: fillProfileActions(); break; case Section::Replies: fillRepliesActions(); break; case Section::Scheduled: fillScheduledActions(); break; + case Section::ContextMenu: fillContextMenuActions(); break; default: Unexpected("_request.section in Filler::fill."); } } +void Filler::addCreateTopic() { + if (!_peer || !_peer->canCreateTopics()) { + return; + } + const auto peer = _peer; + const auto controller = _controller; + _addAction(tr::lng_forum_create_topic(tr::now), [=] { + if (const auto forum = peer->forum()) { + controller->show(Box( + NewForumTopicBox, + controller, + forum->history())); + } + }, &st::menuIconDiscussion); + _addAction(PeerMenuCallback::Args{ .isSeparator = true }); +} + +void Filler::addSearchTopics() { + _addAction(tr::lng_dlg_filter(tr::now), [=] { + + }, &st::menuIconSearch); +} + void Filler::fillChatsListActions() { + if (!_peer || !_peer->isForum()) { + return; + } + addCreateTopic(); + addInfo(); + addNewMembers(); + const auto &all = _peer->forum()->topicsList()->indexed()->all(); + if (all.size() > kTopicsSearchMinCount) { + addSearchTopics(); + } + if (_peer->asChannel()->amIn()) { + addLeaveChat(); + } else { + addJoinChat(); + } +} + +void Filler::fillContextMenuActions() { addHidePromotion(); addToggleArchive(); addTogglePin();