Show recording indicator.

This commit is contained in:
John Preston 2021-03-09 17:24:32 +04:00
parent 50265afe93
commit 7edc91e29e
7 changed files with 146 additions and 80 deletions

View File

@ -2004,7 +2004,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_recording_stop_sure" = "Do you want to stop recording this chat?";
"lng_group_call_recording_start_field" = "Recording Title";
"lng_group_call_recording_start_button" = "Start";
"lng_group_call_is_recorded" = "Voice chat is being recorded";
"lng_group_call_is_recorded" = "Voice chat is being recorded.";
"lng_group_call_can_speak_here" = "You can now speak.";
"lng_group_call_can_speak" = "You can now speak in **{chat}**.";

View File

@ -560,6 +560,9 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
}
groupCallAddButtonPosition: point(10px, 7px);
groupCallMembersWidthMax: 360px;
groupCallRecordingMark: 6px;
groupCallRecordingMarkSkip: 4px;
groupCallRecordingMarkTop: 6px;
groupCallActiveButton: IconButton {
width: 36px;

View File

@ -427,7 +427,7 @@ void GroupPanel::initWindow() {
0,
0,
widget()->width(),
computeMembersListTop());
st::groupCallMembersTop);
return titleRect.contains(widgetPoint)
? (Flag::Move | Flag::Maximize)
: Flag::None;
@ -593,6 +593,46 @@ void GroupPanel::initWithCall(GroupCall *call) {
void GroupPanel::subscribeToChanges(not_null<Data::GroupCall*> real) {
_titleText = real->titleValue();
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
_recordingMark.destroy();
} else if (recording && !_recordingMark) {
_recordingMark.create(widget());
const auto size = st::groupCallRecordingMark;
const auto skip = st::groupCallRecordingMarkSkip;
_recordingMark->resize(size + 2 * skip, size + 2 * skip);
_recordingMark->setClickedCallback([=] {
Ui::Toast::Show(
widget(),
tr::lng_group_call_is_recorded(tr::now));
});
_recordingMark->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(_recordingMark.data());
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::groupCallMemberMutedIcon);
p.drawEllipse(skip, skip, size, size);
}, _recordingMark->lifetime());
}
refreshTitleGeometry();
};
using namespace rpl::mappers;
real->recordStartDateChanges(
) | rpl::map(
_1 != 0
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool recorded) {
validateRecordingMark(recorded);
Ui::Toast::Show(
widget(),
(recorded
? tr::lng_group_call_recording_started(tr::now)
: tr::lng_group_call_recording_stopped(tr::now)));
}, _callLifetime);
validateRecordingMark(real->recordStartDate() != 0);
}
void GroupPanel::addMembers() {
@ -805,20 +845,17 @@ void GroupPanel::initGeometry() {
updateControlsGeometry();
}
int GroupPanel::computeMembersListTop() const {
if (computeTitleRect().has_value()) {
return st::groupCallMembersTop;
}
return st::groupCallMembersTop
- (st::groupCallSubtitleTop - st::groupCallTitleTop);
}
std::optional<QRect> GroupPanel::computeTitleRect() const {
QRect GroupPanel::computeTitleRect() const {
const auto skip = st::groupCallTitleTop;
const auto width = widget()->width();
#ifdef Q_OS_MAC
return QRect(70, 0, widget()->width() - 70, 28);
return QRect(70, 0, width - skip - 70, 28);
#else // Q_OS_MAC
const auto controls = _controls->geometry();
return QRect(0, 0, controls.x(), controls.height());
const auto right = controls.x() + controls.width() + skip;
return (controls.center().x() < width / 2)
? QRect(right, 0, width - right - skip, controls.height())
: QRect(skip, 0, controls.x() - 2 * skip, controls.height());
#endif // !Q_OS_MAC
}
@ -839,7 +876,7 @@ void GroupPanel::updateControlsGeometry() {
st::groupCallMembersWidthMax);
const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip;
const auto buttonsTop = widget()->height() - st::groupCallButtonBottomSkip;
const auto membersTop = computeMembersListTop();
const auto membersTop = st::groupCallMembersTop;
const auto availableHeight = muteTop
- membersTop
- st::groupCallMembersMargin.bottom();
@ -859,29 +896,25 @@ void GroupPanel::updateControlsGeometry() {
}
void GroupPanel::refreshTitle() {
if (computeTitleRect().has_value()) {
if (!_title) {
auto text = rpl::combine(
Info::Profile::NameValue(_peer),
_titleText.value()
) | rpl::map([=](
const TextWithEntities &name,
const QString &title) {
return title.isEmpty() ? name.text : title;
}) | rpl::after_next([=] {
refreshTitleGeometry();
});
_title.create(
widget(),
rpl::duplicate(text),
st::groupCallTitleLabel);
_title->show();
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
}
refreshTitleGeometry();
} else if (_title) {
_title.destroy();
if (!_title) {
auto text = rpl::combine(
Info::Profile::NameValue(_peer),
_titleText.value()
) | rpl::map([=](
const TextWithEntities &name,
const QString &title) {
return title.isEmpty() ? name.text : title;
}) | rpl::after_next([=] {
refreshTitleGeometry();
});
_title.create(
widget(),
rpl::duplicate(text),
st::groupCallTitleLabel);
_title->show();
_title->setAttribute(Qt::WA_TransparentForMouseEvents);
}
refreshTitleGeometry();
if (!_subtitle) {
_subtitle.create(
widget(),
@ -904,26 +937,41 @@ void GroupPanel::refreshTitle() {
}
void GroupPanel::refreshTitleGeometry() {
const auto titleRect = computeTitleRect();
if (!_title || !titleRect) {
if (!_title) {
return;
}
const auto fullRect = computeTitleRect();
const auto recordingWidth = 2 * st::groupCallRecordingMarkSkip
+ st::groupCallRecordingMark;
const auto titleRect = _recordingMark
? QRect(
fullRect.x(),
fullRect.y(),
fullRect.width() - _recordingMark->width(),
fullRect.height())
: fullRect;
const auto best = _title->naturalWidth();
const auto from = (widget()->width() - best) / 2;
const auto top = st::groupCallTitleTop;
const auto left = titleRect->x();
if (from >= left && from + best <= left + titleRect->width()) {
const auto left = titleRect.x();
if (from >= left && from + best <= left + titleRect.width()) {
_title->resizeToWidth(best);
_title->moveToLeft(from, top);
} else if (titleRect->width() < best) {
_title->resizeToWidth(titleRect->width());
} else if (titleRect.width() < best) {
_title->resizeToWidth(titleRect.width());
_title->moveToLeft(left, top);
} else if (from < left) {
_title->resizeToWidth(best);
_title->moveToLeft(left, top);
} else {
_title->resizeToWidth(best);
_title->moveToLeft(left + titleRect->width() - best, top);
_title->moveToLeft(left + titleRect.width() - best, top);
}
if (_recordingMark) {
const auto markTop = top + st::groupCallRecordingMarkTop;
_recordingMark->move(
_title->x() + _title->width(),
markTop - st::groupCallRecordingMarkSkip);
}
}

View File

@ -23,6 +23,7 @@ class GroupCall;
} // namespace Data
namespace Ui {
class AbstractButton;
class CallButton;
class CallMuteButton;
class IconButton;
@ -98,8 +99,7 @@ private:
void addMembers();
void kickMember(not_null<UserData*> user);
void kickMemberSure(not_null<UserData*> user);
[[nodiscard]] int computeMembersListTop() const;
[[nodiscard]] std::optional<QRect> computeTitleRect() const;
[[nodiscard]] QRect computeTitleRect() const;
void refreshTitle();
void refreshTitleGeometry();
void setupRealCallViewers(not_null<GroupCall*> call);
@ -122,6 +122,7 @@ private:
object_ptr<Ui::FlatLabel> _title = { nullptr };
object_ptr<Ui::FlatLabel> _subtitle = { nullptr };
object_ptr<Ui::AbstractButton> _recordingMark = { nullptr };
object_ptr<GroupMembers> _members;
rpl::variable<QString> _titleText;

View File

@ -213,37 +213,45 @@ void GroupCallSettingsBox(
tr::lng_group_call_edit_title(),
st::groupCallSettingsButton).get()
: nullptr;
const auto recordStartDate = goodReal ? real->recordStartDate() : 0;
auto recordingLabel = [&] {
return ;
static const auto ToDurationFrom = [](TimeId startDate) {
return [=] {
const auto now = base::unixtime::now();
const auto elapsed = std::max(now - startDate, 0);
const auto hours = elapsed / 3600;
const auto minutes = (elapsed % 3600) / 60;
const auto seconds = (elapsed % 60);
return hours
? QString("%1:%2:%3"
).arg(hours
).arg(minutes, 2, 10, QChar('0')
).arg(seconds, 2, 10, QChar('0'))
: QString("%1:%2"
).arg(minutes
).arg(seconds, 2, 10, QChar('0'));
};
};
static const auto ToRecordDuration = [](TimeId startDate) {
return !startDate
? (rpl::single(QString()) | rpl::type_erased())
: rpl::single(
rpl::empty_value()
) | rpl::then(base::timer_each(
crl::time(1000)
)) | rpl::map(ToDurationFrom(startDate));
};
using namespace rpl::mappers;
const auto editRecording = !addEditRecording
? nullptr
: !recordStartDate
? AddButton(
layout,
tr::lng_group_call_recording_start(),
st::groupCallSettingsButton).get()
: AddButtonWithLabel(
layout,
tr::lng_group_call_recording_stop(),
base::timer_each(
crl::time(1000)
) | rpl::map([=] {
const auto now = base::unixtime::now();
const auto elapsed = std::max(now - recordStartDate, 0);
const auto hours = elapsed / 3600;
const auto minutes = (elapsed % 3600) / 60;
const auto seconds = (elapsed % 60);
return hours
? QString("%1:%2:%3"
).arg(hours
).arg(minutes, 2, 10, QChar('0')
).arg(seconds, 2, 10, QChar('0'))
: QString("%1:%2"
).arg(minutes
).arg(seconds, 2, 10, QChar('0'));
}),
rpl::conditional(
real->recordStartDateValue() | rpl::map(!!_1),
tr::lng_group_call_recording_stop(),
tr::lng_group_call_recording_start()),
real->recordStartDateValue(
) | rpl::map(
ToRecordDuration
) | rpl::flatten_latest(),
st::groupCallSettingsButton).get();
if (editJoinAs) {
editJoinAs->setClickedCallback([=] {
@ -282,14 +290,14 @@ void GroupCallSettingsBox(
}
if (editRecording) {
editRecording->setClickedCallback([=] {
const auto real = peer->groupCall();
const auto id = call->id();
if (!real || real->id() != id) {
return;
}
const auto recordStartDate = real->recordStartDate();
const auto done = [=](QString title) {
call->toggleRecording(!recordStartDate, title);
const auto container = box->getDelegate()->outerContainer();
Ui::Toast::Show(
container,
(recordStartDate
? tr::lng_group_call_recording_stopped(tr::now)
: tr::lng_group_call_recording_started(tr::now)));
box->closeBox();
};
if (recordStartDate) {

View File

@ -184,7 +184,7 @@ void GroupCall::applyCall(const MTPGroupCall &call, bool force) {
|| (_fullCount.current() != data.vparticipants_count().v)
|| (_canChangeJoinMuted != data.is_can_change_join_muted())
|| (_title.current() != title)
|| (_recordStartDate != recordDate);
|| (_recordStartDate.current() != recordDate);
if (!force && !changed) {
return;
} else if (!force && _version > data.vversion().v) {

View File

@ -54,7 +54,13 @@ public:
_title = title;
}
[[nodiscard]] TimeId recordStartDate() const {
return _recordStartDate;
return _recordStartDate.current();
}
[[nodiscard]] rpl::producer<TimeId> recordStartDateValue() const {
return _recordStartDate.value();
}
[[nodiscard]] rpl::producer<TimeId> recordStartDateChanges() const {
return _recordStartDate.changes();
}
void setPeer(not_null<PeerData*> peer);
@ -130,7 +136,7 @@ private:
base::Timer _speakingByActiveFinishTimer;
QString _nextOffset;
rpl::variable<int> _fullCount = 0;
TimeId _recordStartDate = 0;
rpl::variable<TimeId> _recordStartDate = 0;
base::flat_map<uint32, LastSpokeTimes> _unknownSpokenSsrcs;
base::flat_map<PeerId, LastSpokeTimes> _unknownSpokenPeerIds;