2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-31 14:38:15 +00:00

Add creating of a scheduled group call.

This commit is contained in:
John Preston
2021-04-05 14:29:03 +04:00
parent e6587f2556
commit 15d17c8b0e
15 changed files with 427 additions and 206 deletions

View File

@@ -18,15 +18,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "apiwrap.h"
#include "ui/layers/generic_box.h"
#include "ui/boxes/choose_date_time.h"
#include "ui/text/text_utilities.h"
#include "boxes/peer_list_box.h"
#include "boxes/confirm_box.h"
#include "base/unixtime.h"
#include "base/timer_rpl.h"
#include "styles/style_boxes.h"
#include "styles/style_calls.h"
namespace Calls::Group {
namespace {
constexpr auto kDefaultScheduleDuration = 60 * TimeId(60);
constexpr auto kLabelRefreshInterval = 10 * crl::time(1000);
using Context = ChooseJoinAsProcess::Context;
class ListController : public PeerListController {
@@ -109,6 +115,60 @@ not_null<PeerData*> ListController::selected() const {
return _selected;
}
void ScheduleGroupCallBox(
not_null<Ui::GenericBox*> box,
const JoinInfo &info,
Fn<void(JoinInfo)> done) {
const auto send = [=](TimeId date) {
box->closeBox();
auto copy = info;
copy.scheduleDate = date;
done(std::move(copy));
};
const auto duration = box->lifetime().make_state<
rpl::variable<QString>>();
auto description = (info.peer->isBroadcast()
? tr::lng_group_call_schedule_notified_channel
: tr::lng_group_call_schedule_notified_group)(
lt_duration,
duration->value());
auto descriptor = Ui::ChooseDateTimeBox(
box,
tr::lng_group_call_schedule_title(),
tr::lng_schedule_button(),
send,
base::unixtime::now() + kDefaultScheduleDuration,
std::move(description));
using namespace rpl::mappers;
*duration = rpl::combine(
rpl::single(
rpl::empty_value()
) | rpl::then(base::timer_each(kLabelRefreshInterval)),
std::move(descriptor.values) | rpl::filter(_1 != 0),
_2
) | rpl::map([](TimeId date) {
const auto now = base::unixtime::now();
const auto duration = (date - now);
if (duration >= 24 * 60 * 60) {
return tr::lng_signin_reset_days(
tr::now,
lt_count,
duration / (24 * 60 * 60));
} else if (duration >= 60 * 60) {
return tr::lng_signin_reset_hours(
tr::now,
lt_count,
duration / (60 * 60));
}
return tr::lng_signin_reset_minutes(
tr::now,
lt_count,
std::max(duration / 60, 1));
});
}
void ChooseJoinAsBox(
not_null<Ui::GenericBox*> box,
Context context,
@@ -124,12 +184,13 @@ void ChooseJoinAsBox(
}
Unexpected("Context in ChooseJoinAsBox.");
}());
const auto &labelSt = (context == Context::Switch)
? st::groupCallJoinAsLabel
: st::confirmPhoneAboutLabel;
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_group_call_join_as_about(),
(context == Context::Switch
? st::groupCallJoinAsLabel
: st::confirmPhoneAboutLabel)));
labelSt));
auto &lifetime = box->lifetime();
const auto delegate = lifetime.make_state<
@@ -155,6 +216,27 @@ void ChooseJoinAsBox(
auto next = (context == Context::Switch)
? tr::lng_settings_save()
: tr::lng_continue();
if (context == Context::Create) {
const auto makeLink = [](const QString &text) {
return Ui::Text::Link(text);
};
const auto label = box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_group_call_or_schedule(
lt_link,
tr::lng_group_call_schedule(makeLink),
Ui::Text::WithEntities),
labelSt));
label->setClickHandlerFilter([=](const auto&...) {
auto withJoinAs = info;
withJoinAs.joinAs = controller->selected();
box->getDelegate()->show(Box(
ScheduleGroupCallBox,
withJoinAs,
done));
return false;
});
}
box->addButton(std::move(next), [=] {
auto copy = info;
copy.joinAs = controller->selected();

View File

@@ -184,6 +184,7 @@ GroupCall::GroupCall(
, _joinAs(info.joinAs)
, _possibleJoinAs(std::move(info.possibleJoinAs))
, _joinHash(info.joinHash)
, _scheduleDate(info.scheduleDate)
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
, _checkJoinedTimer([=] { checkJoined(); })
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
@@ -218,14 +219,14 @@ GroupCall::GroupCall(
const auto id = inputCall.c_inputGroupCall().vid().v;
if (id) {
if (const auto call = _peer->groupCall(); call && call->id() == id) {
_scheduleDate = call->scheduleDate();
if (!_peer->canManageGroupCall() && call->joinMuted()) {
_muted = MuteState::ForceMuted;
}
}
_state = State::Joining;
join(inputCall);
} else {
start();
start(info.scheduleDate);
}
_mediaDevices->audioInputId(
@@ -326,13 +327,14 @@ bool GroupCall::showChooseJoinAs() const {
&& !_possibleJoinAs.front()->isSelf());
}
void GroupCall::start() {
void GroupCall::start(TimeId scheduleDate) {
using Flag = MTPphone_CreateGroupCall::Flag;
_createRequestId = _api.request(MTPphone_CreateGroupCall(
MTP_flags(0),
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag(0)),
_peer->input,
MTP_int(openssl::RandomValue<int32>()),
MTPstring(), // title
MTPint() // schedule_date
MTP_int(scheduleDate)
)).done([=](const MTPUpdates &result) {
_acceptFields = true;
_peer->session().api().applyUpdates(result);
@@ -350,6 +352,15 @@ void GroupCall::start() {
}
void GroupCall::join(const MTPInputGroupCall &inputCall) {
inputCall.match([&](const MTPDinputGroupCall &data) {
_id = data.vid().v;
_accessHash = data.vaccess_hash().v;
});
if (_scheduleDate) {
setState(State::Waiting);
return;
}
setState(State::Joining);
if (const auto chat = _peer->asChat()) {
chat->setGroupCall(inputCall);
@@ -358,12 +369,7 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
} else {
Unexpected("Peer type in GroupCall::join.");
}
inputCall.match([&](const MTPDinputGroupCall &data) {
_id = data.vid().v;
_accessHash = data.vaccess_hash().v;
rejoin();
});
rejoin();
using Update = Data::GroupCall::ParticipantUpdate;
_peer->groupCall()->participantUpdated(
@@ -646,8 +652,10 @@ void GroupCall::rejoinAs(Group::JoinInfo info) {
.wasJoinAs = _joinAs,
.nowJoinAs = info.joinAs,
};
setState(State::Joining);
rejoin(info.joinAs);
if (!_scheduleDate) {
setState(State::Joining);
rejoin(info.joinAs);
}
_rejoinEvents.fire_copy(event);
}
@@ -734,6 +742,9 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
void GroupCall::handlePossibleCreateOrJoinResponse(
const MTPDgroupCall &data) {
if (const auto date = data.vschedule_date()) {
_scheduleDate = date->v;
}
if (_acceptFields) {
if (!_instance && !_id) {
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));

View File

@@ -109,8 +109,11 @@ public:
return _joinAs;
}
[[nodiscard]] bool showChooseJoinAs() const;
[[nodiscard]] TimeId scheduleDate() const {
return _scheduleDate;
}
void start();
void start(TimeId scheduleDate);
void hangup();
void discard();
void rejoinAs(Group::JoinInfo info);
@@ -138,6 +141,7 @@ public:
enum State {
Creating,
Waiting,
Joining,
Connecting,
Joined,
@@ -310,6 +314,7 @@ private:
uint64 _id = 0;
uint64 _accessHash = 0;
uint32 _mySsrc = 0;
TimeId _scheduleDate = 0;
base::flat_set<uint32> _mySsrcs;
mtpRequestId _createRequestId = 0;
mtpRequestId _updateMuteRequestId = 0;

View File

@@ -44,6 +44,7 @@ struct JoinInfo {
not_null<PeerData*> joinAs;
std::vector<not_null<PeerData*>> possibleJoinAs;
QString joinHash;
TimeId scheduleDate = 0;
};
} // namespace Calls::Group

View File

@@ -259,7 +259,7 @@ Panel::Panel(not_null<GroupCall*> call)
_window->body(),
st::groupCallTitle))
#endif // !Q_OS_MAC
, _members(widget(), call)
, _scheduleDate(call->scheduleDate())
, _settings(widget(), st::groupCallSettings)
, _mute(std::make_unique<Ui::CallMuteButton>(
widget(),
@@ -286,30 +286,7 @@ Panel::Panel(not_null<GroupCall*> call)
showAndActivate();
setupJoinAsChangedToasts();
setupTitleChangedToasts();
call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (isActive()) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_can_speak_here(tr::now) },
});
} else {
const auto real = _peer->groupCall();
const auto name = (real
&& (real->id() == call->id())
&& !real->title().isEmpty())
? real->title()
: _peer->name;
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
});
}
}, widget()->lifetime());
setupAllowedToSpeakToasts();
}
Panel::~Panel() {
@@ -326,7 +303,7 @@ void Panel::setupRealCallViewers(not_null<GroupCall*> call) {
) | rpl::map([=] {
return peer->groupCall();
}) | rpl::filter([=](Data::GroupCall *real) {
return _call && real && (real->id() == _call->id());
return real && (real->id() == _call->id());
}) | rpl::take(
1
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
@@ -393,11 +370,9 @@ void Panel::initWindow() {
} else if (e->type() == QEvent::KeyPress
|| e->type() == QEvent::KeyRelease) {
if (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Space) {
if (_call) {
_call->pushToTalk(
e->type() == QEvent::KeyPress,
kSpacePushToTalkDelay);
}
_call->pushToTalk(
e->type() == QEvent::KeyPress,
kSpacePushToTalkDelay);
}
}
return base::EventFilterResult::Continue;
@@ -439,9 +414,7 @@ void Panel::initWidget() {
}
void Panel::endCall() {
if (!_call) {
return;
} else if (!_call->peer()->canManageGroupCall()) {
if (!_call->peer()->canManageGroupCall()) {
_call->hangup();
return;
}
@@ -455,7 +428,7 @@ void Panel::endCall() {
void Panel::initControls() {
_mute->clicks(
) | rpl::filter([=](Qt::MouseButton button) {
return (button == Qt::LeftButton) && (_call != nullptr);
return (button == Qt::LeftButton);
}) | rpl::start_with_next([=] {
const auto oldState = _call->muted();
const auto newState = (oldState == MuteState::ForceMuted)
@@ -470,32 +443,17 @@ void Panel::initControls() {
_hangup->setClickedCallback([=] { endCall(); });
_settings->setClickedCallback([=] {
if (_call) {
_layerBg->showBox(Box(SettingsBox, _call));
}
_layerBg->showBox(Box(SettingsBox, _call));
});
_settings->setText(tr::lng_group_call_settings());
_hangup->setText(tr::lng_group_call_leave());
_members->desiredHeightValue(
) | rpl::start_with_next([=] {
updateControlsGeometry();
}, _members->lifetime());
initWithCall(_call);
}
void Panel::initWithCall(GroupCall *call) {
_callLifetime.destroy();
_call = call;
if (!_call) {
return;
if (!_call->scheduleDate()) {
setupMembers();
}
_peer = _call->peer();
call->stateValue(
_call->stateValue(
) | rpl::filter([](State state) {
return (state == State::HangingUp)
|| (state == State::Ended)
@@ -505,59 +463,13 @@ void Panel::initWithCall(GroupCall *call) {
closeBeforeDestroy();
}, _callLifetime);
call->levelUpdates(
_call->levelUpdates(
) | rpl::filter([=](const LevelUpdate &update) {
return update.me;
}) | rpl::start_with_next([=](const LevelUpdate &update) {
_mute->setLevel(update.value);
}, _callLifetime);
_members->toggleMuteRequests(
) | rpl::start_with_next([=](MuteRequest request) {
if (_call) {
_call->toggleMute(request);
}
}, _callLifetime);
_members->changeVolumeRequests(
) | rpl::start_with_next([=](VolumeRequest request) {
if (_call) {
_call->changeVolume(request);
}
}, _callLifetime);
_members->kickParticipantRequests(
) | rpl::start_with_next([=](not_null<PeerData*> participantPeer) {
kickParticipant(participantPeer);
}, _callLifetime);
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
_peer,
showBox,
showToast);
auto shareLink = std::move(shareLinkCallback);
_members->lifetime().add(std::move(shareLinkLifetime));
_members->addMembersRequests(
) | rpl::start_with_next([=] {
if (_call) {
if (_peer->isBroadcast() && _peer->asChannel()->hasUsername()) {
shareLink();
} else {
addMembers();
}
}
}, _callLifetime);
using namespace rpl::mappers;
rpl::combine(
_call->mutedValue() | MapPushToTalkToActive(),
@@ -600,6 +512,61 @@ void Panel::initWithCall(GroupCall *call) {
}, _callLifetime);
}
void Panel::setupMembers() {
Expects(!_members);
_members.create(widget(), _call);
_members->desiredHeightValue(
) | rpl::start_with_next([=] {
updateMembersGeometry();
}, _members->lifetime());
_members->toggleMuteRequests(
) | rpl::start_with_next([=](MuteRequest request) {
if (_call) {
_call->toggleMute(request);
}
}, _callLifetime);
_members->changeVolumeRequests(
) | rpl::start_with_next([=](VolumeRequest request) {
if (_call) {
_call->changeVolume(request);
}
}, _callLifetime);
_members->kickParticipantRequests(
) | rpl::start_with_next([=](not_null<PeerData*> participantPeer) {
kickParticipant(participantPeer);
}, _callLifetime);
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
};
const auto showToast = [=](QString text) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { text },
});
};
auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
_peer,
showBox,
showToast);
auto shareLink = std::move(shareLinkCallback);
_members->lifetime().add(std::move(shareLinkLifetime));
_members->addMembersRequests(
) | rpl::start_with_next([=] {
if (_peer->isBroadcast() && _peer->asChannel()->hasUsername()) {
shareLink();
} else {
addMembers();
}
}, _callLifetime);
}
void Panel::setupJoinAsChangedToasts() {
_call->rejoinEvents(
) | rpl::filter([](RejoinEvent event) {
@@ -623,7 +590,8 @@ void Panel::setupJoinAsChangedToasts() {
void Panel::setupTitleChangedToasts() {
_call->titleChanged(
) | rpl::filter([=] {
return _peer->groupCall() && _peer->groupCall()->id() == _call->id();
const auto real = _peer->groupCall();
return real && (real->id() == _call->id());
}) | rpl::map([=] {
return _peer->groupCall()->title().isEmpty()
? _peer->name
@@ -640,8 +608,44 @@ void Panel::setupTitleChangedToasts() {
}, widget()->lifetime());
}
void Panel::setupAllowedToSpeakToasts() {
_call->allowedToSpeakNotifications(
) | rpl::start_with_next([=] {
if (isActive()) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { tr::lng_group_call_can_speak_here(tr::now) },
});
} else {
const auto real = _peer->groupCall();
const auto name = (real
&& (real->id() == _call->id())
&& !real->title().isEmpty())
? real->title()
: _peer->name;
Ui::ShowMultilineToast({
.text = tr::lng_group_call_can_speak(
tr::now,
lt_chat,
Ui::Text::Bold(name),
Ui::Text::WithEntities),
});
}
}, widget()->lifetime());
}
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
if (!_members) {
real->scheduleDateValue(
) | rpl::filter([=](TimeId scheduleDate) {
return !scheduleDate;
}) | rpl::take(1) | rpl::start_with_next([=] {
setupMembers();
}, _callLifetime);
}
_titleText = real->titleValue();
_scheduleDate = real->scheduleDateValue();
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
@@ -702,7 +706,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
.parentOverride = widget(),
.text = (recorded
? tr::lng_group_call_recording_started
: (_call && _call->recordingStoppedByMe())
: _call->recordingStoppedByMe()
? tr::lng_group_call_recording_saved
: tr::lng_group_call_recording_stopped)(
tr::now,
@@ -751,9 +755,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
void Panel::chooseJoinAs() {
const auto context = ChooseJoinAsProcess::Context::Switch;
const auto callback = [=](JoinInfo info) {
if (_call) {
_call->rejoinAs(info);
}
_call->rejoinAs(info);
};
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
@@ -774,7 +776,7 @@ void Panel::chooseJoinAs() {
}
void Panel::showMainMenu() {
if (_menu || !_call) {
if (_menu) {
return;
}
_menu.create(widget(), st::groupCallDropdownMenu);
@@ -822,7 +824,7 @@ void Panel::showMainMenu() {
void Panel::addMembers() {
const auto real = _peer->groupCall();
if (!_call || !real || real->id() != _call->id()) {
if (!real || real->id() != _call->id()) {
return;
}
auto alreadyIn = _peer->owner().invitedToCallUsers(real->id());
@@ -848,7 +850,7 @@ void Panel::addMembers() {
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
const auto weak = base::make_weak(_call);
const auto weak = base::make_weak(_call.get());
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
const auto call = weak.get();
if (!call) {
@@ -1031,7 +1033,7 @@ void Panel::showControls() {
void Panel::closeBeforeDestroy() {
_window->close();
initWithCall(nullptr);
_callLifetime.destroy();
}
void Panel::initGeometry() {
@@ -1066,28 +1068,8 @@ void Panel::updateControlsGeometry() {
if (widget()->size().isEmpty()) {
return;
}
const auto desiredHeight = _members->desiredHeight();
const auto membersWidthAvailable = widget()->width()
- st::groupCallMembersMargin.left()
- st::groupCallMembersMargin.right();
const auto membersWidthMin = st::groupCallWidth
- st::groupCallMembersMargin.left()
- st::groupCallMembersMargin.right();
const auto membersWidth = std::clamp(
membersWidthAvailable,
membersWidthMin,
st::groupCallMembersWidthMax);
const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip;
const auto buttonsTop = widget()->height() - st::groupCallButtonBottomSkip;
const auto membersTop = st::groupCallMembersTop;
const auto availableHeight = muteTop
- membersTop
- st::groupCallMembersMargin.bottom();
_members->setGeometry(
(widget()->width() - membersWidth) / 2,
membersTop,
membersWidth,
std::min(desiredHeight, availableHeight));
const auto muteSize = _mute->innerSize().width();
const auto fullWidth = muteSize
+ 2 * _settings->width()
@@ -1095,6 +1077,8 @@ void Panel::updateControlsGeometry() {
_mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop });
_settings->moveToLeft((widget()->width() - fullWidth) / 2, buttonsTop);
_hangup->moveToRight((widget()->width() - fullWidth) / 2, buttonsTop);
updateMembersGeometry();
refreshTitle();
#ifdef Q_OS_MAC
@@ -1120,6 +1104,33 @@ void Panel::updateControlsGeometry() {
}
}
void Panel::updateMembersGeometry() {
if (!_members) {
return;
}
const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip;
const auto membersTop = st::groupCallMembersTop;
const auto availableHeight = muteTop
- membersTop
- st::groupCallMembersMargin.bottom();
const auto desiredHeight = _members->desiredHeight();
const auto membersWidthAvailable = widget()->width()
- st::groupCallMembersMargin.left()
- st::groupCallMembersMargin.right();
const auto membersWidthMin = st::groupCallWidth
- st::groupCallMembersMargin.left()
- st::groupCallMembersMargin.right();
const auto membersWidth = std::clamp(
membersWidthAvailable,
membersWidthMin,
st::groupCallMembersWidthMax);
_members->setGeometry(
(widget()->width() - membersWidth) / 2,
membersTop,
membersWidth,
std::min(desiredHeight, availableHeight));
}
void Panel::refreshTitle() {
if (!_title) {
auto text = rpl::combine(
@@ -1143,11 +1154,16 @@ void Panel::refreshTitle() {
if (!_subtitle) {
_subtitle.create(
widget(),
tr::lng_group_call_members(
lt_count_decimal,
_members->fullCountValue() | rpl::map([](int value) {
return (value > 0) ? float64(value) : 1.;
})),
_scheduleDate.value(
) | rpl::map([=](TimeId scheduleDate) {
return scheduleDate
? tr::lng_group_call_scheduled_status()
: tr::lng_group_call_members(
lt_count_decimal,
_members->fullCountValue() | rpl::map([](int value) {
return (value > 0) ? float64(value) : 1.;
}));
}) | rpl::flatten_latest(),
st::groupCallSubtitleLabel);
_subtitle->show();
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);

View File

@@ -73,15 +73,17 @@ private:
void initWindow();
void initWidget();
void initControls();
void initWithCall(GroupCall *call);
void initLayout();
void initGeometry();
void setupMembers();
void setupJoinAsChangedToasts();
void setupTitleChangedToasts();
void setupAllowedToSpeakToasts();
bool handleClose();
void updateControlsGeometry();
void updateMembersGeometry();
void showControls();
void endCall();
@@ -100,7 +102,7 @@ private:
void migrate(not_null<ChannelData*> channel);
void subscribeToPeerChanges();
GroupCall *_call = nullptr;
const not_null<GroupCall*> _call;
not_null<PeerData*> _peer;
const std::unique_ptr<Ui::Window> _window;
@@ -118,8 +120,9 @@ private:
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
object_ptr<Members> _members;
object_ptr<Members> _members = { nullptr };
rpl::variable<QString> _titleText;
rpl::variable<TimeId> _scheduleDate;
ChooseJoinAsProcess _joinAsProcess;
object_ptr<Ui::CallButton> _settings;