mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-08 08:04:08 +02:00
Added initial implementation of voice recording lock.
This commit is contained in:
parent
43635f6e4b
commit
478f5f671c
6 changed files with 278 additions and 16 deletions
|
@ -1341,6 +1341,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_broadcast_silent_ph" = "Silent broadcast...";
|
"lng_broadcast_silent_ph" = "Silent broadcast...";
|
||||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||||
"lng_record_cancel" = "Release outside this field to cancel";
|
"lng_record_cancel" = "Release outside this field to cancel";
|
||||||
|
"lng_record_lock_cancel" = "Click outside of microphone button to cancel";
|
||||||
|
"lng_record_lock_cancel_sure" = "Are you sure you want to stop recording and discard your voice message?";
|
||||||
|
"lng_record_lock_discard" = "Discard";
|
||||||
"lng_will_be_notified" = "Members will be notified when you post";
|
"lng_will_be_notified" = "Members will be notified when you post";
|
||||||
"lng_wont_be_notified" = "Members will not be notified when you post";
|
"lng_wont_be_notified" = "Members will not be notified when you post";
|
||||||
"lng_willbe_history" = "Please select a chat to start messaging";
|
"lng_willbe_history" = "Please select a chat to start messaging";
|
||||||
|
|
|
@ -717,6 +717,16 @@ void HistoryWidget::refreshTabbedPanel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::initVoiceRecordBar() {
|
void HistoryWidget::initVoiceRecordBar() {
|
||||||
|
{
|
||||||
|
auto scrollHeight = rpl::combine(
|
||||||
|
_scroll->topValue(),
|
||||||
|
_scroll->heightValue()
|
||||||
|
) | rpl::map([=](int top, int height) {
|
||||||
|
return top + height;
|
||||||
|
});
|
||||||
|
_voiceRecordBar->setLockBottom(std::move(scrollHeight));
|
||||||
|
}
|
||||||
|
|
||||||
_voiceRecordBar->startRecordingRequests(
|
_voiceRecordBar->startRecordingRequests(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
const auto error = _peer
|
const auto error = _peer
|
||||||
|
@ -756,6 +766,12 @@ void HistoryWidget::initVoiceRecordBar() {
|
||||||
data.duration,
|
data.duration,
|
||||||
action);
|
action);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_voiceRecordBar->lockShowStarts(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
updateHistoryDownVisibility();
|
||||||
|
updateUnreadMentionsVisibility();
|
||||||
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::initTabbedSelector() {
|
void HistoryWidget::initTabbedSelector() {
|
||||||
|
@ -4787,6 +4803,9 @@ void HistoryWidget::updateHistoryDownVisibility() {
|
||||||
if (!_list || _firstLoadRequest) {
|
if (!_list || _firstLoadRequest) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (_voiceRecordBar->isLockPresent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!_history->loadedAtBottom() || _replyReturn) {
|
if (!_history->loadedAtBottom() || _replyReturn) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -4830,6 +4849,9 @@ void HistoryWidget::updateUnreadMentionsVisibility() {
|
||||||
if (!showUnreadMentions || _firstLoadRequest) {
|
if (!showUnreadMentions || _firstLoadRequest) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (_voiceRecordBar->isLockPresent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!_history->getUnreadMentionsLoadedCount()) {
|
if (!_history->getUnreadMentionsLoadedCount()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ class ContactStatus;
|
||||||
class Element;
|
class Element;
|
||||||
class PinnedTracker;
|
class PinnedTracker;
|
||||||
namespace Controls {
|
namespace Controls {
|
||||||
|
class RecordLock;
|
||||||
class VoiceRecordBar;
|
class VoiceRecordBar;
|
||||||
} // namespace Controls
|
} // namespace Controls
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
@ -111,6 +112,7 @@ class HistoryWidget final : public Window::AbstractSectionWidget {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using FieldHistoryAction = Ui::InputField::HistoryAction;
|
using FieldHistoryAction = Ui::InputField::HistoryAction;
|
||||||
|
using RecordLock = HistoryView::Controls::RecordLock;
|
||||||
using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar;
|
using VoiceRecordBar = HistoryView::Controls::VoiceRecordBar;
|
||||||
|
|
||||||
HistoryWidget(
|
HistoryWidget(
|
||||||
|
|
|
@ -8,12 +8,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/controls/history_view_voice_record_bar.h"
|
#include "history/view/controls/history_view_voice_record_bar.h"
|
||||||
|
|
||||||
#include "api/api_send_progress.h"
|
#include "api/api_send_progress.h"
|
||||||
|
#include "base/event_filter.h"
|
||||||
|
#include "boxes/confirm_box.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "media/audio/media_audio_capture.h"
|
#include "media/audio/media_audio_capture.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
#include "ui/controls/send_button.h"
|
#include "ui/controls/send_button.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -25,6 +28,7 @@ namespace {
|
||||||
using SendActionUpdate = VoiceRecordBar::SendActionUpdate;
|
using SendActionUpdate = VoiceRecordBar::SendActionUpdate;
|
||||||
using VoiceToSend = VoiceRecordBar::VoiceToSend;
|
using VoiceToSend = VoiceRecordBar::VoiceToSend;
|
||||||
|
|
||||||
|
constexpr auto kLockDelay = crl::time(100);
|
||||||
constexpr auto kRecordingUpdateDelta = crl::time(100);
|
constexpr auto kRecordingUpdateDelta = crl::time(100);
|
||||||
constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes
|
constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes
|
||||||
constexpr auto kMaxSamples =
|
constexpr auto kMaxSamples =
|
||||||
|
@ -49,6 +53,77 @@ constexpr auto kPrecision = 10;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
class RecordLock final : public Ui::RpWidget {
|
||||||
|
public:
|
||||||
|
RecordLock(not_null<Ui::RpWidget*> parent);
|
||||||
|
|
||||||
|
void requestPaintProgress(float64 progress);
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<> locks() const;
|
||||||
|
[[nodiscard]] bool isLocked() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
|
||||||
|
Ui::Animations::Simple _lockAnimation;
|
||||||
|
|
||||||
|
rpl::variable<float64> _progress = 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
RecordLock::RecordLock(not_null<Ui::RpWidget*> parent) : RpWidget(parent) {
|
||||||
|
resize(st::historyRecordLockSize);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordLock::init() {
|
||||||
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
shownValue(
|
||||||
|
) | rpl::start_with_next([=](bool shown) {
|
||||||
|
if (!shown) {
|
||||||
|
_lockAnimation.stop();
|
||||||
|
_progress = 0.;
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=](const QRect &clip) {
|
||||||
|
Painter p(this);
|
||||||
|
if (isLocked()) {
|
||||||
|
const auto color = anim::color(
|
||||||
|
Qt::red,
|
||||||
|
Qt::green,
|
||||||
|
_lockAnimation.value(1.));
|
||||||
|
p.fillRect(clip, color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p.fillRect(clip, anim::color(Qt::blue, Qt::red, _progress.current()));
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
locks(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
const auto duration = st::historyRecordVoiceShowDuration * 3;
|
||||||
|
_lockAnimation.start([=] { update(); }, 0., 1., duration);
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecordLock::requestPaintProgress(float64 progress) {
|
||||||
|
if (isHidden() || isLocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_progress = progress;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RecordLock::isLocked() const {
|
||||||
|
return _progress.current() == 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> RecordLock::locks() const {
|
||||||
|
return _progress.changes(
|
||||||
|
) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty;
|
||||||
|
}
|
||||||
|
|
||||||
VoiceRecordBar::VoiceRecordBar(
|
VoiceRecordBar::VoiceRecordBar(
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
|
@ -56,8 +131,8 @@ VoiceRecordBar::VoiceRecordBar(
|
||||||
int recorderHeight)
|
int recorderHeight)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _controller(controller)
|
, _controller(controller)
|
||||||
, _wrap(std::make_unique<Ui::RpWidget>(parent))
|
|
||||||
, _send(send)
|
, _send(send)
|
||||||
|
, _lock(std::make_unique<RecordLock>(parent))
|
||||||
, _cancelFont(st::historyRecordFont)
|
, _cancelFont(st::historyRecordFont)
|
||||||
, _recordingAnimation([=](crl::time now) {
|
, _recordingAnimation([=](crl::time now) {
|
||||||
return recordingAnimationCallback(now);
|
return recordingAnimationCallback(now);
|
||||||
|
@ -94,7 +169,7 @@ void VoiceRecordBar::updateControlsGeometry(QSize size) {
|
||||||
+ _durationRect.width()
|
+ _durationRect.width()
|
||||||
+ ((_send->width() - st::historyRecordVoice.width()) / 2);
|
+ ((_send->width() - st::historyRecordVoice.width()) / 2);
|
||||||
const auto right = width() - _send->width();
|
const auto right = width() - _send->width();
|
||||||
const auto width = _cancelFont->width(tr::lng_record_cancel(tr::now));
|
const auto width = _cancelFont->width(cancelMessage());
|
||||||
_messageRect = QRect(
|
_messageRect = QRect(
|
||||||
left + (right - left - width) / 2,
|
left + (right - left - width) / 2,
|
||||||
st::historyRecordTextTop,
|
st::historyRecordTextTop,
|
||||||
|
@ -145,6 +220,44 @@ void VoiceRecordBar::init() {
|
||||||
) | rpl::start_with_next([=](bool value) {
|
) | rpl::start_with_next([=](bool value) {
|
||||||
activeAnimate(value);
|
activeAnimate(value);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_lockShowing.changes(
|
||||||
|
) | rpl::start_with_next([=](bool show) {
|
||||||
|
const auto to = show ? 1. : 0.;
|
||||||
|
const auto from = show ? 0. : 1.;
|
||||||
|
const auto duration = st::historyRecordLockShowDuration;
|
||||||
|
_lock->show();
|
||||||
|
auto callback = [=](auto value) {
|
||||||
|
const auto right = anim::interpolate(
|
||||||
|
-_lock->width(),
|
||||||
|
0,
|
||||||
|
value);
|
||||||
|
_lock->moveToRight(right, _lock->y());
|
||||||
|
if (value == 0. && !show) {
|
||||||
|
_lock->hide();
|
||||||
|
} else if (value == 1. && show) {
|
||||||
|
computeAndSetLockProgress(QCursor::pos());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_showLockAnimation.start(std::move(callback), from, to, duration);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
_lock->hide();
|
||||||
|
_lock->locks(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
|
||||||
|
updateControlsGeometry(rect().size());
|
||||||
|
update(_messageRect);
|
||||||
|
|
||||||
|
installClickOutsideFilter();
|
||||||
|
|
||||||
|
_send->clicks(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return _send->type() == Ui::SendButton::Type::Record;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
stop(true);
|
||||||
|
}, _recordingLifetime);
|
||||||
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceRecordBar::activeAnimate(bool active) {
|
void VoiceRecordBar::activeAnimate(bool active) {
|
||||||
|
@ -177,6 +290,14 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
|
||||||
_showAnimation.start(std::move(animationCallback), from, to, duration);
|
_showAnimation.start(std::move(animationCallback), from, to, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VoiceRecordBar::setLockBottom(rpl::producer<int> &&bottom) {
|
||||||
|
std::move(
|
||||||
|
bottom
|
||||||
|
) | rpl::start_with_next([=](int value) {
|
||||||
|
_lock->moveToLeft(_lock->x(), value - _lock->height());
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
void VoiceRecordBar::startRecording() {
|
void VoiceRecordBar::startRecording() {
|
||||||
auto appearanceCallback = [=] {
|
auto appearanceCallback = [=] {
|
||||||
Expects(!_showAnimation.animating());
|
Expects(!_showAnimation.animating());
|
||||||
|
@ -187,10 +308,17 @@ void VoiceRecordBar::startRecording() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto shown = _recordingLifetime.make_state<bool>(false);
|
||||||
|
|
||||||
_recording = true;
|
_recording = true;
|
||||||
instance()->start();
|
instance()->start();
|
||||||
instance()->updated(
|
instance()->updated(
|
||||||
) | rpl::start_with_next_error([=](const Update &update) {
|
) | rpl::start_with_next_error([=](const Update &update) {
|
||||||
|
if (!(*shown) && !_showAnimation.animating()) {
|
||||||
|
// Show the lock widget after the first successful update.
|
||||||
|
*shown = true;
|
||||||
|
_lockShowing = true;
|
||||||
|
}
|
||||||
recordUpdated(update.level, update.samples);
|
recordUpdated(update.level, update.samples);
|
||||||
}, [=] {
|
}, [=] {
|
||||||
stop(false);
|
stop(false);
|
||||||
|
@ -205,13 +333,20 @@ void VoiceRecordBar::startRecording() {
|
||||||
_send->events(
|
_send->events(
|
||||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
return isTypeRecord()
|
return isTypeRecord()
|
||||||
|
&& !_lock->isLocked()
|
||||||
&& (e->type() == QEvent::MouseMove
|
&& (e->type() == QEvent::MouseMove
|
||||||
|| e->type() == QEvent::MouseButtonRelease);
|
|| e->type() == QEvent::MouseButtonRelease);
|
||||||
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||||
const auto type = e->type();
|
const auto type = e->type();
|
||||||
if (type == QEvent::MouseMove) {
|
if (type == QEvent::MouseMove) {
|
||||||
const auto mouse = static_cast<QMouseEvent*>(e.get());
|
const auto mouse = static_cast<QMouseEvent*>(e.get());
|
||||||
_inField = rect().contains(mapFromGlobal(mouse->globalPos()));
|
const auto localPos = mapFromGlobal(mouse->globalPos());
|
||||||
|
_inField = rect().contains(localPos);
|
||||||
|
|
||||||
|
if (_showLockAnimation.animating()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
computeAndSetLockProgress(mouse->globalPos());
|
||||||
} else if (type == QEvent::MouseButtonRelease) {
|
} else if (type == QEvent::MouseButtonRelease) {
|
||||||
stop(_inField.current());
|
stop(_inField.current());
|
||||||
}
|
}
|
||||||
|
@ -266,6 +401,7 @@ void VoiceRecordBar::stop(bool send) {
|
||||||
|
|
||||||
_controller->widget()->setInnerFocus();
|
_controller->widget()->setInnerFocus();
|
||||||
};
|
};
|
||||||
|
_lockShowing = false;
|
||||||
visibilityAnimate(false, std::move(disappearanceCallback));
|
visibilityAnimate(false, std::move(disappearanceCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +452,7 @@ void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) {
|
||||||
p.drawText(
|
p.drawText(
|
||||||
_messageRect.x(),
|
_messageRect.x(),
|
||||||
_messageRect.y() + _cancelFont->ascent,
|
_messageRect.y() + _cancelFont->ascent,
|
||||||
tr::lng_record_cancel(tr::now));
|
cancelMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<SendActionUpdate> VoiceRecordBar::sendActionUpdates() const {
|
rpl::producer<SendActionUpdate> VoiceRecordBar::sendActionUpdates() const {
|
||||||
|
@ -340,10 +476,21 @@ rpl::producer<bool> VoiceRecordBar::recordingStateChanges() const {
|
||||||
return _recording.changes();
|
return _recording.changes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> VoiceRecordBar::lockShowStarts() const {
|
||||||
|
return _lockShowing.changes();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VoiceRecordBar::isLockPresent() const {
|
||||||
|
return _lockShowing.current();
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<> VoiceRecordBar::startRecordingRequests() const {
|
rpl::producer<> VoiceRecordBar::startRecordingRequests() const {
|
||||||
return _send->events(
|
return _send->events(
|
||||||
) | rpl::filter([=](not_null<QEvent*> e) {
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
return isTypeRecord() && (e->type() == QEvent::MouseButtonPress);
|
return isTypeRecord()
|
||||||
|
&& !_showAnimation.animating()
|
||||||
|
&& !_lock->isLocked()
|
||||||
|
&& (e->type() == QEvent::MouseButtonPress);
|
||||||
}) | rpl::to_empty;
|
}) | rpl::to_empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,4 +502,76 @@ float64 VoiceRecordBar::activeAnimationRatio() const {
|
||||||
return _activeAnimation.value(_inField.current() ? 1. : 0.);
|
return _activeAnimation.value(_inField.current() ? 1. : 0.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString VoiceRecordBar::cancelMessage() const {
|
||||||
|
return _lock->isLocked()
|
||||||
|
? tr::lng_record_lock_cancel(tr::now)
|
||||||
|
: tr::lng_record_cancel(tr::now);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
|
||||||
|
const auto localPos = mapFromGlobal(globalPos);
|
||||||
|
const auto lower = _lock->height();
|
||||||
|
const auto higher = 0;
|
||||||
|
const auto progress = localPos.y() / (float64)(higher - lower);
|
||||||
|
_lock->requestPaintProgress(std::clamp(progress, 0., 1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceRecordBar::installClickOutsideFilter() {
|
||||||
|
const auto box = _recordingLifetime.make_state<QPointer<ConfirmBox>>();
|
||||||
|
const auto showBox = [=] {
|
||||||
|
if (*box || _send->underMouse()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto sure = [=](Fn<void()> &&close) {
|
||||||
|
stop(false);
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
*box = Ui::show(Box<ConfirmBox>(
|
||||||
|
tr::lng_record_lock_cancel_sure(tr::now),
|
||||||
|
tr::lng_record_lock_discard(tr::now),
|
||||||
|
st::attentionBoxButton,
|
||||||
|
std::move(sure)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto computeResult = [=](not_null<QEvent*> e) {
|
||||||
|
using Result = base::EventFilterResult;
|
||||||
|
if (!_lock->isLocked()) {
|
||||||
|
return Result::Continue;
|
||||||
|
}
|
||||||
|
const auto type = e->type();
|
||||||
|
const auto noBox = !(*box);
|
||||||
|
if (type == QEvent::KeyPress) {
|
||||||
|
if (noBox) {
|
||||||
|
return Result::Cancel;
|
||||||
|
}
|
||||||
|
const auto key = static_cast<QKeyEvent*>(e.get())->key();
|
||||||
|
const auto cancelOrConfirmBox = (key == Qt::Key_Escape
|
||||||
|
|| (key == Qt::Key_Enter || key == Qt::Key_Return));
|
||||||
|
return cancelOrConfirmBox ? Result::Continue : Result::Cancel;
|
||||||
|
} else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) {
|
||||||
|
return Result::Cancel;
|
||||||
|
} else if (type == QEvent::MouseButtonPress) {
|
||||||
|
return (noBox && !_send->underMouse())
|
||||||
|
? Result::Cancel
|
||||||
|
: Result::Continue;
|
||||||
|
}
|
||||||
|
return Result::Continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto filterCallback = [=](not_null<QEvent*> e) {
|
||||||
|
const auto result = computeResult(e);
|
||||||
|
if (result == base::EventFilterResult::Cancel) {
|
||||||
|
showBox();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto filter = base::install_event_filter(
|
||||||
|
QCoreApplication::instance(),
|
||||||
|
std::move(filterCallback));
|
||||||
|
|
||||||
|
_recordingLifetime.make_state<base::unique_qptr<QObject>>(
|
||||||
|
std::move(filter));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace HistoryView::Controls
|
} // namespace HistoryView::Controls
|
||||||
|
|
|
@ -22,11 +22,20 @@ class SessionController;
|
||||||
|
|
||||||
namespace HistoryView::Controls {
|
namespace HistoryView::Controls {
|
||||||
|
|
||||||
|
class RecordLock;
|
||||||
|
|
||||||
class VoiceRecordBar final : public Ui::RpWidget {
|
class VoiceRecordBar final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
using SendActionUpdate = Controls::SendActionUpdate;
|
using SendActionUpdate = Controls::SendActionUpdate;
|
||||||
using VoiceToSend = Controls::VoiceToSend;
|
using VoiceToSend = Controls::VoiceToSend;
|
||||||
|
|
||||||
|
VoiceRecordBar(
|
||||||
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
std::shared_ptr<Ui::SendButton> send,
|
||||||
|
int recorderHeight);
|
||||||
|
~VoiceRecordBar();
|
||||||
|
|
||||||
void startRecording();
|
void startRecording();
|
||||||
void finishAnimating();
|
void finishAnimating();
|
||||||
|
|
||||||
|
@ -34,15 +43,12 @@ public:
|
||||||
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
|
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
|
||||||
[[nodiscard]] rpl::producer<bool> recordingStateChanges() const;
|
[[nodiscard]] rpl::producer<bool> recordingStateChanges() const;
|
||||||
[[nodiscard]] rpl::producer<> startRecordingRequests() const;
|
[[nodiscard]] rpl::producer<> startRecordingRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<bool> lockShowStarts() const;
|
||||||
|
|
||||||
|
void setLockBottom(rpl::producer<int> &&bottom);
|
||||||
|
|
||||||
[[nodiscard]] bool isRecording() const;
|
[[nodiscard]] bool isRecording() const;
|
||||||
|
[[nodiscard]] bool isLockPresent() const;
|
||||||
VoiceRecordBar(
|
|
||||||
not_null<Ui::RpWidget*> parent,
|
|
||||||
not_null<Window::SessionController*> controller,
|
|
||||||
std::shared_ptr<Ui::SendButton> send,
|
|
||||||
int recorderHeight);
|
|
||||||
~VoiceRecordBar();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init();
|
void init();
|
||||||
|
@ -58,23 +64,26 @@ private:
|
||||||
void stopRecording(bool send);
|
void stopRecording(bool send);
|
||||||
void visibilityAnimate(bool show, Fn<void()> &&callback);
|
void visibilityAnimate(bool show, Fn<void()> &&callback);
|
||||||
|
|
||||||
void recordStopCallback(bool active);
|
|
||||||
void recordUpdateCallback(QPoint globalPos);
|
|
||||||
|
|
||||||
bool showRecordButton() const;
|
bool showRecordButton() const;
|
||||||
void drawDuration(Painter &p);
|
void drawDuration(Painter &p);
|
||||||
void drawRecording(Painter &p);
|
void drawRecording(Painter &p);
|
||||||
void drawMessage(Painter &p, float64 recordActive);
|
void drawMessage(Painter &p, float64 recordActive);
|
||||||
void updateOverStates(QPoint pos);
|
void updateOverStates(QPoint pos);
|
||||||
|
|
||||||
|
void installClickOutsideFilter();
|
||||||
|
|
||||||
bool isTypeRecord() const;
|
bool isTypeRecord() const;
|
||||||
|
|
||||||
void activeAnimate(bool active);
|
void activeAnimate(bool active);
|
||||||
float64 activeAnimationRatio() const;
|
float64 activeAnimationRatio() const;
|
||||||
|
|
||||||
|
void computeAndSetLockProgress(QPoint globalPos);
|
||||||
|
|
||||||
|
QString cancelMessage() const;
|
||||||
|
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
const std::unique_ptr<Ui::RpWidget> _wrap;
|
|
||||||
const std::shared_ptr<Ui::SendButton> _send;
|
const std::shared_ptr<Ui::SendButton> _send;
|
||||||
|
const std::unique_ptr<RecordLock> _lock;
|
||||||
|
|
||||||
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
|
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
|
||||||
rpl::event_stream<VoiceToSend> _sendVoiceRequests;
|
rpl::event_stream<VoiceToSend> _sendVoiceRequests;
|
||||||
|
@ -92,6 +101,10 @@ private:
|
||||||
|
|
||||||
rpl::lifetime _recordingLifetime;
|
rpl::lifetime _recordingLifetime;
|
||||||
|
|
||||||
|
rpl::variable<bool> _lockShowing = false;
|
||||||
|
|
||||||
|
Ui::Animations::Simple _showLockAnimation;
|
||||||
|
|
||||||
// This can animate for a very long time (like in music playing),
|
// This can animate for a very long time (like in music playing),
|
||||||
// so it should be a Basic, not a Simple animation.
|
// so it should be a Basic, not a Simple animation.
|
||||||
Ui::Animations::Basic _recordingAnimation;
|
Ui::Animations::Basic _recordingAnimation;
|
||||||
|
|
|
@ -344,6 +344,9 @@ historyRecordDurationSkip: 12px;
|
||||||
historyRecordDurationFg: historyComposeAreaFg;
|
historyRecordDurationFg: historyComposeAreaFg;
|
||||||
historyRecordTextTop: 14px;
|
historyRecordTextTop: 14px;
|
||||||
|
|
||||||
|
historyRecordLockShowDuration: historyToDownDuration;
|
||||||
|
historyRecordLockSize: size(50px, 150px);
|
||||||
|
|
||||||
historySilentToggle: IconButton(historyBotKeyboardShow) {
|
historySilentToggle: IconButton(historyBotKeyboardShow) {
|
||||||
icon: icon {{ "send_control_silent_off", historyComposeIconFg }};
|
icon: icon {{ "send_control_silent_off", historyComposeIconFg }};
|
||||||
iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }};
|
iconOver: icon {{ "send_control_silent_off", historyComposeIconFgOver }};
|
||||||
|
|
Loading…
Add table
Reference in a new issue