Suggest invite to empty/discarded joining call.

This commit is contained in:
John Preston 2025-04-11 18:18:16 +04:00
parent d09d1d72e6
commit 53b739139b
11 changed files with 206 additions and 51 deletions

View file

@ -4932,7 +4932,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_confcall_already_joined_many#one" = "{user}, {other} and **{count}** other person already joined this call.";
"lng_confcall_already_joined_many#other" = "{user}, {other} and **{count}** other people already joined this call.";
"lng_confcall_join_button" = "Join Group Call";
"lng_confcall_create_call" = "Create New Call";
"lng_confcall_create_call" = "Start New Call";
"lng_confcall_create_call_description#one" = "You can add up to {count} participant to a call.";
"lng_confcall_create_call_description#other" = "You can add up to {count} participants to a call.";
"lng_confcall_create_title" = "New Call";
@ -4947,6 +4947,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_confcall_link_or" = "or";
"lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}";
"lng_confcall_link_join_link" = "Open call {arrow}";
"lng_confcall_inactive_title" = "Start Group Call";
"lng_confcall_inactive_about" = "This call is no longer active.\nYou can start a new one.";
"lng_confcall_invite_done_user" = "You're calling {user} to join.";
"lng_confcall_invite_done_many#one" = "You're calling **{count} person** to join.";
"lng_confcall_invite_done_many#other" = "You're calling **{count} people** to join.";

View file

