Add creating of a scheduled group call.
This commit is contained in:
parent
e6587f2556
commit
15d17c8b0e
|
@ -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_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_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_edit_title" = "Edit voice chat title";
|
||||
"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 "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();
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -44,6 +44,7 @@ struct JoinInfo {
|
|||
not_null<PeerData*> joinAs;
|
||||
std::vector<not_null<PeerData*>> possibleJoinAs;
|
||||
QString joinHash;
|
||||
TimeId scheduleDate = 0;
|
||||
};
|
||||
|
||||
} // namespace Calls::Group
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -329,6 +329,7 @@ void GroupCall::applyCallFields(const MTPDgroupCall &data) {
|
|||
changePeerEmptyCallFlag();
|
||||
_title = qs(data.vtitle().value_or_empty());
|
||||
_recordStartDate = data.vrecord_start_date().value_or_empty();
|
||||
_scheduleDate = data.vschedule_date().value_or_empty();
|
||||
_allParticipantsLoaded
|
||||
= (_serverParticipantsCount == _participants.size());
|
||||
}
|
||||
|
|
|
@ -63,6 +63,15 @@ public:
|
|||
[[nodiscard]] rpl::producer<TimeId> recordStartDateChanges() const {
|
||||
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);
|
||||
|
||||
|
@ -163,6 +172,7 @@ private:
|
|||
int _serverParticipantsCount = 0;
|
||||
rpl::variable<int> _fullCount = 0;
|
||||
rpl::variable<TimeId> _recordStartDate = 0;
|
||||
rpl::variable<TimeId> _scheduleDate = 0;
|
||||
|
||||
base::flat_map<uint32, LastSpokeTimes> _unknownSpokenSsrcs;
|
||||
base::flat_map<PeerId, LastSpokeTimes> _unknownSpokenPeerIds;
|
||||
|
|
|
@ -330,10 +330,18 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallTracker::ContentByCall(
|
|||
|
||||
RegenerateUserpics(state, call, userpicSize);
|
||||
|
||||
call->fullCountValue(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
rpl::combine(
|
||||
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.shown = (count > 0);
|
||||
state->current.shown = (count > 0) || (scheduleDate != 0);
|
||||
consumer.put_next_copy(state->current);
|
||||
}, lifetime);
|
||||
|
||||
|
|
|
@ -994,6 +994,7 @@ void MainWidget::setCurrentGroupCall(Calls::GroupCall *call) {
|
|||
) | rpl::start_with_next([=](Calls::GroupCall::State state) {
|
||||
using State = Calls::GroupCall::State;
|
||||
if (state != State::Creating
|
||||
&& state != State::Waiting
|
||||
&& state != State::Joining
|
||||
&& state != State::Joined
|
||||
&& state != State::Connecting) {
|
||||
|
|
|
@ -572,36 +572,49 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> submit,
|
||||
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->setWidth(st::boxWideWidth);
|
||||
|
||||
const auto date = CreateChild<rpl::variable<QDate>>(
|
||||
box.get(),
|
||||
base::unixtime::parse(time).date());
|
||||
const auto content = box->addRow(
|
||||
object_ptr<FixedHeightWidget>(box, st::scheduleHeight));
|
||||
const auto dayInput = CreateChild<InputField>(
|
||||
content,
|
||||
st::scheduleDateField);
|
||||
const auto timeInput = CreateChild<TimeInput>(
|
||||
content,
|
||||
TimeString(time));
|
||||
const auto at = CreateChild<FlatLabel>(
|
||||
content,
|
||||
tr::lng_schedule_at(),
|
||||
st::scheduleAtLabel);
|
||||
if (description) {
|
||||
box->addRow(object_ptr<FlatLabel>(
|
||||
box,
|
||||
std::move(description),
|
||||
st::boxLabel));
|
||||
}
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.date = base::unixtime::parse(time).date(),
|
||||
.day = CreateChild<InputField>(
|
||||
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) {
|
||||
dayInput->setText(DayString(date));
|
||||
timeInput->setFocusFast();
|
||||
}, dayInput->lifetime());
|
||||
state->day->setText(DayString(date));
|
||||
state->time->setFocusFast();
|
||||
}, state->day->lifetime());
|
||||
|
||||
const auto minDate = QDate::currentDate();
|
||||
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) {
|
||||
if (event->type() == QEvent::Wheel) {
|
||||
const auto e = static_cast<QWheelEvent*>(event.get());
|
||||
|
@ -609,8 +622,8 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
if (!direction) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
const auto d = date->current().addDays(direction);
|
||||
*date = std::clamp(d, minDate, maxDate);
|
||||
const auto d = state->date.current().addDays(direction);
|
||||
state->date = std::clamp(d, minDate, maxDate);
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
|
@ -619,19 +632,19 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
content->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto paddings = width
|
||||
- at->width()
|
||||
- state->at->width()
|
||||
- 2 * st::scheduleAtSkip
|
||||
- st::scheduleDateWidth
|
||||
- st::scheduleTimeWidth;
|
||||
const auto left = paddings / 2;
|
||||
dayInput->resizeToWidth(st::scheduleDateWidth);
|
||||
dayInput->moveToLeft(left, st::scheduleDateTop, width);
|
||||
at->moveToLeft(
|
||||
state->day->resizeToWidth(st::scheduleDateWidth);
|
||||
state->day->moveToLeft(left, st::scheduleDateTop, width);
|
||||
state->at->moveToLeft(
|
||||
left + st::scheduleDateWidth + st::scheduleAtSkip,
|
||||
st::scheduleAtTop,
|
||||
width);
|
||||
timeInput->resizeToWidth(st::scheduleTimeWidth);
|
||||
timeInput->moveToLeft(
|
||||
state->time->resizeToWidth(st::scheduleTimeWidth);
|
||||
state->time->moveToLeft(
|
||||
width - left - st::scheduleTimeWidth,
|
||||
st::scheduleDateTop,
|
||||
width);
|
||||
|
@ -639,12 +652,12 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
|
||||
const auto calendar =
|
||||
content->lifetime().make_state<QPointer<CalendarBox>>();
|
||||
QObject::connect(dayInput, &InputField::focused, [=] {
|
||||
QObject::connect(state->day, &InputField::focused, [=] {
|
||||
if (*calendar) {
|
||||
return;
|
||||
}
|
||||
const auto chosen = [=](QDate chosen) {
|
||||
*date = chosen;
|
||||
state->date = chosen;
|
||||
(*calendar)->closeBox();
|
||||
};
|
||||
const auto finalize = [=](not_null<CalendarBox*> box) {
|
||||
|
@ -652,31 +665,28 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
box->setMaxDate(maxDate);
|
||||
};
|
||||
*calendar = box->getDelegate()->show(Box<CalendarBox>(
|
||||
date->current(),
|
||||
date->current(),
|
||||
state->date.current(),
|
||||
state->date.current(),
|
||||
crl::guard(box, chosen),
|
||||
finalize));
|
||||
(*calendar)->boxClosing(
|
||||
) | rpl::start_with_next(crl::guard(timeInput, [=] {
|
||||
timeInput->setFocusFast();
|
||||
) | rpl::start_with_next(crl::guard(state->time, [=] {
|
||||
state->time->setFocusFast();
|
||||
}), (*calendar)->lifetime());
|
||||
});
|
||||
|
||||
const auto collect = [=] {
|
||||
const auto timeValue = timeInput->valueCurrent().split(':');
|
||||
const auto timeValue = state->time->valueCurrent().split(':');
|
||||
if (timeValue.size() != 2) {
|
||||
timeInput->showError();
|
||||
return 0;
|
||||
}
|
||||
const auto time = QTime(timeValue[0].toInt(), timeValue[1].toInt());
|
||||
if (!time.isValid()) {
|
||||
timeInput->showError();
|
||||
return 0;
|
||||
}
|
||||
const auto result = base::unixtime::serialize(
|
||||
QDateTime(date->current(), time));
|
||||
QDateTime(state->date.current(), time));
|
||||
if (result <= base::unixtime::now() + kMinimalSchedule) {
|
||||
timeInput->showError();
|
||||
return 0;
|
||||
}
|
||||
return result;
|
||||
|
@ -684,17 +694,27 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
const auto save = [=] {
|
||||
if (const auto result = collect()) {
|
||||
done(result);
|
||||
} else {
|
||||
state->time->showError();
|
||||
}
|
||||
};
|
||||
timeInput->submitRequests(
|
||||
) | rpl::start_with_next(
|
||||
save,
|
||||
timeInput->lifetime());
|
||||
state->time->submitRequests(
|
||||
) | rpl::start_with_next(save, state->time->lifetime());
|
||||
|
||||
auto result = ChooseDateTimeBoxDescriptor();
|
||||
box->setFocusCallback([=] { timeInput->setFocusFast(); });
|
||||
box->setFocusCallback([=] { state->time->setFocusFast(); });
|
||||
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(); });
|
||||
|
||||
return result;
|
||||
|
|
|
@ -16,6 +16,7 @@ class RoundButton;
|
|||
struct ChooseDateTimeBoxDescriptor {
|
||||
QPointer<RoundButton> submit;
|
||||
Fn<TimeId()> collect;
|
||||
rpl::producer<TimeId> values;
|
||||
};
|
||||
|
||||
ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
||||
|
@ -23,6 +24,7 @@ ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
|
|||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> submit,
|
||||
Fn<void(TimeId)> done,
|
||||
TimeId time);
|
||||
TimeId time,
|
||||
rpl::producer<QString> description = nullptr);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_calls.h"
|
||||
#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 textTop = titleTop + st::msgServiceNameFont->height;
|
||||
const auto width = _inner->width();
|
||||
const auto &font = st::defaultMessageBar.title.font;
|
||||
p.setPen(st::defaultMessageBar.textFg);
|
||||
p.setFont(st::defaultMessageBar.title.font);
|
||||
p.drawTextLeft(left, titleTop, width, tr::lng_group_call_title(tr::now));
|
||||
p.setFont(font);
|
||||
|
||||
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.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(
|
||||
left,
|
||||
textTop,
|
||||
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_no_members(tr::now)));
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ struct GroupCallUser;
|
|||
class GroupCallUserpics;
|
||||
|
||||
struct GroupCallBarContent {
|
||||
QString title;
|
||||
TimeId scheduleDate = 0;
|
||||
int count = 0;
|
||||
bool shown = false;
|
||||
std::vector<GroupCallUser> users;
|
||||
|
|
Loading…
Reference in New Issue