mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Show recording indicator.
This commit is contained in:
parent
50265afe93
commit
7edc91e29e
7 changed files with 146 additions and 80 deletions
|
@ -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}**.";
|
||||
|
||||
|
|
|
@ -560,6 +560,9 @@ groupCallTitleLabel: FlatLabel(groupCallSubtitleLabel) {
|
|||
}
|
||||
groupCallAddButtonPosition: point(10px, 7px);
|
||||
groupCallMembersWidthMax: 360px;
|
||||
groupCallRecordingMark: 6px;
|
||||
groupCallRecordingMarkSkip: 4px;
|
||||
groupCallRecordingMarkTop: 6px;
|
||||
|
||||
groupCallActiveButton: IconButton {
|
||||
width: 36px;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue