From ee7a2b564b290ba75da1d149d7a1c1dbd425d070 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 7 May 2025 18:03:59 +0400 Subject: [PATCH] Show "Join" button for pinned call links. --- .../SourceFiles/history/history_widget.cpp | 79 +++++-------- .../history/view/history_view_pinned_bar.cpp | 105 +++++++++++++++++- .../history/view/history_view_pinned_bar.h | 11 +- .../view/history_view_replies_section.cpp | 64 +++++------ 4 files changed, 160 insertions(+), 99 deletions(-) diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e75ccda787..f32fd69ffe 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7651,12 +7651,12 @@ void HistoryWidget::checkPinnedBarState() { } return (count > 1); }) | rpl::distinct_until_changed(); - auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup( + auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton( &session(), _pinnedTracker->shownMessageId()); rpl::combine( rpl::duplicate(pinnedRefreshed), - rpl::duplicate(markupRefreshed) + rpl::duplicate(customButtonItem) ) | rpl::start_with_next([=](bool many, HistoryItem *item) { refreshPinnedBarButton(many, item); }, _pinnedBar->lifetime()); @@ -7667,7 +7667,7 @@ void HistoryWidget::checkPinnedBarState() { _pinnedTracker->shownMessageId(), [bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }), std::move(pinnedRefreshed), - std::move(markupRefreshed) + std::move(customButtonItem) ) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) { const auto id = (!content.title.isEmpty() || !content.text.empty()) ? _pinnedTracker->currentMessageId().message @@ -7805,58 +7805,31 @@ void HistoryWidget::refreshPinnedBarButton(bool many, HistoryItem *item) { ? id.message.msg : (id.message.msg - ServerMaxMsgId)))); }; - if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) { - const auto &rows = replyMarkup->data.rows; - if ((rows.size() == 1) && (rows.front().size() == 1)) { - const auto text = rows.front().front().text; - if (!text.isEmpty()) { - const auto &st = st::historyPinnedBotButton; - auto button = object_ptr( - this, - rpl::never(), - st); - const auto label = Ui::CreateChild( - button.data(), - text, - st::historyPinnedBotLabel); - if (label->width() > st::historyPinnedBotButtonMaxWidth) { - label->resizeToWidth(st::historyPinnedBotButtonMaxWidth); - } - button->setFullWidth(label->width() - + st.padding.left() - + st.padding.right() - + st.height); - label->moveToLeft( - st.padding.left() + st.height / 2, - (button->height() - label->height()) / 2); - label->setTextColorOverride(st.textFg->c); - label->setAttribute(Qt::WA_TransparentForMouseEvents); - button->setTextTransform( - Ui::RoundButton::TextTransform::NoTransform); - button->setFullRadius(true); - button->setClickedCallback([=] { - Api::ActivateBotCommand( - _list->prepareClickHandlerContext(item->fullId()), - 0, - 0); - }); - struct State { - base::unique_qptr menu; - }; - const auto state = button->lifetime().make_state(); - _pinnedBar->contextMenuRequested( - ) | rpl::start_with_next([=, raw = button.data()] { - state->menu = base::make_unique_q(raw); - state->menu->addAction( - tr::lng_settings_events_pinned(tr::now), - openSection); - state->menu->popup(QCursor::pos()); - }, button->lifetime()); - _pinnedBar->setRightButton(std::move(button)); - return; - } + const auto context = [copy = _list](FullMsgId itemId) { + if (const auto raw = copy.data()) { + return raw->prepareClickHandlerContext(itemId); } + return ClickHandlerContext(); + }; + auto customButton = CreatePinnedBarCustomButton(this, item, context); + if (customButton) { + struct State { + base::unique_qptr menu; + }; + const auto buttonRaw = customButton.data(); + const auto state = buttonRaw->lifetime().make_state(); + _pinnedBar->contextMenuRequested( + ) | rpl::start_with_next([=] { + state->menu = base::make_unique_q(buttonRaw); + state->menu->addAction( + tr::lng_settings_events_pinned(tr::now), + openSection); + state->menu->popup(QCursor::pos()); + }, buttonRaw->lifetime()); + _pinnedBar->setRightButton(std::move(customButton)); + return; } + const auto close = !many; auto button = object_ptr( this, diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 9564b94c59..97b7164b2b 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -7,17 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/history_view_pinned_bar.h" +#include "api/api_bot.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_poll.h" +#include "data/data_web_page.h" #include "history/view/history_view_pinned_tracker.h" #include "history/history_item.h" +#include "history/history_item_components.h" #include "history/history.h" +#include "core/click_handler_types.h" #include "core/ui_integration.h" #include "base/weak_ptr.h" #include "apiwrap.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/basic_click_handlers.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" @@ -153,6 +160,45 @@ auto WithPinnedTitle(not_null session, PinnedId id) { }; } +[[nodiscard]] object_ptr MakePinnedBarCustomButton( + not_null parent, + const QString &buttonText, + Fn clickCallback) { + const auto &stButton = st::historyPinnedBotButton; + const auto &stLabel = st::historyPinnedBotLabel; + + auto button = object_ptr( + parent, + rpl::never(), // Text is handled by the inner label. + stButton); + + const auto label = Ui::CreateChild( + button.data(), + buttonText, + stLabel); + + if (label->width() > st::historyPinnedBotButtonMaxWidth) { + label->resizeToWidth(st::historyPinnedBotButtonMaxWidth); + } + button->setFullWidth(label->width() + + stButton.padding.left() + + stButton.padding.right() + + stButton.height); // stButton.height is likely for icon spacing. + + label->moveToLeft( + stButton.padding.left() + stButton.height / 2, + (button->height() - label->height()) / 2); + + label->setTextColorOverride(stButton.textFg->c); // Use button's text color for label. + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + button->setFullRadius(true); + button->setClickedCallback(std::move(clickCallback)); + + return button; +} + } // namespace rpl::producer MessageBarContentByItemId( @@ -178,7 +224,7 @@ rpl::producer PinnedBarContent( }) | rpl::flatten_latest(); } -rpl::producer PinnedBarItemWithReplyMarkup( +rpl::producer PinnedBarItemWithCustomButton( not_null session, rpl::producer id) { return rpl::make_producer([=, @@ -187,7 +233,7 @@ rpl::producer PinnedBarItemWithReplyMarkup( consumer.put_next(nullptr); struct State { - bool hasReplyMarkup = false; + bool hasCustomButton = false; base::has_weak_ptr guard; rpl::lifetime lifetime; FullMsgId resolvedId; @@ -196,10 +242,17 @@ rpl::producer PinnedBarItemWithReplyMarkup( const auto pushUnique = [=](not_null item) { const auto replyMarkup = item->inlineReplyMarkup(); - if (!state->hasReplyMarkup && !replyMarkup) { + const auto media = item->media(); + const auto page = media ? media->webpage() : nullptr; + const auto possiblyHasCustomButton = replyMarkup + || (page + && (page->type == WebPageType::VoiceChat + || page->type == WebPageType::Livestream + || page->type == WebPageType::ConferenceCall)); + if (!state->hasCustomButton && !possiblyHasCustomButton) { return; } - state->hasReplyMarkup = (replyMarkup != nullptr); + state->hasCustomButton = possiblyHasCustomButton; consumer.put_next(item.get()); }; @@ -217,12 +270,14 @@ rpl::producer PinnedBarItemWithReplyMarkup( using Update = Data::MessageUpdate; session->changes().messageUpdates( item, - Update::Flag::ReplyMarkup | Update::Flag::Destroyed + (Update::Flag::ReplyMarkup + | Update::Flag::Edited + | Update::Flag::Destroyed) ) | rpl::start_with_next([=](const Update &update) { if (update.flags & Update::Flag::Destroyed) { state->lifetime.destroy(); invalidate_weak_ptrs(&state->guard); - state->hasReplyMarkup = false; + state->hasCustomButton = false; consumer.put_next(nullptr); } else { pushUnique(update.item); @@ -248,4 +303,42 @@ rpl::producer PinnedBarItemWithReplyMarkup( }); } +[[nodiscard]] object_ptr CreatePinnedBarCustomButton( + not_null parent, + HistoryItem *item, + Fn context) { + if (!item) { + return nullptr; + } else if (const auto replyMarkup = item->inlineReplyMarkup()) { + const auto &rows = replyMarkup->data.rows; + if ((rows.size() == 1) && (rows.front().size() == 1)) { + const auto text = rows.front().front().text; + if (!text.isEmpty()) { + const auto contextId = item->fullId(); + const auto callback = [=] { + Api::ActivateBotCommand(context(contextId), 0, 0); + }; + return MakePinnedBarCustomButton(parent, text, callback); + } + } + } else if (const auto media = item->media()) { + if (const auto page = media->webpage()) { + if (page->type == WebPageType::VoiceChat + || page->type == WebPageType::Livestream + || page->type == WebPageType::ConferenceCall) { + const auto url = page->url; + const auto contextId = item->fullId(); + const auto callback = [=] { + UrlClickHandler::Open( + url, + QVariant::fromValue(context(contextId))); + }; + const auto text = tr::lng_group_call_join(tr::now); + return MakePinnedBarCustomButton(parent, text, callback); + } + } + } + return nullptr; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h index 535cabc6cb..acb27c1c36 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.h +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.h @@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/object_ptr.h" #include "ui/chat/message_bar.h" #include +struct ClickHandlerContext; + namespace Main { class Session; } // namespace Main @@ -18,6 +21,7 @@ class Session; namespace Ui { class IconButton; class PlainShadow; +class RoundButton; struct MessageBarContent; } // namespace Ui @@ -51,8 +55,13 @@ struct PinnedId { rpl::producer id, Fn repaint); -[[nodiscard]] rpl::producer PinnedBarItemWithReplyMarkup( +[[nodiscard]] rpl::producer PinnedBarItemWithCustomButton( not_null session, rpl::producer id); +[[nodiscard]] object_ptr CreatePinnedBarCustomButton( + not_null parent, + HistoryItem *item, + Fn context); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index dd6895dc17..768f42b178 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1838,12 +1838,12 @@ void RepliesWidget::checkPinnedBarState() { } return (count > 1); }) | rpl::distinct_until_changed(); - auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup( + auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton( &session(), _pinnedTracker->shownMessageId()); rpl::combine( rpl::duplicate(pinnedRefreshed), - rpl::duplicate(markupRefreshed) + rpl::duplicate(customButtonItem) ) | rpl::start_with_next([=](bool many, HistoryItem *item) { refreshPinnedBarButton(many, item); }, _pinnedBar->lifetime()); @@ -1854,7 +1854,7 @@ void RepliesWidget::checkPinnedBarState() { _pinnedTracker->shownMessageId(), [bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }), std::move(pinnedRefreshed), - std::move(markupRefreshed), + std::move(customButtonItem), _rootVisible.value() ) | rpl::map([=](Ui::MessageBarContent &&content, auto, auto, bool show) { const auto shown = !content.title.isEmpty() && !content.text.empty(); @@ -1935,43 +1935,29 @@ void RepliesWidget::refreshPinnedBarButton(bool many, HistoryItem *item) { controller()->showSection( std::make_shared(_topic, id.message.msg)); }; - if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) { - const auto &rows = replyMarkup->data.rows; - if ((rows.size() == 1) && (rows.front().size() == 1)) { - const auto text = rows.front().front().text; - if (!text.isEmpty()) { - auto button = object_ptr( - this, - rpl::single(text), - st::historyPinnedBotButton); - button->setTextTransform( - Ui::RoundButton::TextTransform::NoTransform); - button->setFullRadius(true); - button->setClickedCallback([=] { - Api::ActivateBotCommand( - _inner->prepareClickHandlerContext(item->fullId()), - 0, - 0); - }); - if (button->width() > st::historyPinnedBotButtonMaxWidth) { - button->setFullWidth(st::historyPinnedBotButtonMaxWidth); - } - struct State { - base::unique_qptr menu; - }; - const auto state = button->lifetime().make_state(); - _pinnedBar->contextMenuRequested( - ) | rpl::start_with_next([=, raw = button.data()] { - state->menu = base::make_unique_q(raw); - state->menu->addAction( - tr::lng_settings_events_pinned(tr::now), - openSection); - state->menu->popup(QCursor::pos()); - }, button->lifetime()); - _pinnedBar->setRightButton(std::move(button)); - return; - } + const auto context = [copy = _inner](FullMsgId itemId) { + if (const auto raw = copy.data()) { + return raw->prepareClickHandlerContext(itemId); } + return ClickHandlerContext(); + }; + auto customButton = CreatePinnedBarCustomButton(this, item, context); + if (customButton) { + struct State { + base::unique_qptr menu; + }; + const auto buttonRaw = customButton.data(); + const auto state = buttonRaw->lifetime().make_state(); + _pinnedBar->contextMenuRequested( + ) | rpl::start_with_next([=] { + state->menu = base::make_unique_q(buttonRaw); + state->menu->addAction( + tr::lng_settings_events_pinned(tr::now), + openSection); + state->menu->popup(QCursor::pos()); + }, buttonRaw->lifetime()); + _pinnedBar->setRightButton(std::move(customButton)); + return; } const auto close = !many; auto button = object_ptr(