Show "Join" button for pinned call links.

This commit is contained in:
John Preston 2025-05-07 18:03:59 +04:00
parent fb25d90b48
commit ee7a2b564b
4 changed files with 160 additions and 99 deletions

View file

@ -7651,12 +7651,12 @@ void HistoryWidget::checkPinnedBarState() {
} }
return (count > 1); return (count > 1);
}) | rpl::distinct_until_changed(); }) | rpl::distinct_until_changed();
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup( auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton(
&session(), &session(),
_pinnedTracker->shownMessageId()); _pinnedTracker->shownMessageId());
rpl::combine( rpl::combine(
rpl::duplicate(pinnedRefreshed), rpl::duplicate(pinnedRefreshed),
rpl::duplicate(markupRefreshed) rpl::duplicate(customButtonItem)
) | rpl::start_with_next([=](bool many, HistoryItem *item) { ) | rpl::start_with_next([=](bool many, HistoryItem *item) {
refreshPinnedBarButton(many, item); refreshPinnedBarButton(many, item);
}, _pinnedBar->lifetime()); }, _pinnedBar->lifetime());
@ -7667,7 +7667,7 @@ void HistoryWidget::checkPinnedBarState() {
_pinnedTracker->shownMessageId(), _pinnedTracker->shownMessageId(),
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }), [bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
std::move(pinnedRefreshed), std::move(pinnedRefreshed),
std::move(markupRefreshed) std::move(customButtonItem)
) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) { ) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) {
const auto id = (!content.title.isEmpty() || !content.text.empty()) const auto id = (!content.title.isEmpty() || !content.text.empty())
? _pinnedTracker->currentMessageId().message ? _pinnedTracker->currentMessageId().message
@ -7805,58 +7805,31 @@ void HistoryWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
? id.message.msg ? id.message.msg
: (id.message.msg - ServerMaxMsgId)))); : (id.message.msg - ServerMaxMsgId))));
}; };
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) { const auto context = [copy = _list](FullMsgId itemId) {
const auto &rows = replyMarkup->data.rows; if (const auto raw = copy.data()) {
if ((rows.size() == 1) && (rows.front().size() == 1)) { return raw->prepareClickHandlerContext(itemId);
const auto text = rows.front().front().text;
if (!text.isEmpty()) {
const auto &st = st::historyPinnedBotButton;
auto button = object_ptr<Ui::RoundButton>(
this,
rpl::never<QString>(),
st);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button.data(),
text,
st::historyPinnedBotLabel);
if (label->width() > st::historyPinnedBotButtonMaxWidth) {
label->resizeToWidth(st::historyPinnedBotButtonMaxWidth);
} }
button->setFullWidth(label->width() return ClickHandlerContext();
+ st.padding.left() };
+ st.padding.right() auto customButton = CreatePinnedBarCustomButton(this, item, context);
+ st.height); if (customButton) {
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 { struct State {
base::unique_qptr<Ui::PopupMenu> menu; base::unique_qptr<Ui::PopupMenu> menu;
}; };
const auto state = button->lifetime().make_state<State>(); const auto buttonRaw = customButton.data();
const auto state = buttonRaw->lifetime().make_state<State>();
_pinnedBar->contextMenuRequested( _pinnedBar->contextMenuRequested(
) | rpl::start_with_next([=, raw = button.data()] { ) | rpl::start_with_next([=] {
state->menu = base::make_unique_q<Ui::PopupMenu>(raw); state->menu = base::make_unique_q<Ui::PopupMenu>(buttonRaw);
state->menu->addAction( state->menu->addAction(
tr::lng_settings_events_pinned(tr::now), tr::lng_settings_events_pinned(tr::now),
openSection); openSection);
state->menu->popup(QCursor::pos()); state->menu->popup(QCursor::pos());
}, button->lifetime()); }, buttonRaw->lifetime());
_pinnedBar->setRightButton(std::move(button)); _pinnedBar->setRightButton(std::move(customButton));
return; return;
} }
}
}
const auto close = !many; const auto close = !many;
auto button = object_ptr<Ui::IconButton>( auto button = object_ptr<Ui::IconButton>(
this, this,

View file

@ -7,17 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/view/history_view_pinned_bar.h" #include "history/view/history_view_pinned_bar.h"
#include "api/api_bot.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_poll.h" #include "data/data_poll.h"
#include "data/data_web_page.h"
#include "history/view/history_view_pinned_tracker.h" #include "history/view/history_view_pinned_tracker.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/history.h" #include "history/history.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "apiwrap.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.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
@ -153,6 +160,45 @@ auto WithPinnedTitle(not_null<Main::Session*> session, PinnedId id) {
}; };
} }
[[nodiscard]] object_ptr<Ui::RoundButton> MakePinnedBarCustomButton(
not_null<QWidget*> parent,
const QString &buttonText,
Fn<void()> clickCallback) {
const auto &stButton = st::historyPinnedBotButton;
const auto &stLabel = st::historyPinnedBotLabel;
auto button = object_ptr<Ui::RoundButton>(
parent,
rpl::never<QString>(), // Text is handled by the inner label.
stButton);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
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 } // namespace
rpl::producer<Ui::MessageBarContent> MessageBarContentByItemId( rpl::producer<Ui::MessageBarContent> MessageBarContentByItemId(
@ -178,7 +224,7 @@ rpl::producer<Ui::MessageBarContent> PinnedBarContent(
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
} }
rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup( rpl::producer<HistoryItem*> PinnedBarItemWithCustomButton(
not_null<Main::Session*> session, not_null<Main::Session*> session,
rpl::producer<PinnedId> id) { rpl::producer<PinnedId> id) {
return rpl::make_producer<HistoryItem*>([=, return rpl::make_producer<HistoryItem*>([=,
@ -187,7 +233,7 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
consumer.put_next(nullptr); consumer.put_next(nullptr);
struct State { struct State {
bool hasReplyMarkup = false; bool hasCustomButton = false;
base::has_weak_ptr guard; base::has_weak_ptr guard;
rpl::lifetime lifetime; rpl::lifetime lifetime;
FullMsgId resolvedId; FullMsgId resolvedId;
@ -196,10 +242,17 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
const auto pushUnique = [=](not_null<HistoryItem*> item) { const auto pushUnique = [=](not_null<HistoryItem*> item) {
const auto replyMarkup = item->inlineReplyMarkup(); 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; return;
} }
state->hasReplyMarkup = (replyMarkup != nullptr); state->hasCustomButton = possiblyHasCustomButton;
consumer.put_next(item.get()); consumer.put_next(item.get());
}; };
@ -217,12 +270,14 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
using Update = Data::MessageUpdate; using Update = Data::MessageUpdate;
session->changes().messageUpdates( session->changes().messageUpdates(
item, item,
Update::Flag::ReplyMarkup | Update::Flag::Destroyed (Update::Flag::ReplyMarkup
| Update::Flag::Edited
| Update::Flag::Destroyed)
) | rpl::start_with_next([=](const Update &update) { ) | rpl::start_with_next([=](const Update &update) {
if (update.flags & Update::Flag::Destroyed) { if (update.flags & Update::Flag::Destroyed) {
state->lifetime.destroy(); state->lifetime.destroy();
invalidate_weak_ptrs(&state->guard); invalidate_weak_ptrs(&state->guard);
state->hasReplyMarkup = false; state->hasCustomButton = false;
consumer.put_next(nullptr); consumer.put_next(nullptr);
} else { } else {
pushUnique(update.item); pushUnique(update.item);
@ -248,4 +303,42 @@ rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup(
}); });
} }
[[nodiscard]] object_ptr<Ui::RoundButton> CreatePinnedBarCustomButton(
not_null<QWidget*> parent,
HistoryItem *item,
Fn<ClickHandlerContext(FullMsgId)> 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 } // namespace HistoryView

View file

@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/object_ptr.h"
#include "ui/chat/message_bar.h" #include "ui/chat/message_bar.h"
#include <tuple> #include <tuple>
struct ClickHandlerContext;
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -18,6 +21,7 @@ class Session;
namespace Ui { namespace Ui {
class IconButton; class IconButton;
class PlainShadow; class PlainShadow;
class RoundButton;
struct MessageBarContent; struct MessageBarContent;
} // namespace Ui } // namespace Ui
@ -51,8 +55,13 @@ struct PinnedId {
rpl::producer<PinnedId> id, rpl::producer<PinnedId> id,
Fn<void()> repaint); Fn<void()> repaint);
[[nodiscard]] rpl::producer<HistoryItem*> PinnedBarItemWithReplyMarkup( [[nodiscard]] rpl::producer<HistoryItem*> PinnedBarItemWithCustomButton(
not_null<Main::Session*> session, not_null<Main::Session*> session,
rpl::producer<PinnedId> id); rpl::producer<PinnedId> id);
[[nodiscard]] object_ptr<Ui::RoundButton> CreatePinnedBarCustomButton(
not_null<QWidget*> parent,
HistoryItem *item,
Fn<ClickHandlerContext(FullMsgId)> context);
} // namespace HistoryView } // namespace HistoryView

View file

@ -1838,12 +1838,12 @@ void RepliesWidget::checkPinnedBarState() {
} }
return (count > 1); return (count > 1);
}) | rpl::distinct_until_changed(); }) | rpl::distinct_until_changed();
auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup( auto customButtonItem = HistoryView::PinnedBarItemWithCustomButton(
&session(), &session(),
_pinnedTracker->shownMessageId()); _pinnedTracker->shownMessageId());
rpl::combine( rpl::combine(
rpl::duplicate(pinnedRefreshed), rpl::duplicate(pinnedRefreshed),
rpl::duplicate(markupRefreshed) rpl::duplicate(customButtonItem)
) | rpl::start_with_next([=](bool many, HistoryItem *item) { ) | rpl::start_with_next([=](bool many, HistoryItem *item) {
refreshPinnedBarButton(many, item); refreshPinnedBarButton(many, item);
}, _pinnedBar->lifetime()); }, _pinnedBar->lifetime());
@ -1854,7 +1854,7 @@ void RepliesWidget::checkPinnedBarState() {
_pinnedTracker->shownMessageId(), _pinnedTracker->shownMessageId(),
[bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }), [bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
std::move(pinnedRefreshed), std::move(pinnedRefreshed),
std::move(markupRefreshed), std::move(customButtonItem),
_rootVisible.value() _rootVisible.value()
) | rpl::map([=](Ui::MessageBarContent &&content, auto, auto, bool show) { ) | rpl::map([=](Ui::MessageBarContent &&content, auto, auto, bool show) {
const auto shown = !content.title.isEmpty() && !content.text.empty(); const auto shown = !content.title.isEmpty() && !content.text.empty();
@ -1935,44 +1935,30 @@ void RepliesWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
controller()->showSection( controller()->showSection(
std::make_shared<PinnedMemento>(_topic, id.message.msg)); std::make_shared<PinnedMemento>(_topic, id.message.msg));
}; };
if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) { const auto context = [copy = _inner](FullMsgId itemId) {
const auto &rows = replyMarkup->data.rows; if (const auto raw = copy.data()) {
if ((rows.size() == 1) && (rows.front().size() == 1)) { return raw->prepareClickHandlerContext(itemId);
const auto text = rows.front().front().text;
if (!text.isEmpty()) {
auto button = object_ptr<Ui::RoundButton>(
this,
rpl::single(text),
st::historyPinnedBotButton);
button->setTextTransform(
Ui::RoundButton::TextTransform::NoTransform);
button->setFullRadius(true);
button->setClickedCallback([=] {
Api::ActivateBotCommand(
_inner->prepareClickHandlerContext(item->fullId()),
0,
0);
});
if (button->width() > st::historyPinnedBotButtonMaxWidth) {
button->setFullWidth(st::historyPinnedBotButtonMaxWidth);
} }
return ClickHandlerContext();
};
auto customButton = CreatePinnedBarCustomButton(this, item, context);
if (customButton) {
struct State { struct State {
base::unique_qptr<Ui::PopupMenu> menu; base::unique_qptr<Ui::PopupMenu> menu;
}; };
const auto state = button->lifetime().make_state<State>(); const auto buttonRaw = customButton.data();
const auto state = buttonRaw->lifetime().make_state<State>();
_pinnedBar->contextMenuRequested( _pinnedBar->contextMenuRequested(
) | rpl::start_with_next([=, raw = button.data()] { ) | rpl::start_with_next([=] {
state->menu = base::make_unique_q<Ui::PopupMenu>(raw); state->menu = base::make_unique_q<Ui::PopupMenu>(buttonRaw);
state->menu->addAction( state->menu->addAction(
tr::lng_settings_events_pinned(tr::now), tr::lng_settings_events_pinned(tr::now),
openSection); openSection);
state->menu->popup(QCursor::pos()); state->menu->popup(QCursor::pos());
}, button->lifetime()); }, buttonRaw->lifetime());
_pinnedBar->setRightButton(std::move(button)); _pinnedBar->setRightButton(std::move(customButton));
return; return;
} }
}
}
const auto close = !many; const auto close = !many;
auto button = object_ptr<Ui::IconButton>( auto button = object_ptr<Ui::IconButton>(
this, this,