Implement "Create New Call" interface.

This commit is contained in:
John Preston 2025-04-10 13:38:13 +04:00
parent 042f51e58f
commit c6b2967da0
12 changed files with 375 additions and 227 deletions

View file

@ -4932,6 +4932,10 @@ 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_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";
"lng_confcall_create_link" = "Create Call Link";
"lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call.";
"lng_confcall_link_revoke" = "Revoke link";

View file

@ -1505,6 +1505,42 @@ groupCallCalendarColors: CalendarColors {
titleTextColor: groupCallMembersFg;
}
createCallInviteLink: SettingsButton(defaultSettingsButton) {
textFg: windowActiveTextFg;
textFgOver: windowActiveTextFg;
textBg: windowBg;
textBgOver: windowBgOver;
style: TextStyle(defaultTextStyle) {
font: font(14px semibold);
}
height: 20px;
padding: margins(74px, 8px, 8px, 9px);
}
createCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", windowActiveTextFg }};
createCallInviteLinkIconPosition: point(23px, 2px);
createCallVideo: IconButton {
width: 36px;
height: 52px;
icon: icon {{ "info/info_media_video", menuIconFg }};
iconOver: icon {{ "info/info_media_video", menuIconFgOver }};
iconPosition: point(-1px, -1px);
ripple: defaultRippleAnimation;
rippleAreaPosition: point(0px, 8px);
rippleAreaSize: 36px;
}
createCallVideoActive: icon {{ "info/info_media_video", windowActiveTextFg }};
createCallVideoMargins: margins(0px, 0px, 10px, 0px);
createCallAudio: IconButton(createCallVideo) {
icon: icon {{ "menu/phone", menuIconFg }};
iconOver: icon {{ "menu/phone", menuIconFgOver }};
}
createCallAudioActive: icon {{ "menu/phone", windowActiveTextFg }};
createCallAudioMargins: margins(0px, 0px, 4px, 0px);
confcallLinkButton: RoundButton(defaultActiveButton) {
height: 42px;
textTop: 12px;
@ -1572,26 +1608,17 @@ confcallInviteParticipants: FlatLabel(defaultFlatLabel) {
textFg: callNameFg;
}
confcallInviteParticipantsPadding: margins(8px, 3px, 12px, 2px);
confcallInviteVideo: IconButton {
width: 36px;
height: 52px;
confcallInviteVideo: IconButton(createCallVideo) {
icon: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }};
iconOver: icon {{ "info/info_media_video", groupCallMemberInactiveIcon }};
iconPosition: point(-1px, -1px);
ripple: groupCallRipple;
rippleAreaPosition: point(0px, 8px);
rippleAreaSize: 36px;
}
confcallInviteVideoActive: icon {{ "info/info_media_video", groupCallActiveFg }};
confcallInviteVideoMargins: margins(0px, 0px, 10px, 0px);
confcallInviteAudio: IconButton(confcallInviteVideo) {
icon: icon {{ "menu/phone", groupCallMemberInactiveIcon }};
iconOver: icon {{ "menu/phone", groupCallMemberInactiveIcon }};
}
confcallInviteAudioActive: icon {{ "menu/phone", groupCallActiveFg }};
confcallInviteAudioMargins: margins(0px, 0px, 4px, 0px);
groupCallLinkBox: Box(confcallLinkBox) {
bg: groupCallMembersBg;
@ -1610,23 +1637,17 @@ groupCallLinkPreview: InputField(defaultInputField) {
style: defaultTextStyle;
heightMin: 35px;
}
groupCallInviteLink: SettingsButton(defaultSettingsButton) {
groupCallInviteLink: SettingsButton(createCallInviteLink) {
textFg: mediaviewTextLinkFg;
textFgOver: mediaviewTextLinkFg;
textBg: groupCallMembersBg;
textBgOver: groupCallMembersBgOver;
style: TextStyle(defaultTextStyle) {
font: font(14px semibold);
}
height: 20px;
padding: margins(63px, 8px, 8px, 9px);
ripple: groupCallRipple;
}
groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLinkFg }};
groupCallInviteLinkIconPosition: point(23px, 2px);
confcallLinkMenu: IconButton(boxTitleClose) {
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};

View file

