Allow sharing link from confcall.

This commit is contained in:
John Preston 2025-03-29 12:52:36 +05:00
parent 0d8e5b139b
commit 9f3f715527
13 changed files with 226 additions and 53 deletions

View file

@ -4755,6 +4755,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_also_end_channel" = "End live stream";
"lng_group_call_settings_title" = "Settings";
"lng_group_call_invite" = "Invite Members";
"lng_group_call_invite_conf" = "Add People";
"lng_group_call_invited_status" = "invited";
"lng_group_call_muted_by_me_status" = "muted for you";
"lng_group_call_invite_title" = "Invite members";

View file

@ -804,6 +804,7 @@ groupCallAddMember: SettingsButton(defaultSettingsButton) {
ripple: groupCallRipple;
}
groupCallAddMemberIcon: icon {{ "info/info_add_member", groupCallMemberInactiveIcon, point(0px, 3px) }};
groupCallShareLinkIcon: icon {{ "menu/links_profile", groupCallMemberInactiveIcon, point(4px, 3px) }};
groupCallSubtitleLabel: FlatLabel(defaultFlatLabel) {
maxHeight: 18px;
textFg: groupCallMemberNotJoinedStatus;
@ -1489,12 +1490,15 @@ confcallLinkButton: RoundButton(defaultActiveButton) {
textTop: 12px;
style: semiboldTextStyle;
}
confcallLinkBox: Box(defaultBox) {
confcallLinkBoxInitial: Box(defaultBox) {
buttonPadding: margins(12px, 11px, 24px, 96px);
buttonHeight: 42px;
button: confcallLinkButton;
shadowIgnoreTopSkip: true;
}
confcallLinkBox: Box(confcallLinkBoxInitial) {
buttonPadding: margins(12px, 11px, 24px, 24px);
}
confcallLinkCopyButton: RoundButton(confcallLinkButton) {
icon: icon {{ "info/edit/links_copy", activeButtonFg }};
iconOver: icon {{ "info/edit/links_copy", activeButtonFgOver }};
@ -1516,3 +1520,21 @@ confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) {
confcallLinkFooterOrTop: 12px;
confcallLinkFooterOrSkip: 8px;
confcallLinkFooterOrLineTop: 9px;
groupCallLinkBox: Box(confcallLinkBox) {
bg: groupCallMembersBg;
title: FlatLabel(boxTitle) {
textFg: groupCallMembersFg;
}
titleAdditionalFg: groupCallMemberNotJoinedStatus;
}
groupCallLinkCenteredText: FlatLabel(confcallLinkCenteredText) {
textFg: groupCallMembersFg;
}
groupCallLinkPreview: InputField(defaultInputField) {
textBg: groupCallMembersBgOver;
textFg: groupCallMembersFg;
textMargins: margins(12px, 8px, 30px, 5px);
style: defaultTextStyle;
heightMin: 35px;
}

View file

@ -1153,13 +1153,17 @@ void GroupCall::setRtmpInfo(const Calls::Group::RtmpInfo &value) {
}
Data::GroupCall *GroupCall::lookupReal() const {
if (_conferenceCall) {
return _conferenceCall.get();
if (const auto conference = _conferenceCall.get()) {
return conference;
}
const auto real = _peer->groupCall();
return (real && real->id() == _id) ? real : nullptr;
}
std::shared_ptr<Data::GroupCall> GroupCall::conferenceCall() const {
return _conferenceCall;
}
rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
if (const auto real = lookupReal()) {
return rpl::single(not_null{ real });

View file

@ -256,6 +256,7 @@ public:
void setRtmpInfo(const Group::RtmpInfo &value);
[[nodiscard]] Data::GroupCall *lookupReal() const;
[[nodiscard]] std::shared_ptr<Data::GroupCall> conferenceCall() const;
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
[[nodiscard]] rpl::producer<QByteArray> emojiHashValue() const;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/group/calls_group_common.h"
#include "apiwrap.h"
#include "base/platform/base_platform_info.h"
#include "boxes/share_box.h"
#include "core/local_url_handlers.h"
@ -19,8 +20,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_media_view.h"
#include "styles/style_calls.h"
#include "styles/style_chat.h"
@ -86,16 +89,34 @@ void ConferenceCallJoinConfirm(
});
}
ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle() {
return {
.box = &st::groupCallLinkBox,
.close = &st::storiesStealthBoxClose,
.centerLabel = &st::groupCallLinkCenteredText,
.linkPreview = &st::groupCallLinkPreview,
.shareBox = std::make_shared<ShareBoxStyleOverrides>(
DarkShareBoxStyle()),
};
}
void ShowConferenceCallLinkBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
std::shared_ptr<Data::GroupCall> call,
const QString &link,
bool initial) {
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setStyle(st::confcallLinkBox);
ConferenceCallLinkArgs &&args) {
const auto st = args.st;
const auto initial = args.initial;
const auto weakWindow = args.weakWindow;
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
box->setStyle(st.box
? *st.box
: initial
? st::confcallLinkBoxInitial
: st::confcallLinkBox);
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
box->addTopButton(st::boxTitleClose, [=] {
box->addTopButton(st.close ? *st.close : st::boxTitleClose, [=] {
box->closeBox();
});
@ -108,19 +129,24 @@ void ShowConferenceCallLinkBox(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_confcall_link_title(),
st::boxTitle)),
st.box ? st.box->title : st::boxTitle)),
st::boxRowPadding + st::confcallLinkTitlePadding);
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_confcall_link_about(),
st::confcallLinkCenteredText),
(st.centerLabel
? *st.centerLabel
: st::confcallLinkCenteredText)),
st::boxRowPadding
)->setTryMakeSimilarLines(true);
Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 2);
const auto preview = box->addRow(
Info::BotStarRef::MakeLinkLabel(box, link));
Info::BotStarRef::MakeLinkLabel(
box,
link,
st.linkPreview));
Ui::AddSkip(box->verticalLayout());
const auto copyCallback = [=] {
@ -128,7 +154,10 @@ void ShowConferenceCallLinkBox(
box->uiShow()->showToast(tr::lng_username_copied(tr::now));
};
const auto shareCallback = [=] {
FastShareLink(controller, link);
FastShareLink(
show,
link,
st.shareBox ? *st.shareBox : ShareBoxStyleOverrides());
};
preview->setClickedCallback(copyCallback);
[[maybe_unused]] const auto share = box->addButton(
@ -155,6 +184,10 @@ void ShowConferenceCallLinkBox(
share->moveToRight(padding.right(), share->y(), width);
}, box->lifetime());
if (!initial) {
return;
}
const auto sep = Ui::CreateChild<Ui::FlatLabel>(
copy->parentWidget(),
tr::lng_confcall_link_or(),
@ -180,14 +213,18 @@ void ShowConferenceCallLinkBox(
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
[](QString v) { return Ui::Text::Link(v); }),
Ui::Text::WithEntities),
st::confcallLinkCenteredText);
(st.centerLabel
? *st.centerLabel
: st::confcallLinkCenteredText));
footer->setTryMakeSimilarLines(true);
footer->setClickHandlerFilter([=](const auto &...) {
const auto local = Core::TryConvertUrlToLocal(link);
controller->resolveConferenceCall(
local,
crl::guard(box, [=](bool ok) { if (ok) box->closeBox(); }),
true);
if (const auto controller = weakWindow.get()) {
controller->resolveConferenceCall(
local,
crl::guard(box, [=](bool ok) { if (ok) box->closeBox(); }),
true);
}
return false;
});
copy->geometryValue() | rpl::start_with_next([=](QRect geometry) {
@ -209,4 +246,33 @@ void ShowConferenceCallLinkBox(
}));
}
void ExportConferenceCallLink(
std::shared_ptr<Main::SessionShow> show,
std::shared_ptr<Data::GroupCall> call,
ConferenceCallLinkArgs &&args) {
const auto session = &show->session();
const auto finished = std::move(args.finished);
using Flag = MTPphone_ExportGroupCallInvite::Flag;
session->api().request(MTPphone_ExportGroupCallInvite(
MTP_flags(Flag::f_can_self_unmute),
call->input()
)).done([=](const MTPphone_ExportedGroupCallInvite &result) {
const auto link = qs(result.data().vlink());
Calls::Group::ShowConferenceCallLinkBox(
show,
call,
link,
base::duplicate(args));
if (const auto onstack = finished) {
finished(true);
}
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
if (const auto onstack = finished) {
finished(false);
}
}).send();
}
} // namespace Calls::Group

