mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-06 15:13:57 +02:00
Add creating of a scheduled group call.
This commit is contained in:
parent
e6587f2556
commit
15d17c8b0e
15 changed files with 427 additions and 206 deletions
|
@ -2056,6 +2056,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_group_call_join_as_header" = "Join Voice Chat as...";
|
"lng_group_call_join_as_header" = "Join Voice Chat as...";
|
||||||
"lng_group_call_display_as_header" = "Display me as...";
|
"lng_group_call_display_as_header" = "Display me as...";
|
||||||
"lng_group_call_join_as_about" = "Choose whether you want to be displayed as your personal account or as your channel.";
|
"lng_group_call_join_as_about" = "Choose whether you want to be displayed as your personal account or as your channel.";
|
||||||
|
"lng_group_call_or_schedule" = "Or you can {link}.";
|
||||||
|
"lng_group_call_schedule" = "Schedule Voice Chat";
|
||||||
|
"lng_group_call_schedule_title" = "Schedule Voice Chat";
|
||||||
|
"lng_group_call_schedule_notified_group" = "The members of the group will be notified that the voice chat will start in {duration}.";
|
||||||
|
"lng_group_call_schedule_notified_channel" = "The subscribers of the channel will be notified that the voice chat will start in {duration}.";
|
||||||
|
"lng_group_call_scheduled_status" = "Scheduled";
|
||||||
|
"lng_group_call_scheduled_title" = "Scheduled Voice Chat";
|
||||||
|
"lng_group_call_starts_short" = "Starts {when}";
|
||||||
|
"lng_group_call_starts" = "Voice Chat starts {when}";
|
||||||
|
"lng_group_call_starts_today" = "today at {time}";
|
||||||
|
"lng_group_call_starts_tomorrow" = "tomorrow at {time}";
|
||||||
|
"lng_group_call_starts_date" = "{date} at {time}";
|
||||||
|
"lng_group_call_starts_in" = "Starts in";
|
||||||
|
"lng_group_call_set_reminder" = "Set Reminder";
|
||||||
|
"lng_group_call_cancel_reminder" = "Cancel Reminder";
|
||||||
"lng_group_call_join_as_personal" = "personal account";
|
"lng_group_call_join_as_personal" = "personal account";
|
||||||
"lng_group_call_edit_title" = "Edit voice chat title";
|
"lng_group_call_edit_title" = "Edit voice chat title";
|
||||||
"lng_group_call_switch_done" = "Members of this voice chat will now see you as **{user}**";
|
"lng_group_call_switch_done" = "Members of this voice chat will now see you as **{user}**";
|
||||||
|
|
|
@ -18,15 +18,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
|
#include "ui/boxes/choose_date_time.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "boxes/peer_list_box.h"
|
#include "boxes/peer_list_box.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
|
#include "base/timer_rpl.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
|
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kDefaultScheduleDuration = 60 * TimeId(60);
|
||||||
|
constexpr auto kLabelRefreshInterval = 10 * crl::time(1000);
|
||||||
|
|
||||||
using Context = ChooseJoinAsProcess::Context;
|
using Context = ChooseJoinAsProcess::Context;
|
||||||
|
|
||||||
class ListController : public PeerListController {
|
class ListController : public PeerListController {
|
||||||
|
@ -109,6 +115,60 @@ not_null<PeerData*> ListController::selected() const {
|
||||||
return _selected;
|
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(
|
void ChooseJoinAsBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
Context context,
|
Context context,
|
||||||
|
@ -124,12 +184,13 @@ void ChooseJoinAsBox(
|
||||||
}
|
}
|
||||||
Unexpected("Context in ChooseJoinAsBox.");
|
Unexpected("Context in ChooseJoinAsBox.");
|
||||||
}());
|
}());
|
||||||
|
const auto &labelSt = (context == Context::Switch)
|
||||||
|
? st::groupCallJoinAsLabel
|
||||||
|
: st::confirmPhoneAboutLabel;
|
||||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||||
box,
|
box,
|
||||||
tr::lng_group_call_join_as_about(),
|
tr::lng_group_call_join_as_about(),
|
||||||
(context == Context::Switch
|
labelSt));
|
||||||
? st::groupCallJoinAsLabel
|
|
||||||
: st::confirmPhoneAboutLabel)));
|
|
||||||
|
|
||||||
auto &lifetime = box->lifetime();
|
auto &lifetime = box->lifetime();
|
||||||
const auto delegate = lifetime.make_state<
|
const auto delegate = lifetime.make_state<
|
||||||
|
@ -155,6 +216,27 @@ void ChooseJoinAsBox(
|
||||||
auto next = (context == Context::Switch)
|
auto next = (context == Context::Switch)
|
||||||
? tr::lng_settings_save()
|
? tr::lng_settings_save()
|
||||||
: tr::lng_continue();
|
: 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), [=] {
|
box->addButton(std::move(next), [=] {
|
||||||
auto copy = info;
|
auto copy = info;
|
||||||
copy.joinAs = controller->selected();
|
copy.joinAs = controller->selected();
|
||||||
|
|
|
@ -184,6 +184,7 @@ GroupCall::GroupCall(
|
||||||
, _joinAs(info.joinAs)
|
, _joinAs(info.joinAs)
|
||||||
, _possibleJoinAs(std::move(info.possibleJoinAs))
|
, _possibleJoinAs(std::move(info.possibleJoinAs))
|
||||||
, _joinHash(info.joinHash)
|
, _joinHash(info.joinHash)
|
||||||
|
, _scheduleDate(info.scheduleDate)
|
||||||
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
|
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
|
||||||
, _checkJoinedTimer([=] { checkJoined(); })
|
, _checkJoinedTimer([=] { checkJoined(); })
|
||||||
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
|
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
|
||||||
|
@ -218,14 +219,14 @@ GroupCall::GroupCall(
|
||||||
const auto id = inputCall.c_inputGroupCall().vid().v;
|
const auto id = inputCall.c_inputGroupCall().vid().v;
|
||||||
if (id) {
|
if (id) {
|
||||||
if (const auto call = _peer->groupCall(); call && call->id() == id) {
|
if (const auto call = _peer->groupCall(); call && call->id() == id) {
|
||||||
|
_scheduleDate = call->scheduleDate();
|
||||||
if (!_peer->canManageGroupCall() && call->joinMuted()) {
|
if (!_peer->canManageGroupCall() && call->joinMuted()) {
|
||||||
_muted = MuteState::ForceMuted;
|
_muted = MuteState::ForceMuted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_state = State::Joining;
|
|
||||||
join(inputCall);
|
join(inputCall);
|
||||||
} else {
|
} else {
|
||||||
start();
|
start(info.scheduleDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_mediaDevices->audioInputId(
|
_mediaDevices->audioInputId(
|
||||||
|
@ -326,13 +327,14 @@ bool GroupCall::showChooseJoinAs() const {
|
||||||
&& !_possibleJoinAs.front()->isSelf());
|
&& !_possibleJoinAs.front()->isSelf());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::start() {
|
void GroupCall::start(TimeId scheduleDate) {
|
||||||
|
using Flag = MTPphone_CreateGroupCall::Flag;
|
||||||
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
_createRequestId = _api.request(MTPphone_CreateGroupCall(
|
||||||
MTP_flags(0),
|
MTP_flags(scheduleDate ? Flag::f_schedule_date : Flag(0)),
|
||||||
_peer->input,
|
_peer->input,
|
||||||
MTP_int(openssl::RandomValue<int32>()),
|
MTP_int(openssl::RandomValue<int32>()),
|
||||||
MTPstring(), // title
|
MTPstring(), // title
|
||||||
MTPint() // schedule_date
|
MTP_int(scheduleDate)
|
||||||
)).done([=](const MTPUpdates &result) {
|
)).done([=](const MTPUpdates &result) {
|
||||||
_acceptFields = true;
|
_acceptFields = true;
|
||||||
_peer->session().api().applyUpdates(result);
|
_peer->session().api().applyUpdates(result);
|
||||||
|
@ -350,6 +352,15 @@ void GroupCall::start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
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);
|
setState(State::Joining);
|
||||||
if (const auto chat = _peer->asChat()) {
|
if (const auto chat = _peer->asChat()) {
|
||||||
chat->setGroupCall(inputCall);
|
chat->setGroupCall(inputCall);
|
||||||
|
@ -358,12 +369,7 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
|
||||||
} else {
|
} else {
|
||||||
Unexpected("Peer type in GroupCall::join.");
|
Unexpected("Peer type in GroupCall::join.");
|
||||||
}
|
}
|
||||||
|
rejoin();
|
||||||
inputCall.match([&](const MTPDinputGroupCall &data) {
|
|
||||||
_id = data.vid().v;
|
|
||||||
_accessHash = data.vaccess_hash().v;
|
|
||||||
rejoin();
|
|
||||||
});
|
|
||||||
|
|
||||||
using Update = Data::GroupCall::ParticipantUpdate;
|
using Update = Data::GroupCall::ParticipantUpdate;
|
||||||
_peer->groupCall()->participantUpdated(
|
_peer->groupCall()->participantUpdated(
|
||||||
|
@ -646,8 +652,10 @@ void GroupCall::rejoinAs(Group::JoinInfo info) {
|
||||||
.wasJoinAs = _joinAs,
|
.wasJoinAs = _joinAs,
|
||||||
.nowJoinAs = info.joinAs,
|
.nowJoinAs = info.joinAs,
|
||||||
};
|
};
|
||||||
setState(State::Joining);
|
if (!_scheduleDate) {
|
||||||
rejoin(info.joinAs);
|
setState(State::Joining);
|
||||||
|
rejoin(info.joinAs);
|
||||||
|
}
|
||||||
_rejoinEvents.fire_copy(event);
|
_rejoinEvents.fire_copy(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -734,6 +742,9 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||||
|
|
||||||
void GroupCall::handlePossibleCreateOrJoinResponse(
|
void GroupCall::handlePossibleCreateOrJoinResponse(
|
||||||
const MTPDgroupCall &data) {
|
const MTPDgroupCall &data) {
|
||||||
|
if (const auto date = data.vschedule_date()) {
|
||||||
|
_scheduleDate = date->v;
|
||||||
|
}
|
||||||
if (_acceptFields) {
|
if (_acceptFields) {
|
||||||
if (!_instance && !_id) {
|
if (!_instance && !_id) {
|
||||||
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
|
join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
|
||||||
|
|
|
@ -109,8 +109,11 @@ public:
|
||||||
return _joinAs;
|
return _joinAs;
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool showChooseJoinAs() const;
|
[[nodiscard]] bool showChooseJoinAs() const;
|
||||||
|
[[nodiscard]] TimeId scheduleDate() const {
|
||||||
|
return _scheduleDate;
|
||||||
|
}
|
||||||
|
|
||||||
void start();
|
void start(TimeId scheduleDate);
|
||||||
void hangup();
|
void hangup();
|
||||||
void discard();
|
void discard();
|
||||||
void rejoinAs(Group::JoinInfo info);
|
void rejoinAs(Group::JoinInfo info);
|
||||||
|
@ -138,6 +141,7 @@ public:
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
Creating,
|
Creating,
|
||||||
|
Waiting,
|
||||||
Joining,
|
Joining,
|
||||||
Connecting,
|
Connecting,
|
||||||
Joined,
|
Joined,
|
||||||
|
@ -310,6 +314,7 @@ private:
|
||||||
uint64 _id = 0;
|
uint64 _id = 0;
|
||||||
uint64 _accessHash = 0;
|
uint64 _accessHash = 0;
|
||||||
uint32 _mySsrc = 0;
|
uint32 _mySsrc = 0;
|
||||||
|
TimeId _scheduleDate = 0;
|
||||||
base::flat_set<uint32> _mySsrcs;
|
base::flat_set<uint32> _mySsrcs;
|
||||||
mtpRequestId _createRequestId = 0;
|
mtpRequestId _createRequestId = 0;
|
||||||
mtpRequestId _updateMuteRequestId = 0;
|
mtpRequestId _updateMuteRequestId = 0;
|
||||||
|
|
|
@ -44,6 +44,7 @@ struct JoinInfo {
|
||||||
not_null<PeerData*> joinAs;
|
not_null<PeerData*> joinAs;
|
||||||
std::vector<not_null<PeerData*>> possibleJoinAs;
|
std::vector<not_null<PeerData*>> possibleJoinAs;
|
||||||
QString joinHash;
|
QString joinHash;
|
||||||
|
TimeId scheduleDate = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Calls::Group
|
} // namespace Calls::Group
|
||||||
|
|
|
@ -259,7 +259,7 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||||
_window->body(),
|
_window->body(),
|
||||||
st::groupCallTitle))
|
st::groupCallTitle))
|
||||||
#endif // !Q_OS_MAC
|
#endif // !Q_OS_MAC
|
||||||
, _members(widget(), call)
|
, _scheduleDate(call->scheduleDate())
|
||||||
, _settings(widget(), st::groupCallSettings)
|
, _settings(widget(), st::groupCallSettings)
|
||||||
, _mute(std::make_unique<Ui::CallMuteButton>(
|
, _mute(std::make_unique<Ui::CallMuteButton>(
|
||||||
widget(),
|
widget(),
|
||||||
|
@ -286,30 +286,7 @@ Panel::Panel(not_null<GroupCall*> call)
|
||||||
showAndActivate();
|
showAndActivate();
|
||||||
setupJoinAsChangedToasts();
|
setupJoinAsChangedToasts();
|
||||||
setupTitleChangedToasts();
|
setupTitleChangedToasts();
|
||||||
|
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Panel::~Panel() {
|
Panel::~Panel() {
|
||||||
|
@ -326,7 +303,7 @@ void Panel::setupRealCallViewers(not_null<GroupCall*> call) {
|
||||||
) | rpl::map([=] {
|
) | rpl::map([=] {
|
||||||
return peer->groupCall();
|
return peer->groupCall();
|
||||||
}) | rpl::filter([=](Data::GroupCall *real) {
|
}) | rpl::filter([=](Data::GroupCall *real) {
|
||||||
return _call && real && (real->id() == _call->id());
|
return real && (real->id() == _call->id());
|
||||||
}) | rpl::take(
|
}) | rpl::take(
|
||||||
1
|
1
|
||||||
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
) | rpl::start_with_next([=](not_null<Data::GroupCall*> real) {
|
||||||
|
@ -393,11 +370,9 @@ void Panel::initWindow() {
|
||||||
} else if (e->type() == QEvent::KeyPress
|
} else if (e->type() == QEvent::KeyPress
|
||||||
|| e->type() == QEvent::KeyRelease) {
|
|| e->type() == QEvent::KeyRelease) {
|
||||||
if (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Space) {
|
if (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Space) {
|
||||||
if (_call) {
|
_call->pushToTalk(
|
||||||
_call->pushToTalk(
|
e->type() == QEvent::KeyPress,
|
||||||
e->type() == QEvent::KeyPress,
|
kSpacePushToTalkDelay);
|
||||||
kSpacePushToTalkDelay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return base::EventFilterResult::Continue;
|
return base::EventFilterResult::Continue;
|
||||||
|
@ -439,9 +414,7 @@ void Panel::initWidget() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::endCall() {
|
void Panel::endCall() {
|
||||||
if (!_call) {
|
if (!_call->peer()->canManageGroupCall()) {
|
||||||
return;
|
|
||||||
} else if (!_call->peer()->canManageGroupCall()) {
|
|
||||||
_call->hangup();
|
_call->hangup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -455,7 +428,7 @@ void Panel::endCall() {
|
||||||
void Panel::initControls() {
|
void Panel::initControls() {
|
||||||
_mute->clicks(
|
_mute->clicks(
|
||||||
) | rpl::filter([=](Qt::MouseButton button) {
|
) | rpl::filter([=](Qt::MouseButton button) {
|
||||||
return (button == Qt::LeftButton) && (_call != nullptr);
|
return (button == Qt::LeftButton);
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
const auto oldState = _call->muted();
|
const auto oldState = _call->muted();
|
||||||
const auto newState = (oldState == MuteState::ForceMuted)
|
const auto newState = (oldState == MuteState::ForceMuted)
|
||||||
|
@ -470,32 +443,17 @@ void Panel::initControls() {
|
||||||
|
|
||||||
_hangup->setClickedCallback([=] { endCall(); });
|
_hangup->setClickedCallback([=] { endCall(); });
|
||||||
_settings->setClickedCallback([=] {
|
_settings->setClickedCallback([=] {
|
||||||
if (_call) {
|
_layerBg->showBox(Box(SettingsBox, _call));
|
||||||
_layerBg->showBox(Box(SettingsBox, _call));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_settings->setText(tr::lng_group_call_settings());
|
_settings->setText(tr::lng_group_call_settings());
|
||||||
_hangup->setText(tr::lng_group_call_leave());
|
_hangup->setText(tr::lng_group_call_leave());
|
||||||
|
|
||||||
_members->desiredHeightValue(
|
if (!_call->scheduleDate()) {
|
||||||
) | rpl::start_with_next([=] {
|
setupMembers();
|
||||||
updateControlsGeometry();
|
|
||||||
}, _members->lifetime());
|
|
||||||
|
|
||||||
initWithCall(_call);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Panel::initWithCall(GroupCall *call) {
|
|
||||||
_callLifetime.destroy();
|
|
||||||
_call = call;
|
|
||||||
if (!_call) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_peer = _call->peer();
|
_call->stateValue(
|
||||||
|
|
||||||
call->stateValue(
|
|
||||||
) | rpl::filter([](State state) {
|
) | rpl::filter([](State state) {
|
||||||
return (state == State::HangingUp)
|
return (state == State::HangingUp)
|
||||||
|| (state == State::Ended)
|
|| (state == State::Ended)
|
||||||
|
@ -505,59 +463,13 @@ void Panel::initWithCall(GroupCall *call) {
|
||||||
closeBeforeDestroy();
|
closeBeforeDestroy();
|
||||||
}, _callLifetime);
|
}, _callLifetime);
|
||||||
|
|
||||||
call->levelUpdates(
|
_call->levelUpdates(
|
||||||
) | rpl::filter([=](const LevelUpdate &update) {
|
) | rpl::filter([=](const LevelUpdate &update) {
|
||||||
return update.me;
|
return update.me;
|
||||||
}) | rpl::start_with_next([=](const LevelUpdate &update) {
|
}) | rpl::start_with_next([=](const LevelUpdate &update) {
|
||||||
_mute->setLevel(update.value);
|
_mute->setLevel(update.value);
|
||||||
}, _callLifetime);
|
}, _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;
|
using namespace rpl::mappers;
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
_call->mutedValue() | MapPushToTalkToActive(),
|
_call->mutedValue() | MapPushToTalkToActive(),
|
||||||
|
@ -600,6 +512,61 @@ void Panel::initWithCall(GroupCall *call) {
|
||||||
}, _callLifetime);
|
}, _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() {
|
void Panel::setupJoinAsChangedToasts() {
|
||||||
_call->rejoinEvents(
|
_call->rejoinEvents(
|
||||||
) | rpl::filter([](RejoinEvent event) {
|
) | rpl::filter([](RejoinEvent event) {
|
||||||
|
@ -623,7 +590,8 @@ void Panel::setupJoinAsChangedToasts() {
|
||||||
void Panel::setupTitleChangedToasts() {
|
void Panel::setupTitleChangedToasts() {
|
||||||
_call->titleChanged(
|
_call->titleChanged(
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return _peer->groupCall() && _peer->groupCall()->id() == _call->id();
|
const auto real = _peer->groupCall();
|
||||||
|
return real && (real->id() == _call->id());
|
||||||
}) | rpl::map([=] {
|
}) | rpl::map([=] {
|
||||||
return _peer->groupCall()->title().isEmpty()
|
return _peer->groupCall()->title().isEmpty()
|
||||||
? _peer->name
|
? _peer->name
|
||||||
|
@ -640,8 +608,44 @@ void Panel::setupTitleChangedToasts() {
|
||||||
}, widget()->lifetime());
|
}, 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) {
|
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();
|
_titleText = real->titleValue();
|
||||||
|
_scheduleDate = real->scheduleDateValue();
|
||||||
|
|
||||||
const auto validateRecordingMark = [=](bool recording) {
|
const auto validateRecordingMark = [=](bool recording) {
|
||||||
if (!recording && _recordingMark) {
|
if (!recording && _recordingMark) {
|
||||||
|
@ -702,7 +706,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
.parentOverride = widget(),
|
.parentOverride = widget(),
|
||||||
.text = (recorded
|
.text = (recorded
|
||||||
? tr::lng_group_call_recording_started
|
? tr::lng_group_call_recording_started
|
||||||
: (_call && _call->recordingStoppedByMe())
|
: _call->recordingStoppedByMe()
|
||||||
? tr::lng_group_call_recording_saved
|
? tr::lng_group_call_recording_saved
|
||||||
: tr::lng_group_call_recording_stopped)(
|
: tr::lng_group_call_recording_stopped)(
|
||||||
tr::now,
|
tr::now,
|
||||||
|
@ -751,9 +755,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||||
void Panel::chooseJoinAs() {
|
void Panel::chooseJoinAs() {
|
||||||
const auto context = ChooseJoinAsProcess::Context::Switch;
|
const auto context = ChooseJoinAsProcess::Context::Switch;
|
||||||
const auto callback = [=](JoinInfo info) {
|
const auto callback = [=](JoinInfo info) {
|
||||||
if (_call) {
|
_call->rejoinAs(info);
|
||||||
_call->rejoinAs(info);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
|
const auto showBox = [=](object_ptr<Ui::BoxContent> next) {
|
||||||
_layerBg->showBox(std::move(next));
|
_layerBg->showBox(std::move(next));
|
||||||
|
@ -774,7 +776,7 @@ void Panel::chooseJoinAs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::showMainMenu() {
|
void Panel::showMainMenu() {
|
||||||
if (_menu || !_call) {
|
if (_menu) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_menu.create(widget(), st::groupCallDropdownMenu);
|
_menu.create(widget(), st::groupCallDropdownMenu);
|
||||||
|
@ -822,7 +824,7 @@ void Panel::showMainMenu() {
|
||||||
|
|
||||||
void Panel::addMembers() {
|
void Panel::addMembers() {
|
||||||
const auto real = _peer->groupCall();
|
const auto real = _peer->groupCall();
|
||||||
if (!_call || !real || real->id() != _call->id()) {
|
if (!real || real->id() != _call->id()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto alreadyIn = _peer->owner().invitedToCallUsers(real->id());
|
auto alreadyIn = _peer->owner().invitedToCallUsers(real->id());
|
||||||
|
@ -848,7 +850,7 @@ void Panel::addMembers() {
|
||||||
&st::groupCallInviteMembersList,
|
&st::groupCallInviteMembersList,
|
||||||
&st::groupCallMultiSelect);
|
&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 invite = [=](const std::vector<not_null<UserData*>> &users) {
|
||||||
const auto call = weak.get();
|
const auto call = weak.get();
|
||||||
if (!call) {
|
if (!call) {
|
||||||
|
@ -1031,7 +1033,7 @@ void Panel::showControls() {
|
||||||
|
|
||||||
void Panel::closeBeforeDestroy() {
|
void Panel::closeBeforeDestroy() {
|
||||||
_window->close();
|
_window->close();
|
||||||
initWithCall(nullptr);
|
_callLifetime.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::initGeometry() {
|
void Panel::initGeometry() {
|
||||||
|
@ -1066,28 +1068,8 @@ void Panel::updateControlsGeometry() {
|
||||||
if (widget()->size().isEmpty()) {
|
if (widget()->size().isEmpty()) {
|
||||||
return;
|
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 muteTop = widget()->height() - st::groupCallMuteBottomSkip;
|
||||||
const auto buttonsTop = widget()->height() - st::groupCallButtonBottomSkip;
|
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 muteSize = _mute->innerSize().width();
|
||||||
const auto fullWidth = muteSize
|
const auto fullWidth = muteSize
|
||||||
+ 2 * _settings->width()
|
+ 2 * _settings->width()
|
||||||
|
@ -1095,6 +1077,8 @@ void Panel::updateControlsGeometry() {
|
||||||
_mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop });
|
_mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop });
|
||||||
_settings->moveToLeft((widget()->width() - fullWidth) / 2, buttonsTop);
|
_settings->moveToLeft((widget()->width() - fullWidth) / 2, buttonsTop);
|
||||||
_hangup->moveToRight((widget()->width() - fullWidth) / 2, buttonsTop);
|
_hangup->moveToRight((widget()->width() - fullWidth) / 2, buttonsTop);
|
||||||
|
|
||||||
|
updateMembersGeometry();
|
||||||
refreshTitle();
|
refreshTitle();
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#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() {
|
void Panel::refreshTitle() {
|
||||||
if (!_title) {
|
if (!_title) {
|
||||||
auto text = rpl::combine(
|
auto text = rpl::combine(
|
||||||
|
@ -1143,11 +1154,16 @@ void Panel::refreshTitle() {
|
||||||
if (!_subtitle) {
|
if (!_subtitle) {
|
||||||
_subtitle.create(
|
_subtitle.create(
|
||||||
widget(),
|
widget(),
|
||||||
tr::lng_group_call_members(
|
_scheduleDate.value(
|
||||||
lt_count_decimal,
|
) | rpl::map([=](TimeId scheduleDate) {
|
||||||
_members->fullCountValue() | rpl::map([](int value) {
|
return scheduleDate
|
||||||
return (value > 0) ? float64(value) : 1.;
|
? 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);
|
st::groupCallSubtitleLabel);
|
||||||
_subtitle->show();
|
_subtitle->show();
|
||||||
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
_subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
|
@ -73,15 +73,17 @@ private:
|
||||||
void initWindow();
|
void initWindow();
|
||||||
void initWidget();
|
void initWidget();
|
||||||
void initControls();
|
void initControls();
|
||||||
void initWithCall(GroupCall *call);
|
|
||||||
void initLayout();
|
void initLayout();
|
||||||
void initGeometry();
|
void initGeometry();
|
||||||
|
void setupMembers();
|
||||||
void setupJoinAsChangedToasts();
|
void setupJoinAsChangedToasts();
|
||||||
void setupTitleChangedToasts();
|
void setupTitleChangedToasts();
|
||||||
|
void setupAllowedToSpeakToasts();
|
||||||
|
|
||||||
bool handleClose();
|
bool handleClose();
|
||||||
|
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
|
void updateMembersGeometry();
|
||||||
void showControls();
|
void showControls();
|
||||||
|
|
||||||
void endCall();
|
void endCall();
|
||||||
|
@ -100,7 +102,7 @@ private:
|
||||||
void migrate(not_null<ChannelData*> channel);
|
void migrate(not_null<ChannelData*> channel);
|
||||||
void subscribeToPeerChanges();
|
void subscribeToPeerChanges();
|
||||||
|
|
||||||
GroupCall *_call = nullptr;
|
const not_null<GroupCall*> _call;
|
||||||
not_null<PeerData*> _peer;
|
not_null<PeerData*> _peer;
|
||||||
|
|
||||||
const std::unique_ptr<Ui::Window> _window;
|
const std::unique_ptr<Ui::Window> _window;
|
||||||
|
@ -118,8 +120,9 @@ private:
|
||||||
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
|
object_ptr<Ui::IconButton> _menuToggle = { nullptr };
|
||||||
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
||||||
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
object_ptr<Ui::AbstractButton> _joinAsToggle = { nullptr };
|
||||||
object_ptr<Members> _members;
|
object_ptr<Members> _members = { nullptr };
|
||||||
rpl::variable<QString> _titleText;
|
rpl::variable<QString> _titleText;
|
||||||
|
rpl::variable<TimeId> _scheduleDate;
|
||||||
ChooseJoinAsProcess _joinAsProcess;
|
ChooseJoinAsProcess _joinAsProcess;
|
||||||
|
|
||||||
object_ptr<Ui::CallButton> _settings;
|
object_ptr<Ui::CallButton> _settings;
|
||||||
|
|
|
@ -329,6 +329,7 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) {
|
||||||
changePeerEmptyCallFlag();
|
changePeerEmptyCallFlag();
|
||||||
_title = qs(data.vtitle().value_or_empty());
|
_title = qs(data.vtitle().value_or_empty());
|
||||||
_recordStartDate = data.vrecord_start_date().value_or_empty();
|
_recordStartDate = data.vrecord_start_date().value_or_empty();
|
||||||
|
_scheduleDate = data.vschedule_date().value_or_empty();
|
||||||
_allParticipantsLoaded
|
_allParticipantsLoaded
|
||||||
= (_serverParticipantsCount == _participants.size());
|
= (_serverParticipantsCount == _participants.size());
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,15 @@ public:
|
||||||
[[nodiscard]] rpl::producer<TimeId> recordStartDateChanges() const {
|
[[nodiscard]] rpl::producer<TimeId> recordStartDateChanges() const {
|
||||||
return _recordStartDate.changes();
|
return _recordStartDate.changes();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] TimeId scheduleDate() const {
|
||||||
|
return _scheduleDate.current();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<TimeId> scheduleDateValue() const {
|
||||||
|
return _scheduleDate.value();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<TimeId> scheduleDateChanges() const {
|
||||||
|
return _scheduleDate.changes();
|
||||||
|
}
|
||||||
|
|
||||||
void setPeer(not_null<PeerData*> peer);
|
void setPeer(not_null<PeerData*> peer);
|
||||||
|
|
||||||
|
@ -163,6 +172,7 @@ private:
|
||||||
int _serverParticipantsCount = 0;
|
int _serverParticipantsCount = 0;
|
||||||
rpl::variable<int> _fullCount = 0;
|
rpl::variable<int> _fullCount = 0;
|
||||||
rpl::variable<TimeId> _recordStartDate = 0;
|
rpl::variable<TimeId> _recordStartDate = 0;
|
||||||
|
rpl::variable<TimeId> _scheduleDate = 0;
|
||||||
|
|
||||||
base::flat_map<uint32, LastSpokeTimes> _unknownSpokenSsrcs;
|
base::flat_map<uint32, LastSpokeTimes> _unknownSpokenSsrcs;
|
||||||
base::flat_map<PeerId, LastSpokeTimes> _unknownSpokenPeerIds;
|
base::flat_map<PeerId, LastSpokeTimes> _unknownSpokenPeerIds;
|
||||||
|
|
|
@ -330,10 +330,18 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
||||||
|
|
||||||
RegenerateUserpics(state, call, userpicSize);
|
RegenerateUserpics(state, call, userpicSize);
|
||||||
|
|
||||||
call->fullCountValue(
|
rpl::combine(
|
||||||
) | rpl::start_with_next([=](int count) {
|
call->titleValue(),
|
||||||
|
call->scheduleDateValue(),
|
||||||
|
call->fullCountValue()
|
||||||
|
) | rpl::start_with_next([=](
|
||||||
|
const QString &title,
|
||||||
|
TimeId scheduleDate,
|
||||||
|
int count) {
|
||||||
|
state->current.title = title;
|
||||||
|
state->current.scheduleDate = scheduleDate;
|
||||||
state->current.count = count;
|
state->current.count = count;
|
||||||
state->current.shown = (count > 0);
|
state->current.shown = (count > 0) || (scheduleDate != 0);
|
||||||
consumer.put_next_copy(state->current);
|
consumer.put_next_copy(state->current);
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
|
||||||
|
|
|
@ -994,6 +994,7 @@ void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) {
|
||||||
) | rpl::start_with_next([=](Calls::GroupCall::State state) {
|
) | rpl::start_with_next([=](Calls::GroupCall::State state) {
|
||||||
using State = Calls::GroupCall::State;
|
using State = Calls::GroupCall::State;
|
||||||
if (state != State::Creating
|
if (state != State::Creating
|
||||||
|
&& state != State::Waiting
|
||||||
&& state != State::Joining
|
&& state != State::Joining
|
||||||
&& state != State::Joined
|
&& state != State::Joined
|
||||||
&& state != State::Connecting) {
|
&& state != State::Connecting) {
|
||||||
|
|
|
@ -572,36 +572,49 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
rpl::producer<QString> title,
|
rpl::producer<QString> title,
|
||||||
rpl::producer<QString> submit,
|
rpl::producer<QString> submit,
|
||||||
Fn<void(TimeId)> done,
|
Fn<void(TimeId)> done,
|
||||||
TimeId time) {
|
TimeId time,
|
||||||
|
rpl::producer<QString> description) {
|
||||||
|
struct State {
|
||||||
|
rpl::variable<QDate> date;
|
||||||
|
not_null<InputField*> day;
|
||||||
|
not_null<TimeInput*> time;
|
||||||
|
not_null<FlatLabel*> at;
|
||||||
|
};
|
||||||
box->setTitle(std::move(title));
|
box->setTitle(std::move(title));
|
||||||
box->setWidth(st::boxWideWidth);
|
box->setWidth(st::boxWideWidth);
|
||||||
|
|
||||||
const auto date = CreateChild<rpl::variable<QDate>>(
|
|
||||||
box.get(),
|
|
||||||
base::unixtime::parse(time).date());
|
|
||||||
const auto content = box->addRow(
|
const auto content = box->addRow(
|
||||||
object_ptr<FixedHeightWidget>(box, st::scheduleHeight));
|
object_ptr<FixedHeightWidget>(box, st::scheduleHeight));
|
||||||
const auto dayInput = CreateChild<InputField>(
|
if (description) {
|
||||||
content,
|
box->addRow(object_ptr<FlatLabel>(
|
||||||
st::scheduleDateField);
|
box,
|
||||||
const auto timeInput = CreateChild<TimeInput>(
|
std::move(description),
|
||||||
content,
|
st::boxLabel));
|
||||||
TimeString(time));
|
}
|
||||||
const auto at = CreateChild<FlatLabel>(
|
const auto state = box->lifetime().make_state<State>(State{
|
||||||
content,
|
.date = base::unixtime::parse(time).date(),
|
||||||
tr::lng_schedule_at(),
|
.day = CreateChild<InputField>(
|
||||||
st::scheduleAtLabel);
|
content,
|
||||||
|
st::scheduleDateField),
|
||||||
|
.time = CreateChild<TimeInput>(
|
||||||
|
content,
|
||||||
|
TimeString(time)),
|
||||||
|
.at = CreateChild<FlatLabel>(
|
||||||
|
content,
|
||||||
|
tr::lng_schedule_at(),
|
||||||
|
st::scheduleAtLabel),
|
||||||
|
});
|
||||||
|
|
||||||
date->value(
|
state->date.value(
|
||||||
) | rpl::start_with_next([=](QDate date) {
|
) | rpl::start_with_next([=](QDate date) {
|
||||||
dayInput->setText(DayString(date));
|
state->day->setText(DayString(date));
|
||||||
timeInput->setFocusFast();
|
state->time->setFocusFast();
|
||||||
}, dayInput->lifetime());
|
}, state->day->lifetime());
|
||||||
|
|
||||||
const auto minDate = QDate::currentDate();
|
const auto minDate = QDate::currentDate();
|
||||||
const auto maxDate = minDate.addYears(1).addDays(-1);
|
const auto maxDate = minDate.addYears(1).addDays(-1);
|
||||||
|
|
||||||
const auto &dayViewport = dayInput->rawTextEdit()->viewport();
|
const auto &dayViewport = state->day->rawTextEdit()->viewport();
|
||||||
base::install_event_filter(dayViewport, [=](not_null<QEvent*> event) {
|
base::install_event_filter(dayViewport, [=](not_null<QEvent*> event) {
|
||||||
if (event->type() == QEvent::Wheel) {
|
if (event->type() == QEvent::Wheel) {
|
||||||
const auto e = static_cast<QWheelEvent*>(event.get());
|
const auto e = static_cast<QWheelEvent*>(event.get());
|
||||||
|
@ -609,8 +622,8 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
if (!direction) {
|
if (!direction) {
|
||||||
return base::EventFilterResult::Continue;
|
return base::EventFilterResult::Continue;
|
||||||
}
|
}
|
||||||
const auto d = date->current().addDays(direction);
|
const auto d = state->date.current().addDays(direction);
|
||||||
*date = std::clamp(d, minDate, maxDate);
|
state->date = std::clamp(d, minDate, maxDate);
|
||||||
return base::EventFilterResult::Cancel;
|
return base::EventFilterResult::Cancel;
|
||||||
}
|
}
|
||||||
return base::EventFilterResult::Continue;
|
return base::EventFilterResult::Continue;
|
||||||
|
@ -619,19 +632,19 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
content->widthValue(
|
content->widthValue(
|
||||||
) | rpl::start_with_next([=](int width) {
|
) | rpl::start_with_next([=](int width) {
|
||||||
const auto paddings = width
|
const auto paddings = width
|
||||||
- at->width()
|
- state->at->width()
|
||||||
- 2 * st::scheduleAtSkip
|
- 2 * st::scheduleAtSkip
|
||||||
- st::scheduleDateWidth
|
- st::scheduleDateWidth
|
||||||
- st::scheduleTimeWidth;
|
- st::scheduleTimeWidth;
|
||||||
const auto left = paddings / 2;
|
const auto left = paddings / 2;
|
||||||
dayInput->resizeToWidth(st::scheduleDateWidth);
|
state->day->resizeToWidth(st::scheduleDateWidth);
|
||||||
dayInput->moveToLeft(left, st::scheduleDateTop, width);
|
state->day->moveToLeft(left, st::scheduleDateTop, width);
|
||||||
at->moveToLeft(
|
state->at->moveToLeft(
|
||||||
left + st::scheduleDateWidth + st::scheduleAtSkip,
|
left + st::scheduleDateWidth + st::scheduleAtSkip,
|
||||||
st::scheduleAtTop,
|
st::scheduleAtTop,
|
||||||
width);
|
width);
|
||||||
timeInput->resizeToWidth(st::scheduleTimeWidth);
|
state->time->resizeToWidth(st::scheduleTimeWidth);
|
||||||
timeInput->moveToLeft(
|
state->time->moveToLeft(
|
||||||
width - left - st::scheduleTimeWidth,
|
width - left - st::scheduleTimeWidth,
|
||||||
st::scheduleDateTop,
|
st::scheduleDateTop,
|
||||||
width);
|
width);
|
||||||
|
@ -639,12 +652,12 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
|
|
||||||
const auto calendar =
|
const auto calendar =
|
||||||
content->lifetime().make_state<QPointer<CalendarBox>>();
|
content->lifetime().make_state<QPointer<CalendarBox>>();
|
||||||
QObject::connect(dayInput, &InputField::focused, [=] {
|
QObject::connect(state->day, &InputField::focused, [=] {
|
||||||
if (*calendar) {
|
if (*calendar) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto chosen = [=](QDate chosen) {
|
const auto chosen = [=](QDate chosen) {
|
||||||
*date = chosen;
|
state->date = chosen;
|
||||||
(*calendar)->closeBox();
|
(*calendar)->closeBox();
|
||||||
};
|
};
|
||||||
const auto finalize = [=](not_null<CalendarBox*> box) {
|
const auto finalize = [=](not_null<CalendarBox*> box) {
|
||||||
|
@ -652,31 +665,28 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
box->setMaxDate(maxDate);
|
box->setMaxDate(maxDate);
|
||||||
};
|
};
|
||||||
*calendar = box->getDelegate()->show(Box<CalendarBox>(
|
*calendar = box->getDelegate()->show(Box<CalendarBox>(
|
||||||
date->current(),
|
state->date.current(),
|
||||||
date->current(),
|
state->date.current(),
|
||||||
crl::guard(box, chosen),
|
crl::guard(box, chosen),
|
||||||
finalize));
|
finalize));
|
||||||
(*calendar)->boxClosing(
|
(*calendar)->boxClosing(
|
||||||
) | rpl::start_with_next(crl::guard(timeInput, [=] {
|
) | rpl::start_with_next(crl::guard(state->time, [=] {
|
||||||
timeInput->setFocusFast();
|
state->time->setFocusFast();
|
||||||
}), (*calendar)->lifetime());
|
}), (*calendar)->lifetime());
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto collect = [=] {
|
const auto collect = [=] {
|
||||||
const auto timeValue = timeInput->valueCurrent().split(':');
|
const auto timeValue = state->time->valueCurrent().split(':');
|
||||||
if (timeValue.size() != 2) {
|
if (timeValue.size() != 2) {
|
||||||
timeInput->showError();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto time = QTime(timeValue[0].toInt(), timeValue[1].toInt());
|
const auto time = QTime(timeValue[0].toInt(), timeValue[1].toInt());
|
||||||
if (!time.isValid()) {
|
if (!time.isValid()) {
|
||||||
timeInput->showError();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto result = base::unixtime::serialize(
|
const auto result = base::unixtime::serialize(
|
||||||
QDateTime(date->current(), time));
|
QDateTime(state->date.current(), time));
|
||||||
if (result <= base::unixtime::now() + kMinimalSchedule) {
|
if (result <= base::unixtime::now() + kMinimalSchedule) {
|
||||||
timeInput->showError();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -684,17 +694,27 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
const auto save = [=] {
|
const auto save = [=] {
|
||||||
if (const auto result = collect()) {
|
if (const auto result = collect()) {
|
||||||
done(result);
|
done(result);
|
||||||
|
} else {
|
||||||
|
state->time->showError();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
timeInput->submitRequests(
|
state->time->submitRequests(
|
||||||
) | rpl::start_with_next(
|
) | rpl::start_with_next(save, state->time->lifetime());
|
||||||
save,
|
|
||||||
timeInput->lifetime());
|
|
||||||
|
|
||||||
auto result = ChooseDateTimeBoxDescriptor();
|
auto result = ChooseDateTimeBoxDescriptor();
|
||||||
box->setFocusCallback([=] { timeInput->setFocusFast(); });
|
box->setFocusCallback([=] { state->time->setFocusFast(); });
|
||||||
result.submit = box->addButton(std::move(submit), save);
|
result.submit = box->addButton(std::move(submit), save);
|
||||||
result.collect = collect;
|
result.collect = [=] {
|
||||||
|
if (const auto result = collect()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
state->time->showError();
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
result.values = rpl::combine(
|
||||||
|
state->date.value(),
|
||||||
|
state->time->value()
|
||||||
|
) | rpl::map(collect);
|
||||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -16,6 +16,7 @@ class RoundButton;
|
||||||
struct ChooseDateTimeBoxDescriptor {
|
struct ChooseDateTimeBoxDescriptor {
|
||||||
QPointer<RoundButton> submit;
|
QPointer<RoundButton> submit;
|
||||||
Fn<TimeId()> collect;
|
Fn<TimeId()> collect;
|
||||||
|
rpl::producer<TimeId> values;
|
||||||
};
|
};
|
||||||
|
|
||||||
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
|
@ -23,6 +24,7 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||||
rpl::producer<QString> title,
|
rpl::producer<QString> title,
|
||||||
rpl::producer<QString> submit,
|
rpl::producer<QString> submit,
|
||||||
Fn<void(TimeId)> done,
|
Fn<void(TimeId)> done,
|
||||||
TimeId time);
|
TimeId time,
|
||||||
|
rpl::producer<QString> description = nullptr);
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
#include "styles/style_info.h" // st::topBarArrowPadding, like TopBarWidget.
|
||||||
|
@ -126,16 +127,59 @@ void GroupCallBar::paint(Painter &p) {
|
||||||
const auto titleTop = st::msgReplyPadding.top();
|
const auto titleTop = st::msgReplyPadding.top();
|
||||||
const auto textTop = titleTop + st::msgServiceNameFont->height;
|
const auto textTop = titleTop + st::msgServiceNameFont->height;
|
||||||
const auto width = _inner->width();
|
const auto width = _inner->width();
|
||||||
|
const auto &font = st::defaultMessageBar.title.font;
|
||||||
p.setPen(st::defaultMessageBar.textFg);
|
p.setPen(st::defaultMessageBar.textFg);
|
||||||
p.setFont(st::defaultMessageBar.title.font);
|
p.setFont(font);
|
||||||
p.drawTextLeft(left, titleTop, width, tr::lng_group_call_title(tr::now));
|
|
||||||
|
const auto available = _join->x() - left;
|
||||||
|
const auto titleWidth = font->width(_content.title);
|
||||||
|
p.drawTextLeft(
|
||||||
|
left,
|
||||||
|
titleTop,
|
||||||
|
width,
|
||||||
|
(!_content.scheduleDate
|
||||||
|
? tr::lng_group_call_title(tr::now)
|
||||||
|
: _content.title.isEmpty()
|
||||||
|
? tr::lng_group_call_scheduled_title(tr::now)
|
||||||
|
: (titleWidth > available)
|
||||||
|
? font->elided(_content.title, available)
|
||||||
|
: _content.title));
|
||||||
p.setPen(st::historyStatusFg);
|
p.setPen(st::historyStatusFg);
|
||||||
p.setFont(st::defaultMessageBar.text.font);
|
p.setFont(st::defaultMessageBar.text.font);
|
||||||
|
const auto when = [&] {
|
||||||
|
if (!_content.scheduleDate) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
const auto parsed = base::unixtime::parse(_content.scheduleDate);
|
||||||
|
const auto date = parsed.date();
|
||||||
|
const auto time = parsed.time().toString(
|
||||||
|
QLocale::system().timeFormat(QLocale::ShortFormat));
|
||||||
|
const auto today = QDate::currentDate();
|
||||||
|
if (date == today) {
|
||||||
|
return tr::lng_group_call_starts_today(tr::now, lt_time, time);
|
||||||
|
} else if (date == today.addDays(1)) {
|
||||||
|
return tr::lng_group_call_starts_tomorrow(
|
||||||
|
tr::now,
|
||||||
|
lt_time,
|
||||||
|
time);
|
||||||
|
} else {
|
||||||
|
return tr::lng_group_call_starts_date(
|
||||||
|
tr::now,
|
||||||
|
lt_date,
|
||||||
|
langDayOfMonthFull(date),
|
||||||
|
lt_time,
|
||||||
|
time);
|
||||||
|
}
|
||||||
|
}();
|
||||||
p.drawTextLeft(
|
p.drawTextLeft(
|
||||||
left,
|
left,
|
||||||
textTop,
|
textTop,
|
||||||
width,
|
width,
|
||||||
(_content.count > 0
|
(_content.scheduleDate
|
||||||
|
? (_content.title.isEmpty()
|
||||||
|
? tr::lng_group_call_starts_short
|
||||||
|
: tr::lng_group_call_starts)(tr::now, lt_when, when)
|
||||||
|
: _content.count > 0
|
||||||
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
|
? tr::lng_group_call_members(tr::now, lt_count, _content.count)
|
||||||
: tr::lng_group_call_no_members(tr::now)));
|
: tr::lng_group_call_no_members(tr::now)));
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ struct GroupCallUser;
|
||||||
class GroupCallUserpics;
|
class GroupCallUserpics;
|
||||||
|
|
||||||
struct GroupCallBarContent {
|
struct GroupCallBarContent {
|
||||||
|
QString title;
|
||||||
|
TimeId scheduleDate = 0;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
bool shown = false;
|
bool shown = false;
|
||||||
std::vector<GroupCallUser> users;
|
std::vector<GroupCallUser> users;
|
||||||
|
|
Loading…
Add table
Reference in a new issue