@ -9,17 +9,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "core/application.h"
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_invite_controller.h"
#include "calls/calls_instance.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "mainwidget.h"
#include "window/window_session_controller.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_changes.h"
@ -32,6 +41,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "api/api_updates.h"
#include "apiwrap.h"
#include "info/profile/info_profile_icon.h"
#include "settings/settings_calls.h"
#include "styles/style_info.h" // infoTopBarMenu
#include "styles/style_layers.h" // st::boxLabel.
#include "styles/style_calls.h"
#include "styles/style_boxes.h"
@ -150,7 +162,7 @@ void GroupCallRow::rightActionStopLastRipple() {
namespace GroupCalls {
ListController::ListController(not_null<Window::SessionController*> window)
ListController::ListController(not_null<::Window::SessionController*> window)
: _window(window) {
setStyleOverrides(&st::peerListSingleRow);
}
@ -227,7 +239,7 @@ void ListController::rowClicked(not_null<PeerListRow*> row) {
crl::on_main(window, [=, peer = row->peer()] {
window->showPeerHistory(
peer,
Window::SectionShow::Way::ClearStack);
::Window::SectionShow::Way::ClearStack);
});
}
@ -470,7 +482,7 @@ void BoxController::Row::rightActionStopLastRipple() {
}
}
BoxController::BoxController(not_null<Window::SessionController*> window)
BoxController::BoxController(not_null<::Window::SessionController*> window)
: _window(window)
, _api(&_window->session().mtp()) {
}
@ -591,7 +603,7 @@ void BoxController::rowClicked(not_null<PeerListRow*> row) {
crl::on_main(window, [=, peer = row->peer()] {
window->showPeerHistory(
peer,
Window::SectionShow::Way::ClearStack,
::Window::SectionShow::Way::ClearStack,
itemId);
});
}
@ -698,7 +710,7 @@ std::unique_ptr<PeerListRow> BoxController::createRow(
void ClearCallsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window) {
not_null<::Window::SessionController*> window) {
const auto weak = Ui::MakeWeak(box);
box->addRow(
object_ptr<Ui::FlatLabel>(
@ -756,4 +768,133 @@ void ClearCallsBox(
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
[[nodiscard]] not_null<Ui::SettingsButton*> AddCreateCallButton(
not_null<Ui::VerticalLayout*> container,
not_null<::Window::SessionController*> controller,
Fn<void()> done) {
const auto result = container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_confcall_create_call(),
st::inviteViaLinkButton), QMargins());
Ui::AddSkip(container);
Ui::AddDividerText(
container,
tr::lng_confcall_create_call_description(
lt_count,
rpl::single(controller->session().appConfig().confcallSizeLimit()
* 1.),
Ui::Text::WithEntities));
const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
result,
st::inviteViaLinkIcon,
QPoint());
result->heightValue(
) | rpl::start_with_next([=](int height) {
icon->moveToLeft(
st::inviteViaLinkIconPosition.x(),
(height - st::inviteViaLinkIcon.height()) / 2);
}, icon->lifetime());
result->setClickedCallback([=] {
controller->show(Group::PrepareCreateCallBox(controller, done));
});
return result;
}
void ShowCallsBox(not_null<::Window::SessionController*> window) {
struct State {
State(not_null<::Window::SessionController*> window)
: callsController(window)
, groupCallsController(window) {
}
Calls::BoxController callsController;
PeerListContentDelegateSimple callsDelegate;
Calls::GroupCalls::ListController groupCallsController;
PeerListContentDelegateSimple groupCallsDelegate;
base::unique_qptr<Ui::PopupMenu> menu;
};
window->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto state = box->lifetime().make_state<State>(window);
const auto groupCalls = box->addRow(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box,
object_ptr<Ui::VerticalLayout>(box)),
{});
groupCalls->hide(anim::type::instant);
groupCalls->toggleOn(state->groupCallsController.shownValue());
Ui::AddSubsectionTitle(
groupCalls->entity(),
tr::lng_call_box_groupcalls_subtitle());
state->groupCallsDelegate.setContent(groupCalls->entity()->add(
object_ptr<PeerListContent>(box, &state->groupCallsController),
{}));
state->groupCallsController.setDelegate(&state->groupCallsDelegate);
Ui::AddSkip(groupCalls->entity());
Ui::AddDivider(groupCalls->entity());
Ui::AddSkip(groupCalls->entity());
const auto button = AddCreateCallButton(
box->verticalLayout(),
window,
crl::guard(box, [=] { box->closeBox(); }));
button->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter);
}) | rpl::start_with_next([=] {
state->callsDelegate.peerListMouseLeftGeometry();
}, button->lifetime());
const auto content = box->addRow(
object_ptr<PeerListContent>(box, &state->callsController),
{});
state->callsDelegate.setContent(content);
state->callsController.setDelegate(&state->callsDelegate);
box->setWidth(state->callsController.contentWidth());
state->callsController.boxHeightValue(
) | rpl::start_with_next([=](int height) {
box->setMinHeight(height);
}, box->lifetime());
box->setTitle(tr::lng_call_box_title());
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
const auto menuButton = box->addTopButton(st::infoTopBarMenu);
menuButton->setClickedCallback([=] {
state->menu = base::make_unique_q<Ui::PopupMenu>(
menuButton,
st::popupMenuWithIcons);
const auto showSettings = [=] {
window->showSettings(
Settings::Calls::Id(),
::Window::SectionShow(anim::type::instant));
};
const auto clearAll = crl::guard(box, [=] {
box->uiShow()->showBox(Box(Calls::ClearCallsBox, window));
});
state->menu->addAction(
tr::lng_settings_section_call_settings(tr::now),
showSettings,
&st::menuIconSettings);
if (state->callsDelegate.peerListFullRowsCount() > 0) {
Ui::Menu::CreateAddActionCallback(state->menu)({
.text = tr::lng_call_box_clear_all(tr::now),
.handler = clearAll,
.icon = &st::menuIconDeleteAttention,
.isAttention = true,
});
}
state->menu->popup(QCursor::pos());
return true;
});
}));
}
} // namespace Calls