View file

@ -8,13 +8,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/object_ptr.h"
#include "base/weak_ptr.h"
class UserData;
struct ShareBoxStyleOverrides;
namespace style {
struct Box;
struct FlatLabel;
struct IconButton;
struct InputField;
} // namespace style
namespace Data {
class GroupCall;
} // namespace Data
namespace Main {
class SessionShow;
} // namespace Main
namespace Ui {
class Show;
class GenericBox;
@ -118,10 +131,30 @@ void ConferenceCallJoinConfirm(
std::shared_ptr<Data::GroupCall> call,
Fn<void()> join);
struct ConferenceCallLinkStyleOverrides {
const style::Box *box = nullptr;
const style::IconButton *close = nullptr;
const style::FlatLabel *centerLabel = nullptr;
const style::InputField *linkPreview = nullptr;
std::shared_ptr<ShareBoxStyleOverrides> shareBox;
};
[[nodiscard]] ConferenceCallLinkStyleOverrides DarkConferenceCallLinkStyle();
struct ConferenceCallLinkArgs {
bool initial = false;
Fn<void(bool)> finished;
base::weak_ptr<Window::SessionController> weakWindow = nullptr;
ConferenceCallLinkStyleOverrides st;
};
void ShowConferenceCallLinkBox(
not_null<Window::SessionController*> controller,
std::shared_ptr<Main::SessionShow> show,
std::shared_ptr<Data::GroupCall> call,
const QString &link,
bool initial = false);
ConferenceCallLinkArgs &&args);
void ExportConferenceCallLink(
std::shared_ptr<Main::SessionShow> show,
std::shared_ptr<Data::GroupCall> call,
ConferenceCallLinkArgs &&args);
} // namespace Calls::Group