@ -138,15 +138,16 @@ void PeerListBox::createMultiSelect() {
}
});
_select->resizeToWidth(_controller->contentWidth());
_select->moveToLeft(0, 0);
_select->moveToLeft(0, topSelectSkip());
}
void PeerListBox::appendQueryChangedCallback(Fn<void(QString)> callback) {
_customQueryChangedCallback = std::move(callback);
}
void PeerListBox::setAddedTopScrollSkip(int skip) {
void PeerListBox::setAddedTopScrollSkip(int skip, bool aboveSearch) {
_addedTopScrollSkip = skip;
_addedTopScrollAboveSearch = aboveSearch;
_scrollBottomFixed = false;
updateScrollSkips();
}
@ -155,7 +156,7 @@ void PeerListBox::showFinished() {
_controller->showFinished();
}
int PeerListBox::getTopScrollSkip() const {
int PeerListBox::topScrollSkip() const {
auto result = _addedTopScrollSkip;
if (_select && !_select->isHidden()) {
result += _select->height();
@ -163,12 +164,19 @@ int PeerListBox::getTopScrollSkip() const {
return result;
}
int PeerListBox::topSelectSkip() const {
return _addedTopScrollAboveSearch ? _addedTopScrollSkip : 0;
}
void PeerListBox::updateScrollSkips() {
// If we show / hide the search field scroll top is fixed.
// If we resize search field by bubbles scroll bottom is fixed.
setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
if (_select && !_select->animating()) {
_scrollBottomFixed = true;
setInnerTopSkip(topScrollSkip(), _scrollBottomFixed);
if (_select) {
_select->moveToLeft(0, topSelectSkip());
if (!_select->animating()) {
_scrollBottomFixed = true;
}
}
}
@ -232,8 +240,6 @@ void PeerListBox::resizeEvent(QResizeEvent *e) {
if (_select) {
_select->resizeToWidth(width());
_select->moveToLeft(0, 0);
updateScrollSkips();
}

View file

@ -1142,7 +1142,7 @@ public:
void peerListScrollToTop() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void setAddedTopScrollSkip(int skip);
void setAddedTopScrollSkip(int skip, bool aboveSearch = false);
void showFinished() override;
@ -1178,7 +1178,8 @@ private:
PaintRoundImageCallback paintUserpic,
anim::type animated);
void createMultiSelect();
int getTopScrollSkip() const;
[[nodiscard]] int topScrollSkip() const;
[[nodiscard]] int topSelectSkip() const;
void updateScrollSkips();
void searchQueryChanged(const QString &query);
@ -1189,6 +1190,7 @@ private:
std::unique_ptr<PeerListController> _controller;
Fn<void(PeerListBox*)> _init;
bool _scrollBottomFixed = false;
bool _addedTopScrollAboveSearch = false;
int _addedTopScrollSkip = 0;
};

View file

@ -635,6 +635,23 @@ callDeviceSelectionMenu: PopupMenu(groupCallPopupMenu) {
}
}
createCallListButton: OutlineButton(defaultPeerListButton) {
font: normalFont;
padding: margins(11px, 5px, 11px, 5px);
}
createCallListItem: PeerListItem(defaultPeerListItem) {
button: createCallListButton;
height: 52px;
photoPosition: point(12px, 6px);
namePosition: point(63px, 7px);
statusPosition: point(63px, 26px);
photoSize: 40px;
}
createCallList: PeerList(defaultPeerList) {
item: createCallListItem;
padding: margins(0px, 10px, 0px, 10px);
}
groupCallRecordingTimerPadding: margins(0px, 4px, 0px, 4px);
groupCallRecordingTimerFont: font(12px);
@ -658,26 +675,18 @@ groupCallMembersListCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) {
selectFg: groupCallActiveFg;
check: groupCallMembersListCheck;
}
groupCallMembersListItem: PeerListItem(defaultPeerListItem) {
button: OutlineButton(defaultPeerListButton) {
groupCallMembersListItem: PeerListItem(createCallListItem) {
button: OutlineButton(createCallListButton) {
textBg: groupCallMembersBg;
textBgOver: groupCallMembersBgOver;
textFg: groupCallMemberInactiveStatus;
textFgOver: groupCallMemberInactiveStatus;
font: normalFont;
padding: margins(11px, 5px, 11px, 5px);
ripple: groupCallRipple;
}
disabledCheckFg: groupCallMemberNotJoinedStatus;
checkbox: groupCallMembersListCheckbox;
height: 52px;
photoPosition: point(12px, 6px);
namePosition: point(63px, 7px);
statusPosition: point(63px, 26px);
photoSize: 40px;
nameFg: groupCallMembersFg;
nameFgChecked: groupCallMembersFg;
statusFg: groupCallMemberInactiveStatus;

View file

@ -266,6 +266,7 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) {
migrationInfo);
_currentGroupCall = std::move(call);
_currentGroupCallChanges.fire_copy(raw);
finishConferenceInvitations(args);
if (args.migrating) {
destroyCurrentCall(args.call.get(), args.linkSlug);
}
@ -294,12 +295,18 @@ void Instance::startedConferenceReady(
const auto real = call->conferenceCall().get();
const auto link = real->conferenceInviteLink();
const auto slug = Group::ExtractConferenceSlug(link);
finishConferenceInvitations(args);
destroyCurrentCall(real, slug);
}
void Instance::finishConferenceInvitations(const StartConferenceInfo &args) {
Expects(_currentGroupCallPanel != nullptr);
if (!args.invite.empty()) {
_currentGroupCallPanel->migrationInviteUsers(std::move(args.invite));
} else if (args.sharingLink) {
_currentGroupCallPanel->migrationShowShareLink();
}
destroyCurrentCall(real, slug);
}
void Instance::confirmLeaveCurrent(
@ -402,7 +409,7 @@ void Instance::destroyCall(not_null<Call*> call) {
void Instance::createCall(
not_null<UserData*> user,
Call::Type type,
CallType type,
bool isVideo) {
struct Performer final {
explicit Performer(Fn<void(bool, bool, const Performer &)> callback)

View file

@ -159,6 +159,7 @@ private:
void createCall(not_null<UserData*> user, CallType type, bool isVideo);
void destroyCall(not_null<Call*> call);
void finishConferenceInvitations(const StartConferenceInfo &args);
void createGroupCall(
Group::JoinInfo info,

View file

@ -77,23 +77,11 @@ object_ptr<Ui::GenericBox> ScreenSharingPrivacyRequestBox() {
#endif // Q_OS_MAC
}
void ConferenceCallJoinConfirm(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Data::GroupCall> call,
UserData *maybeInviter,
Fn<void(Fn<void()> close)> join) {
box->setStyle(st::confcallJoinBox);
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
object_ptr<Ui::RpWidget> MakeJoinCallLogo(not_null<QWidget*> parent) {
const auto logoSize = st::confcallJoinLogo.size();
const auto logoOuter = logoSize.grownBy(st::confcallJoinLogoPadding);
const auto logo = box->addRow(
object_ptr<Ui::RpWidget>(box),
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
auto result = object_ptr<Ui::RpWidget>(parent);
const auto logo = result.data();
logo->resize(logo->width(), logoOuter.height());
logo->paintRequest() | rpl::start_with_next([=] {
if (logo->width() < logoOuter.width()) {
@ -108,6 +96,24 @@ void ConferenceCallJoinConfirm(
p.drawEllipse(outer);
st::confcallJoinLogo.paintInCenter(p, outer);
}, logo->lifetime());
return result;
}
void ConferenceCallJoinConfirm(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Data::GroupCall> call,
UserData *maybeInviter,
Fn<void(Fn<void()> close)> join) {
box->setStyle(st::confcallJoinBox);
box->setWidth(st::boxWideWidth);
box->setNoContentMargin(true);
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
box->addRow(
MakeJoinCallLogo(box),
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(

View file

@ -31,6 +31,7 @@ class SessionShow;
namespace Ui {
class Show;
class RpWidget;
class GenericBox;
} // namespace Ui
@ -160,6 +161,9 @@ using StickedTooltips = base::flags<StickedTooltip>;
[[nodiscard]] object_ptr<Ui::GenericBox> ScreenSharingPrivacyRequestBox();
[[nodiscard]] object_ptr<Ui::RpWidget> MakeJoinCallLogo(
not_null<QWidget*> parent);
void ConferenceCallJoinConfirm(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Data::GroupCall> call,

View file

@ -29,12 +29,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h" // membersMarginTop
#include "styles/style_calls.h"
#include "styles/style_dialogs.h" // searchedBarHeight
#include "styles/style_layers.h" // boxWideWidth
namespace Calls::Group {
namespace {
@ -134,6 +136,7 @@ protected:
private:
[[nodiscard]] int fullCount() const;
void toggleRowSelected(not_null<PeerListRow*> row, bool video);
void addShareLinkButton();
const ConfInviteStyles _st;
const base::flat_set<not_null<UserData*>> _alreadyIn;
@ -383,6 +386,12 @@ void ConfInviteController::toggleRowSelected(
}
void ConfInviteController::prepareViewHook() {
if (_shareLink) {
addShareLinkButton();
}
}
void ConfInviteController::addShareLinkButton() {
auto button = object_ptr<Ui::PaddingWrap<Ui::SettingsButton>>(
nullptr,
object_ptr<Ui::SettingsButton>(
@ -757,9 +766,86 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
return Box<PeerListBox>(std::move(controller), initBox);
}
not_null<Ui::RpWidget*> CreateReActivateHeader(not_null<QWidget*> parent) {
const auto result = Ui::CreateChild<Ui::VerticalLayout>(parent);
result->add(
MakeJoinCallLogo(result),
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
result->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
result,
object_ptr<Ui::FlatLabel>(
result,
tr::lng_confcall_inactive_title(),
st::boxTitle)),
st::boxRowPadding + st::confcallLinkTitlePadding);
result->add(
object_ptr<Ui::FlatLabel>(
result,
tr::lng_confcall_inactive_about(),
st::confcallLinkCenteredText),
st::boxRowPadding + st::confcallLinkTitlePadding
)->setTryMakeSimilarLines(true);
Ui::AddDivider(result);
return result;
}
void InitReActivate(not_null<PeerListBox*> box) {
box->setTitle(rpl::producer<TextWithEntities>(nullptr));
box->setNoContentMargin(true);
const auto header = CreateReActivateHeader(box);
header->resizeToWidth(st::boxWideWidth);
header->heightValue() | rpl::start_with_next([=](int height) {
box->setAddedTopScrollSkip(height, true);
}, header->lifetime());
header->moveToLeft(0, 0);
}
object_ptr<Ui::BoxContent> PrepareInviteToEmptyBox(
std::shared_ptr<Data::GroupCall> call,
MsgId inviteMsgId) {
auto controller = std::make_unique<ConfInviteController>(
&call->session(),
ConfInviteDefaultStyles(),
base::flat_set<not_null<UserData*>>(),
nullptr);
const auto raw = controller.get();
raw->setStyleOverrides(&st::createCallList);
const auto initBox = [=](not_null<PeerListBox*> box) {
InitReActivate(box);
const auto join = [=] {
const auto weak = Ui::MakeWeak(box);
auto selected = raw->requests(box->collectSelectedRows());
Core::App().calls().startOrJoinConferenceCall({
.call = call,
.joinMessageId = inviteMsgId,
.invite = std::move(selected),
});
if (const auto strong = weak.data()) {
strong->closeBox();
}
};
box->addButton(
rpl::conditional(
raw->hasSelectedValue(),
tr::lng_group_call_confcall_add(),
tr::lng_create_group_create()),
join);
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
};
return Box<PeerListBox>(std::move(controller), initBox);
}
object_ptr<Ui::BoxContent> PrepareCreateCallBox(
not_null<::Window::SessionController*> window,
Fn<void()> created) {
Fn<void()> created,
MsgId discardedInviteMsgId) {
struct State {
bool creatingLink = false;
QPointer<PeerListBox> box;
@ -791,14 +877,21 @@ object_ptr<Ui::BoxContent> PrepareCreateCallBox(
&window->session(),
ConfInviteDefaultStyles(),
base::flat_set<not_null<UserData*>>(),
shareLink);
discardedInviteMsgId ? Fn<void()>() : shareLink);
const auto raw = controller.get();
if (discardedInviteMsgId) {
raw->setStyleOverrides(&st::createCallList);
}
const auto initBox = [=](not_null<PeerListBox*> box) {
box->setTitle(tr::lng_confcall_create_title());
if (discardedInviteMsgId) {
InitReActivate(box);
} else {
box->setTitle(tr::lng_confcall_create_title());
}
const auto create = [=] {
auto selected = raw->requests(box->collectSelectedRows());
if (selected.size() != 1) {
if (selected.size() != 1 || discardedInviteMsgId) {
Core::App().calls().startOrJoinConferenceCall({
.show = window->uiShow(),
.invite = std::move(selected),

View file

@ -16,6 +16,10 @@ class GroupCall;
struct InviteRequest;
} // namespace Calls
namespace Data {
class GroupCall;
} // namespace Data
namespace Calls::Group {
class InviteController final : public ParticipantsBoxController {
@ -87,8 +91,13 @@ private:
Fn<void(std::vector<InviteRequest>)> inviteUsers,
Fn<void()> shareLink);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareInviteToEmptyBox(
std::shared_ptr<Data::GroupCall> call,
MsgId inviteMsgId);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareCreateCallBox(
not_null<::Window::SessionController*> window,
Fn<void()> created = nullptr);
Fn<void()> created = nullptr,
MsgId discardedInviteMsgId = 0);
} // namespace Calls::Group

View file

@ -73,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/calls_instance.h" // Core::App().calls().inCall().
#include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_invite_controller.h"
#include "ui/boxes/calendar_box.h"
#include "ui/boxes/collectible_info_box.h"
#include "ui/boxes/confirm_box.h"
@ -899,14 +900,24 @@ void SessionNavigation::resolveConferenceCall(
const auto inviter = context
? context->from()->asUser()
: nullptr;
uiShow()->show(Box(
Calls::Group::ConferenceCallJoinConfirm,
call,
(inviter && !inviter->isSelf()) ? inviter : nullptr,
join));
if (inviteMsgId && call->participants().empty()) {
uiShow()->show(Calls::Group::PrepareInviteToEmptyBox(
call,
inviteMsgId));
} else {
uiShow()->show(Box(
Calls::Group::ConferenceCallJoinConfirm,
call,
(inviter && !inviter->isSelf()) ? inviter : nullptr,
join));
}
}, [&](const MTPDgroupCallDiscarded &data) {
if (inviteMsgId) {
showToast(tr::lng_confcall_not_accessible(tr::now));
uiShow()->show(
Calls::Group::PrepareCreateCallBox(
parentController(),
nullptr,
inviteMsgId));
} else {
showToast(tr::lng_confcall_link_inactive(tr::now));
}
@ -915,8 +926,13 @@ void SessionNavigation::resolveConferenceCall(
_conferenceCallRequestId = 0;
_conferenceCallSlug = QString();
_conferenceCallResolveContextId = FullMsgId();
if (base::take(_conferenceCallResolveContextId)) {
showToast(tr::lng_confcall_not_accessible(tr::now));
const auto inviteMsgId = base::take(_conferenceCallInviteMsgId);
if (inviteMsgId) {
uiShow()->show(
Calls::Group::PrepareCreateCallBox(
parentController(),
nullptr,
inviteMsgId));
} else {
showToast(tr::lng_confcall_link_inactive(tr::now));
}