View file

@ -81,4 +81,6 @@ void ClearCallsBox(
not_null<Ui::GenericBox*> box,
not_null<::Window::SessionController*> window);
void ShowCallsBox(not_null<::Window::SessionController*> window);
} // namespace Calls

View file

@ -238,14 +238,14 @@ void Instance::startOrJoinGroupCall(
}
void Instance::startOrJoinConferenceCall(StartConferenceInfo args) {
Expects(args.call || (args.migrating && args.show));
Expects(args.call || args.show);
const auto migrationInfo = (args.migrating
&& args.call
&& _currentCallPanel)
? _currentCallPanel->migrationInfo()
: ConferencePanelMigration();
if (args.call && !args.migrating) {
if (!args.migrating) {
destroyCurrentCall();
}
@ -270,17 +270,17 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) {
destroyCurrentCall(args.call.get(), args.linkSlug);
}
} else {
if (const auto was = base::take(_migratingGroupCall)) {
if (const auto was = base::take(_startingGroupCall)) {
destroyGroupCall(was.get());
}
_migratingGroupCall = std::move(call);
_startingGroupCall = std::move(call);
}
}
void Instance::migratedConferenceReady(
void Instance::startedConferenceReady(
not_null<GroupCall*> call,
StartConferenceInfo args) {
if (_migratingGroupCall.get() != call) {
if (_startingGroupCall.get() != call) {
return;
}
const auto migrationInfo = _currentCallPanel
@ -289,7 +289,7 @@ void Instance::migratedConferenceReady(
_currentGroupCallPanel = std::make_unique<Group::Panel>(
call,
migrationInfo);
_currentGroupCall = std::move(_migratingGroupCall);
_currentGroupCall = std::move(_startingGroupCall);
_currentGroupCallChanges.fire_copy(call);
const auto real = call->conferenceCall().get();
const auto link = real->conferenceInviteLink();
@ -461,8 +461,8 @@ void Instance::destroyGroupCall(not_null<GroupCall*> call) {
LOG(("Calls::Instance doesn't prevent quit any more."));
}
Core::App().quitPreventFinished();
} else if (_migratingGroupCall.get() == call) {
base::take(_migratingGroupCall);
} else if (_startingGroupCall.get() == call) {
base::take(_startingGroupCall);
}
}
@ -695,7 +695,7 @@ void Instance::handleGroupCallUpdate(
const MTPUpdate &update) {
const auto groupCall = _currentGroupCall
? _currentGroupCall.get()
: _migratingGroupCall.get();
: _startingGroupCall.get();
if (groupCall && (&groupCall->peer()->session() == session)) {
update.match([&](const MTPDupdateGroupCall &data) {
groupCall->handlePossibleCreateOrJoinResponse(data);
@ -744,7 +744,7 @@ void Instance::applyGroupCallUpdateChecked(
const MTPUpdate &update) {
const auto groupCall = _currentGroupCall
? _currentGroupCall.get()
: _migratingGroupCall.get();
: _startingGroupCall.get();
if (groupCall && (&groupCall->peer()->session() == session)) {
groupCall->handleUpdate(update);
}
@ -798,7 +798,7 @@ void Instance::destroyCurrentCall(
}
}
}
base::take(_migratingGroupCall);
base::take(_startingGroupCall);
}
bool Instance::hasVisiblePanel(Main::Session *session) const {

View file

@ -86,7 +86,7 @@ public:
not_null<PeerData*> peer,
StartGroupCallArgs args);
void startOrJoinConferenceCall(StartConferenceInfo args);
void migratedConferenceReady(
void startedConferenceReady(
not_null<GroupCall*> call,
StartConferenceInfo args);
void showStartWithRtmp(
@ -203,7 +203,7 @@ private:
std::unique_ptr<Panel> _currentCallPanel;
std::unique_ptr<GroupCall> _currentGroupCall;
std::unique_ptr<GroupCall> _migratingGroupCall;
std::unique_ptr<GroupCall> _startingGroupCall;
rpl::event_stream<GroupCall*> _currentGroupCallChanges;
std::unique_ptr<Group::Panel> _currentGroupCallPanel;

View file

@ -662,7 +662,7 @@ GroupCall::GroupCall(
if (!canManage() && real->joinMuted()) {
_muted = MuteState::ForceMuted;
}
} else if (!conference.migrating) {
} else if (!conference.migrating && !conference.show) {
_peer->session().changes().peerFlagsValue(
_peer,
Data::PeerUpdate::Flag::GroupCall
@ -682,18 +682,18 @@ GroupCall::GroupCall(
setupMediaDevices();
setupOutgoingVideo();
if (_conferenceCall || conference.migrating) {
if (_conferenceCall || conference.migrating || conference.show) {
setupConference();
}
if (conference.migrating) {
if (conference.migrating || (conference.show && !_conferenceCall)) {
if (!conference.muted) {
setMuted(MuteState::Active);
}
_migratedConferenceInfo = std::make_shared<StartConferenceInfo>(
_startConferenceInfo = std::make_shared<StartConferenceInfo>(
std::move(conference));
}
if (_id || (!_conferenceCall && _migratedConferenceInfo)) {
if (_id || (!_conferenceCall && _startConferenceInfo)) {
initialJoin();
} else {
start(join.scheduleDate, join.rtmp);
@ -703,7 +703,7 @@ GroupCall::GroupCall(
}
}
void GroupCall::processMigration(StartConferenceInfo conference) {
void GroupCall::processConferenceStart(StartConferenceInfo conference) {
if (!conference.videoCapture) {
return;
}
@ -1185,7 +1185,7 @@ bool GroupCall::rtmp() const {
}
bool GroupCall::conference() const {
return _conferenceCall || _migratedConferenceInfo;
return _conferenceCall || _startConferenceInfo;
}
bool GroupCall::listenersHidden() const {
@ -1552,7 +1552,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
};
LOG(("Call Info: Join payload received, joining with ssrc: %1."
).arg(_joinState.payload.ssrc));
if (!_conferenceCall && _migratedConferenceInfo) {
if (!_conferenceCall && _startConferenceInfo) {
startConference();
} else if (_conferenceCall
&& !_conferenceCall->blockchainMayBeEmpty()
@ -1655,7 +1655,7 @@ void GroupCall::refreshLastBlockAndJoin() {
}
void GroupCall::startConference() {
Expects(_e2e != nullptr && _migratedConferenceInfo != nullptr);
Expects(_e2e != nullptr && _startConferenceInfo != nullptr);
const auto joinBlock = _e2e->makeJoinBlock().data;
Assert(!joinBlock.isEmpty());
@ -1706,7 +1706,7 @@ void GroupCall::joinDone(
MuteState wasMuteState,
bool wasVideoStopped,
bool justCreated) {
Expects(!justCreated || _migratedConferenceInfo != nullptr);
Expects(!justCreated || _startConferenceInfo != nullptr);
_serverTimeMs = serverTimeMs;
_serverTimeMsGotAt = crl::now();
@ -1728,9 +1728,9 @@ void GroupCall::joinDone(
setupConferenceCall();
_conferenceLinkSlug = Group::ExtractConferenceSlug(
_conferenceCall->conferenceInviteLink());
Core::App().calls().migratedConferenceReady(
Core::App().calls().startedConferenceReady(
this,
*_migratedConferenceInfo);
*_startConferenceInfo);
}
applyQueuedSelfUpdates();
@ -1757,8 +1757,8 @@ void GroupCall::joinDone(
sendOutboundBlock(base::take(_pendingOutboundBlock));
}
}
if (const auto once = base::take(_migratedConferenceInfo)) {
processMigration(*once);
if (const auto once = base::take(_startConferenceInfo)) {
processConferenceStart(*once);
}
for (const auto &callback : base::take(_rejoinedCallbacks)) {
callback();

View file

@ -635,7 +635,7 @@ private:
void markTrackPaused(const VideoEndpoint &endpoint, bool paused);
void markTrackShown(const VideoEndpoint &endpoint, bool shown);
void processMigration(StartConferenceInfo conference);
void processConferenceStart(StartConferenceInfo conference);
void inviteToConference(
InviteRequest request,
Fn<not_null<InviteResult*>()> resultAddress,
@ -650,7 +650,7 @@ private:
std::shared_ptr<Data::GroupCall> _conferenceCall;
std::shared_ptr<TdE2E::Call> _e2e;
QByteArray _pendingOutboundBlock;
std::shared_ptr<StartConferenceInfo> _migratedConferenceInfo;
std::shared_ptr<StartConferenceInfo> _startConferenceInfo;
not_null<PeerData*> _peer; // Can change in legacy group migration.
rpl::event_stream<PeerData*> _peerStream;

View file

@ -436,8 +436,6 @@ void ShowConferenceCallLinkBox(
void MakeConferenceCall(ConferenceFactoryArgs &&args) {
const auto show = std::move(args.show);
const auto finished = std::move(args.finished);
const auto joining = args.joining;
const auto info = std::move(args.info);
const auto session = &show->session();
const auto fail = [=](QString error) {
show->showToast(error);
@ -464,20 +462,10 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) {
fail(u"Call link not found!"_q);
return;
}
if (joining) {
if (auto slug = ExtractConferenceSlug(link); !slug.isEmpty()) {
auto copy = info;
copy.call = call;
copy.linkSlug = std::move(slug);
Core::App().calls().startOrJoinConferenceCall(
std::move(copy));
}
} else {
Calls::Group::ShowConferenceCallLinkBox(
show,
call,
{ .initial = true });
}
Calls::Group::ShowConferenceCallLinkBox(
show,
call,
{ .initial = true });
if (const auto onstack = finished) {
finished(true);
}

View file

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_group_menu.h"
#include "calls/calls_call.h"
#include "calls/calls_instance.h"
#include "core/application.h"
#include "boxes/peer_lists_box.h"
#include "data/data_user.h"
#include "data/data_channel.h"
@ -29,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.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
@ -65,9 +68,18 @@ namespace {
return result;
}
struct ConfInviteStyles {
const style::IconButton *video = nullptr;
const style::icon *videoActive = nullptr;
const style::IconButton *audio = nullptr;
const style::icon *audioActive = nullptr;
const style::SettingsButton *inviteViaLink = nullptr;
const style::icon *inviteViaLinkIcon = nullptr;
};
class ConfInviteRow final : public PeerListRow {
public:
using PeerListRow::PeerListRow;
ConfInviteRow(not_null<UserData*> user, const ConfInviteStyles &st);
void setAlreadyIn(bool alreadyIn);
void setVideo(bool video);
@ -88,6 +100,9 @@ public:
int selectedElement) override;
private:
[[nodiscard]] const style::IconButton &buttonSt(int element) const;
const ConfInviteStyles &_st;
std::unique_ptr<Ui::RippleAnimation> _videoRipple;
std::unique_ptr<Ui::RippleAnimation> _audioRipple;
bool _alreadyIn = false;
@ -99,6 +114,7 @@ class ConfInviteController final : public ContactsBoxController {
public:
ConfInviteController(
not_null<Main::Session*> session,
ConfInviteStyles st,
base::flat_set<not_null<UserData*>> alreadyIn,
Fn<void()> shareLink);
@ -119,6 +135,7 @@ private:
[[nodiscard]] int fullCount() const;
void toggleRowSelected(not_null<PeerListRow*> row, bool video);
const ConfInviteStyles _st;
const base::flat_set<not_null<UserData*>> _alreadyIn;
const Fn<void()> _shareLink;
rpl::variable<bool> _hasSelected;
@ -127,6 +144,33 @@ private:
};
[[nodiscard]] ConfInviteStyles ConfInviteDarkStyles() {
return {
.video = &st::confcallInviteVideo,
.videoActive = &st::confcallInviteVideoActive,
.audio = &st::confcallInviteAudio,
.audioActive = &st::confcallInviteAudioActive,
.inviteViaLink = &st::groupCallInviteLink,
.inviteViaLinkIcon = &st::groupCallInviteLinkIcon,
};
}
[[nodiscard]] ConfInviteStyles ConfInviteDefaultStyles() {
return {
.video = &st::createCallVideo,
.videoActive = &st::createCallVideoActive,
.audio = &st::createCallAudio,
.audioActive = &st::createCallAudioActive,
.inviteViaLink = &st::createCallInviteLink,
.inviteViaLinkIcon = &st::createCallInviteLinkIcon,
};
}
ConfInviteRow::ConfInviteRow(not_null<UserData*> user, const ConfInviteStyles &st)
: PeerListRow(user)
, _st(st) {
}
void ConfInviteRow::setAlreadyIn(bool alreadyIn) {
_alreadyIn = alreadyIn;
setDisabledState(alreadyIn ? State::DisabledChecked : State::Active);
@ -136,6 +180,12 @@ void ConfInviteRow::setVideo(bool video) {
_video = video;
}
const style::IconButton &ConfInviteRow::buttonSt(int element) const {
return (element == 1)
? (_st.video ? *_st.video : st::createCallVideo)
: (_st.audio ? *_st.audio : st::createCallAudio);
}
int ConfInviteRow::elementsCount() const {
return _alreadyIn ? 0 : 2;
}
@ -144,13 +194,11 @@ QRect ConfInviteRow::elementGeometry(int element, int outerWidth) const {
if (_alreadyIn || (element != 1 && element != 2)) {
return QRect();
}
const auto &st = (element == 1)
? st::confcallInviteVideo
: st::confcallInviteAudio;
const auto &st = buttonSt(element);
const auto size = QSize(st.width, st.height);
const auto margins = (element == 1)
? st::confcallInviteVideoMargins
: st::confcallInviteAudioMargins;
? st::createCallVideoMargins
: st::createCallAudioMargins;
const auto right = margins.right();
const auto top = margins.top();
const auto side = (element == 1)
@ -178,9 +226,7 @@ void ConfInviteRow::elementAddRipple(
return;
}
auto &ripple = (element == 1) ? _videoRipple : _audioRipple;
const auto &st = (element == 1)
? st::confcallInviteVideo
: st::confcallInviteAudio;
const auto &st = buttonSt(element);
if (!ripple) {
auto mask = Ui::RippleAnimation::EllipseMask(QSize(
st.rippleAreaSize,
@ -211,9 +257,7 @@ void ConfInviteRow::elementsPaint(
return;
}
const auto paintElement = [&](int element) {
const auto &st = (element == 1)
? st::confcallInviteVideo
: st::confcallInviteAudio;
const auto &st = buttonSt(element);
auto &ripple = (element == 1) ? _videoRipple : _audioRipple;
const auto active = checked() && ((element == 1) ? _video : !_video);
const auto geometry = elementGeometry(element, outerWidth);
@ -230,8 +274,12 @@ void ConfInviteRow::elementsPaint(
const auto selected = (element == selectedElement);
const auto &icon = active
? (element == 1
? st::confcallInviteVideoActive
: st::confcallInviteAudioActive)
? (_st.videoActive
? *_st.videoActive
: st::createCallVideoActive)
: (_st.audioActive
? *_st.audioActive
: st::createCallAudioActive))
: (selected ? st.iconOver : st.icon);
icon.paintInCenter(p, geometry);
};
@ -241,9 +289,11 @@ void ConfInviteRow::elementsPaint(
ConfInviteController::ConfInviteController(
not_null<Main::Session*> session,
ConfInviteStyles st,
base::flat_set<not_null<UserData*>> alreadyIn,
Fn<void()> shareLink)
: ContactsBoxController(session)
, _st(st)
, _alreadyIn(std::move(alreadyIn))
, _shareLink(std::move(shareLink)) {
}
@ -272,7 +322,7 @@ std::unique_ptr<PeerListRow> ConfInviteController::createRow(
|| user->isInaccessible()) {
return nullptr;
}
auto result = std::make_unique<ConfInviteRow>(user);
auto result = std::make_unique<ConfInviteRow>(user, _st);
if (_alreadyIn.contains(user)) {
result->setAlreadyIn(true);
}
@ -283,7 +333,9 @@ std::unique_ptr<PeerListRow> ConfInviteController::createRow(
}
int ConfInviteController::fullCount() const {
return _alreadyIn.size() + delegate()->peerListSelectedRowsCount();
return _alreadyIn.size()
+ delegate()->peerListSelectedRowsCount()
+ (_alreadyIn.contains(session().user()) ? 1 : 0);
}
void ConfInviteController::rowClicked(not_null<PeerListRow*> row) {
@ -336,17 +388,21 @@ void ConfInviteController::prepareViewHook() {
object_ptr<Ui::SettingsButton>(
nullptr,
tr::lng_profile_add_via_link(),
st::groupCallInviteLink),
(_st.inviteViaLink
? *_st.inviteViaLink
: st::createCallInviteLink)),
style::margins(0, st::membersMarginTop, 0, 0));
const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
button->entity(),
st::groupCallInviteLinkIcon,
(_st.inviteViaLinkIcon
? *_st.inviteViaLinkIcon
: st::createCallInviteLinkIcon),
QPoint());
button->entity()->heightValue(
) | rpl::start_with_next([=](int height) {
icon->moveToLeft(
st::groupCallInviteLinkIconPosition.x(),
st::createCallInviteLinkIconPosition.x(),
(height - st::groupCallInviteLinkIcon.height()) / 2);
}, icon->lifetime());
@ -504,6 +560,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
};
auto controller = std::make_unique<ConfInviteController>(
&real->session(),
ConfInviteDarkStyles(),
alreadyIn,
shareLink);
const auto raw = controller.get();
@ -674,6 +731,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
auto alreadyIn = base::flat_set<not_null<UserData*>>{ user };
auto controller = std::make_unique<ConfInviteController>(
&user->session(),
ConfInviteDarkStyles(),
alreadyIn,
shareLink);
const auto raw = controller.get();
@ -699,4 +757,73 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
return Box<PeerListBox>(std::move(controller), initBox);
}
object_ptr<Ui::BoxContent> PrepareCreateCallBox(
not_null<::Window::SessionController*> window,
Fn<void()> created) {
struct State {
bool creatingLink = false;
QPointer<PeerListBox> box;
};
const auto state = std::make_shared<State>();
const auto finished = [=](bool ok) {
if (!ok) {
state->creatingLink = false;
} else {
if (const auto strong = state->box.data()) {
strong->closeBox();
}
if (const auto onstack = created) {
onstack();
}
}
};
const auto shareLink = [=] {
if (state->creatingLink) {
return;
}
state->creatingLink = true;
MakeConferenceCall({
.show = window->uiShow(),
.finished = finished,
});
};
auto controller = std::make_unique<ConfInviteController>(
&window->session(),
ConfInviteDefaultStyles(),
base::flat_set<not_null<UserData*>>(),
shareLink);
const auto raw = controller.get();
const auto initBox = [=](not_null<PeerListBox*> box) {
box->setTitle(tr::lng_confcall_create_title());
const auto create = [=] {
auto selected = raw->requests(box->collectSelectedRows());
if (selected.size() != 1) {
Core::App().calls().startOrJoinConferenceCall({
.show = window->uiShow(),
.invite = std::move(selected),
});
} else {
const auto &invite = selected.front();
Core::App().calls().startOutgoingCall(
invite.user,
invite.video);
}
finished(true);
};
box->addButton(
rpl::conditional(
raw->hasSelectedValue(),
tr::lng_group_call_confcall_add(),
tr::lng_create_group_create()),
create);
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
};
auto result = Box<PeerListBox>(std::move(controller), initBox);
state->box = result.data();
return result;
}
} // namespace Calls::Group

View file

@ -87,4 +87,8 @@ private:
Fn<void(std::vector<InviteRequest>)> inviteUsers,
Fn<void()> shareLink);
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareCreateCallBox(
not_null<::Window::SessionController*> window,
Fn<void()> created = nullptr);
} // namespace Calls::Group

View file

@ -98,145 +98,6 @@ constexpr auto kPlayStatusLimit = 2;
|| (now.month() == 1 && now.day() == 1);
}
[[nodiscard]] not_null<Ui::SettingsButton*> AddCreateCallLinkButton(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Fn<void()> done) {
const auto result = container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_confcall_create_link(),
st::inviteViaLinkButton), QMargins());
Ui::AddSkip(container);
Ui::AddDividerText(
container,
tr::lng_confcall_create_link_description(Ui::Text::WithEntities));
const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
result,
st::inviteViaLinkIcon,
QPoint());
result->heightValue(
) | rpl::start_with_next([=](int height) {
icon->moveToLeft(
st::inviteViaLinkIconPosition.x(),
(height - st::inviteViaLinkIcon.height()) / 2);
}, icon->lifetime());
const auto creating = std::make_shared<bool>();
result->setClickedCallback([=] {
if (*creating) {
return;
}
*creating = true;
const auto finished = [=](bool ok) {
if (!ok) {
*creating = false;
} else if (const auto onstack = done) {
onstack();
}
};
Calls::Group::MakeConferenceCall({
.show = controller->uiShow(),
.finished = finished,
});
});
return result;
}
void ShowCallsBox(not_null<Window::SessionController*> window) {
struct State {
State(not_null<Window::SessionController*> window)
: callsController(window)
, groupCallsController(window) {
}
Calls::BoxController callsController;
PeerListContentDelegateSimple callsDelegate;
Calls::GroupCalls::ListController groupCallsController;
PeerListContentDelegateSimple groupCallsDelegate;
base::unique_qptr<Ui::PopupMenu> menu;
};
window->show(Box([=](not_null<Ui::GenericBox*> box) {
const auto state = box->lifetime().make_state<State>(window);
const auto groupCalls = box->addRow(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box,
object_ptr<Ui::VerticalLayout>(box)),
{});
groupCalls->hide(anim::type::instant);
groupCalls->toggleOn(state->groupCallsController.shownValue());
Ui::AddSubsectionTitle(
groupCalls->entity(),
tr::lng_call_box_groupcalls_subtitle());
state->groupCallsDelegate.setContent(groupCalls->entity()->add(
object_ptr<PeerListContent>(box, &state->groupCallsController),
{}));
state->groupCallsController.setDelegate(&state->groupCallsDelegate);
Ui::AddSkip(groupCalls->entity());
Ui::AddDivider(groupCalls->entity());
Ui::AddSkip(groupCalls->entity());
const auto button = AddCreateCallLinkButton(
box->verticalLayout(),
window,
crl::guard(box, [=] { box->closeBox(); }));
button->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter);
}) | rpl::start_with_next([=] {
state->callsDelegate.peerListMouseLeftGeometry();
}, button->lifetime());
const auto content = box->addRow(
object_ptr<PeerListContent>(box, &state->callsController),
{});
state->callsDelegate.setContent(content);
state->callsController.setDelegate(&state->callsDelegate);
box->setWidth(state->callsController.contentWidth());
state->callsController.boxHeightValue(
) | rpl::start_with_next([=](int height) {
box->setMinHeight(height);
}, box->lifetime());
box->setTitle(tr::lng_call_box_title());
box->addButton(tr::lng_close(), [=] {
box->closeBox();
});
const auto menuButton = box->addTopButton(st::infoTopBarMenu);
menuButton->setClickedCallback([=] {
state->menu = base::make_unique_q<Ui::PopupMenu>(
menuButton,
st::popupMenuWithIcons);
const auto showSettings = [=] {
window->showSettings(
Settings::Calls::Id(),
Window::SectionShow(anim::type::instant));
};
const auto clearAll = crl::guard(box, [=] {
box->uiShow()->showBox(Box(Calls::ClearCallsBox, window));
});
state->menu->addAction(
tr::lng_settings_section_call_settings(tr::now),
showSettings,
&st::menuIconSettings);
if (state->callsDelegate.peerListFullRowsCount() > 0) {
Ui::Menu::CreateAddActionCallback(state->menu)({
.text = tr::lng_call_box_clear_all(tr::now),
.handler = clearAll,
.icon = &st::menuIconDeleteAttention,
.isAttention = true,
});
}
state->menu->popup(QCursor::pos());
return true;
});
}));
}
[[nodiscard]] rpl::producer<TextWithEntities> SetStatusLabel(
not_null<Main::Session*> session) {
const auto self = session->user();
@ -835,7 +696,7 @@ void MainMenu::setupMenu() {
tr::lng_menu_calls(),
{ &st::menuIconPhone }
)->setClickedCallback([=] {
ShowCallsBox(controller);
::Calls::ShowCallsBox(controller);
});
addAction(
tr::lng_saved_messages(),