View file

@ -1615,6 +1615,7 @@ rpl::producer<int> Members::desiredHeightValue() const {
return rpl::combine(
heightValue(),
_addMemberButton.value(),
_shareLinkButton.value(),
_listController->fullCountValue(),
_mode.value()
) | rpl::map([=] {
@ -1626,8 +1627,11 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
using namespace rpl::mappers;
const auto peer = call->peer();
const auto conference = call->conference();
const auto canAddByPeer = [=](not_null<PeerData*> peer) {
if (peer->isBroadcast()) {
if (conference) {
return rpl::single(true) | rpl::type_erased();
} else if (peer->isBroadcast()) {
return rpl::single(false) | rpl::type_erased();
}
return rpl::combine(
@ -1638,6 +1642,9 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
}) | rpl::type_erased();
};
const auto canInviteByLinkByPeer = [=](not_null<PeerData*> peer) {
if (conference) {
return rpl::single(true) | rpl::type_erased();
}
const auto channel = peer->asChannel();
if (!channel) {
return rpl::single(false) | rpl::type_erased();
@ -1672,11 +1679,18 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
_addMemberButton = nullptr;
updateControlsGeometry();
}
if (const auto old = _shareLinkButton.current()) {
delete old;
_shareLinkButton = nullptr;
updateControlsGeometry();
}
return;
}
auto addMember = Settings::CreateButtonWithIcon(
_layout.get(),
tr::lng_group_call_invite(),
(conference
? tr::lng_group_call_invite_conf()
: tr::lng_group_call_invite()),
st::groupCallAddMember,
{ .icon = &st::groupCallAddMemberIcon });
addMember->clicks(
@ -1688,6 +1702,21 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
delete _addMemberButton.current();
_addMemberButton = addMember.data();
_layout->insert(3, std::move(addMember));
if (conference) {
auto shareLink = Settings::CreateButtonWithIcon(
_layout.get(),
tr::lng_group_invite_share(),
st::groupCallAddMember,
{ .icon = &st::groupCallShareLinkIcon });
shareLink->clicks() | rpl::to_empty | rpl::start_to_stream(
_shareLinkRequests,
shareLink->lifetime());
shareLink->show();
shareLink->resizeToWidth(_layout->width());
delete _shareLinkButton.current();
_shareLinkButton = shareLink.data();
_layout->insert(4, std::move(shareLink));
}
}, lifetime());
updateControlsGeometry();

View file

@ -59,6 +59,9 @@ public:
[[nodiscard]] rpl::producer<> addMembersRequests() const {
return _addMemberRequests.events();
}
[[nodiscard]] rpl::producer<> shareLinkRequests() const {
return _shareLinkRequests.events();
}
[[nodiscard]] MembersRow *lookupRow(not_null<PeerData*> peer) const;
[[nodiscard]] not_null<MembersRow*> rtmpFakeRow(
@ -106,10 +109,12 @@ private:
const not_null<Ui::RpWidget*> _videoWrap;
std::unique_ptr<Viewport> _viewport;
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
rpl::variable<Ui::RpWidget*> _shareLinkButton = nullptr;
RpWidget *_topSkip = nullptr;
RpWidget *_bottomSkip = nullptr;
ListWidget *_list = nullptr;
rpl::event_stream<> _addMemberRequests;
rpl::event_stream<> _shareLinkRequests;
mutable std::unique_ptr<MembersRow> _rtmpFakeRow;

View file

@ -934,16 +934,12 @@ void Panel::setupMembers() {
_members->toggleMuteRequests(
) | rpl::start_with_next([=](MuteRequest request) {
if (_call) {
_call->toggleMute(request);
}
_call->toggleMute(request);
}, _callLifetime);
_members->changeVolumeRequests(
) | rpl::start_with_next([=](VolumeRequest request) {
if (_call) {
_call->changeVolume(request);
}
_call->changeVolume(request);
}, _callLifetime);
_members->kickParticipantRequests(
@ -964,6 +960,21 @@ void Panel::setupMembers() {
}
}, _callLifetime);
const auto exporting = std::make_shared<bool>();
_members->shareLinkRequests(
) | rpl::start_with_next([=] {
Expects(_call->conference());
if (*exporting) {
return;
}
*exporting = true;
ExportConferenceCallLink(uiShow(), _call->conferenceCall(), {
.st = DarkConferenceCallLinkStyle(),
.finished = [=](bool) { *exporting = false; },
});
}, _callLifetime);
_call->videoEndpointLargeValue(
) | rpl::start_with_next([=](const VideoEndpoint &large) {
if (large && mode() != PanelMode::Wide) {

View file

@ -390,14 +390,16 @@ void AddFullWidthButtonFooter(
object_ptr<Ui::AbstractButton> MakeLinkLabel(
not_null<QWidget*> parent,
const QString &link) {
const QString &link,
const style::InputField *stOverride) {
const auto &st = stOverride ? *stOverride : st::dialogsFilter;
const auto text = link.startsWith(u"https://"_q)
? link.mid(8)
: link.startsWith(u"http://"_q)
? link.mid(7)
: link;
const auto margins = st::dialogsFilter.textMargins;
const auto height = st::dialogsFilter.heightMin;
const auto margins = st.textMargins;
const auto height = st.heightMin;
const auto skip = margins.left();
auto result = object_ptr<Ui::AbstractButton>(parent);
@ -408,12 +410,12 @@ object_ptr<Ui::AbstractButton> MakeLinkLabel(
auto p = QPainter(raw);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::dialogsFilter.textBg);
p.setBrush(st.textBg);
const auto radius = st::roundRadiusLarge;
p.drawRoundedRect(0, 0, raw->width(), height, radius, radius);
const auto font = st::dialogsFilter.style.font;
p.setPen(st::dialogsFilter.textFg);
const auto font = st.style.font;
p.setPen(st.textFg);
p.setFont(font);
const auto available = raw->width() - skip * 2;
p.drawText(
@ -1065,4 +1067,4 @@ object_ptr<Ui::RpWidget> CreateLinkHeaderIcon(
return CreateLinkIcon(parent, session, usersCount);
}
} // namespace Info::BotStarRef
} // namespace Info::BotStarRef

View file

@ -21,6 +21,7 @@ class Show;
namespace style {
struct RoundButton;
struct InputField;
} // namespace style
namespace Main {
@ -107,7 +108,8 @@ void FinishProgram(
[[nodiscard]] object_ptr<Ui::AbstractButton> MakeLinkLabel(
not_null<QWidget*> parent,
const QString &link);
const QString &link,
const style::InputField *stOverride = nullptr);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateLinkHeaderIcon(
not_null<QWidget*> parent,
not_null<Main::Session*> session,

View file

@ -259,3 +259,6 @@ darkGiftTableMessage: FlatLabel(giveawayGiftMessage) {
textFg: groupCallMembersFg;
palette: darkGiftPalette;
}
darkGiftCodeLink: FlatLabel(giveawayGiftCodeLink) {
textFg: mediaviewMenuFg;
}

View file

@ -123,7 +123,7 @@ constexpr auto kPlayStatusLimit = 2;
(height - st::inviteViaLinkIcon.height()) / 2);
}, icon->lifetime());
const auto creating = result->lifetime().make_state<int32>();
const auto creating = std::make_shared<int32>();
result->setClickedCallback([=] {
if (*creating) {
return;
@ -143,25 +143,19 @@ constexpr auto kPlayStatusLimit = 2;
false, // rtmp
true); // conference
call->processFullCall(result);
using Flag = MTPphone_ExportGroupCallInvite::Flag;
session->api().request(MTPphone_ExportGroupCallInvite(
MTP_flags(Flag::f_can_self_unmute),
MTP_inputGroupCall(data.vid(), data.vaccess_hash())
)).done(crl::guard(controller, [=](
const MTPphone_ExportedGroupCallInvite &result) {
const auto link = qs(result.data().vlink());
Calls::Group::ShowConferenceCallLinkBox(
controller,
call,
link,
true);
if (const auto onstack = done) {
const auto finished = [=](bool ok) {
if (!ok) {
*creating = 0;
} else if (const auto onstack = done) {
onstack();
}
})).fail(crl::guard(controller, [=](const MTP::Error &error) {
show->showToast(error.type());
*creating = 0;
})).send();
};
const auto show = controller->uiShow();
Calls::Group::ExportConferenceCallLink(show, call, {
.initial = true,
.finished = finished,
.weakWindow = controller,
});
});
})).fail(crl::guard(controller, [=](const MTP::Error &error) {
show->showToast(error.type());