Added initial ability to delete recorded voice data.

This commit is contained in:
23rd 2020-11-06 21:29:51 +03:00 committed by John Preston
parent 131c2e1c56
commit 647cbc5464
3 changed files with 175 additions and 14 deletions

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h" #include "base/event_filter.h"
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
#include "core/application.h" #include "core/application.h"
#include "data/data_document.h"
#include "history/view/controls/history_view_voice_record_button.h" #include "history/view/controls/history_view_voice_record_button.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -60,14 +61,93 @@ enum class FilterType {
.arg(decimalPart); .arg(decimalPart);
} }
[[nodiscard]] std::unique_ptr<VoiceData> ProcessCaptureResult(
const ::Media::Capture::Result &data) {
auto voiceData = std::make_unique<VoiceData>();
voiceData->duration = Duration(data.samples);
voiceData->waveform = data.waveform;
voiceData->wavemax = voiceData->waveform.empty()
? uchar(0)
: *ranges::max_element(voiceData->waveform);
return voiceData;
}
} // namespace } // namespace
class ListenWrap final {
public:
ListenWrap(
not_null<Ui::RpWidget*> parent,
const ::Media::Capture::Result &data);
void requestPaintProgress(float64 progress);
rpl::producer<> stopRequests() const;
rpl::lifetime &lifetime();
private:
void init();
not_null<Ui::RpWidget*> _parent;
const std::unique_ptr<VoiceData> _voiceData;
const style::IconButton &_stDelete;
base::unique_qptr<Ui::IconButton> _delete;
rpl::variable<float64> _showProgress = 0.;
rpl::lifetime _lifetime;
};
ListenWrap::ListenWrap(
not_null<Ui::RpWidget*> parent,
const ::Media::Capture::Result &data)
: _parent(parent)
, _voiceData(ProcessCaptureResult(data))
, _stDelete(st::historyRecordDelete)
, _delete(base::make_unique_q<Ui::IconButton>(parent, _stDelete)) {
init();
}
void ListenWrap::init() {
auto deleteShow = _showProgress.value(
) | rpl::map([](auto value) {
return value == 1.;
}) | rpl::distinct_until_changed();
_delete->showOn(std::move(deleteShow));
_parent->paintRequest(
) | rpl::start_with_next([=](const QRect &clip) {
const auto progress = _showProgress.current();
if (progress == 0. || progress == 1.) {
return;
}
Painter p(_parent);
p.setOpacity(progress);
_stDelete.icon.paint(p, _stDelete.iconPosition, _parent->width());
}, _lifetime);
}
void ListenWrap::requestPaintProgress(float64 progress) {
_showProgress = progress;
}
rpl::producer<> ListenWrap::stopRequests() const {
return _delete->clicks() | rpl::to_empty;
}
rpl::lifetime &ListenWrap::lifetime() {
return _lifetime;
}
class RecordLock final : public Ui::RpWidget { class RecordLock final : public Ui::RpWidget {
public: public:
RecordLock(not_null<Ui::RpWidget*> parent); RecordLock(not_null<Ui::RpWidget*> parent);
void requestPaintProgress(float64 progress); void requestPaintProgress(float64 progress);
[[nodiscard]] rpl::producer<> stops() const;
[[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] rpl::producer<> locks() const;
[[nodiscard]] bool isLocked() const; [[nodiscard]] bool isLocked() const;
@ -102,10 +182,11 @@ RecordLock::RecordLock(not_null<Ui::RpWidget*> parent)
} }
void RecordLock::init() { void RecordLock::init() {
setAttribute(Qt::WA_TransparentForMouseEvents);
shownValue( shownValue(
) | rpl::start_with_next([=](bool shown) { ) | rpl::start_with_next([=](bool shown) {
if (!shown) { if (!shown) {
setCursor(style::cur_default);
setAttribute(Qt::WA_TransparentForMouseEvents, true);
_lockAnimation.stop(); _lockAnimation.stop();
_lockEnderAnimation.stop(); _lockEnderAnimation.stop();
_progress = 0.; _progress = 0.;
@ -129,8 +210,17 @@ void RecordLock::init() {
locks( locks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto duration = st::historyRecordVoiceShowDuration; const auto &duration = st::historyRecordVoiceShowDuration;
_lockAnimation.start([=] { update(); }, 0., 1., duration); const auto from = 0.;
const auto to = 1.;
auto callback = [=](auto value) {
update();
if (value == to) {
setCursor(style::cur_pointer);
setAttribute(Qt::WA_TransparentForMouseEvents, false);
}
};
_lockAnimation.start(std::move(callback), from, to, duration);
}, lifetime()); }, lifetime());
} }
@ -282,6 +372,15 @@ rpl::producer<> RecordLock::locks() const {
) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty; ) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty;
} }
rpl::producer<> RecordLock::stops() const {
return events(
) | rpl::filter([=](not_null<QEvent*> e) {
return isLocked()
&& (_lockAnimation.value(1.) == 1.)
&& (e->type() == QEvent::MouseButtonRelease);
}) | rpl::to_empty;
}
VoiceRecordBar::VoiceRecordBar( VoiceRecordBar::VoiceRecordBar(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
not_null<Ui::RpWidget*> sectionWidget, not_null<Ui::RpWidget*> sectionWidget,
@ -313,7 +412,7 @@ VoiceRecordBar::VoiceRecordBar(
VoiceRecordBar::~VoiceRecordBar() { VoiceRecordBar::~VoiceRecordBar() {
if (isRecording()) { if (isRecording()) {
stopRecording(false); stopRecording(StopType::Cancel);
} }
} }
@ -395,6 +494,7 @@ void VoiceRecordBar::init() {
} }
p.fillRect(clip, st::historyComposeAreaBg); p.fillRect(clip, st::historyComposeAreaBg);
p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio()));
if (clip.intersects(_messageRect)) { if (clip.intersects(_messageRect)) {
// The message should be painted first to avoid flickering. // The message should be painted first to avoid flickering.
drawMessage(p, activeAnimationRatio()); drawMessage(p, activeAnimationRatio());
@ -430,6 +530,29 @@ void VoiceRecordBar::init() {
_showLockAnimation.start(std::move(callback), from, to, duration); _showLockAnimation.start(std::move(callback), from, to, duration);
}, lifetime()); }, lifetime());
_lock->stops(
) | rpl::start_with_next([=] {
::Media::Capture::instance()->startedChanges(
) | rpl::filter([](auto capturing) {
return !capturing;
}) | rpl::take(1) | rpl::start_with_next([=] {
Assert(_listen != nullptr);
_lockShowing = false;
const auto to = 1.;
const auto &duration = st::historyRecordVoiceShowDuration;
auto callback = [=](auto value) {
_listen->requestPaintProgress(value);
_level->requestPaintProgress(to - value);
update();
};
_showListenAnimation.start(std::move(callback), 0., to, duration);
}, lifetime());
stopRecording(StopType::Listen);
}, lifetime());
_lock->locks( _lock->locks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
installClickOutsideFilter(); installClickOutsideFilter();
@ -504,7 +627,11 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
const auto from = show ? 0. : 1.; const auto from = show ? 0. : 1.;
const auto duration = st::historyRecordVoiceShowDuration; const auto duration = st::historyRecordVoiceShowDuration;
auto animationCallback = [=, callback = std::move(callback)](auto value) { auto animationCallback = [=, callback = std::move(callback)](auto value) {
_level->requestPaintProgress(value); if (!_listen) {
_level->requestPaintProgress(value);
} else {
_listen->requestPaintProgress(value);
}
update(); update();
if ((show && value == 1.) || (!show && value == 0.)) { if ((show && value == 1.) || (!show && value == 0.)) {
if (callback) { if (callback) {
@ -621,7 +748,7 @@ void VoiceRecordBar::stop(bool send) {
auto disappearanceCallback = [=] { auto disappearanceCallback = [=] {
hide(); hide();
stopRecording(send); stopRecording(send ? StopType::Send : StopType::Cancel);
}; };
_lockShowing = false; _lockShowing = false;
visibilityAnimate(false, std::move(disappearanceCallback)); visibilityAnimate(false, std::move(disappearanceCallback));
@ -637,6 +764,8 @@ void VoiceRecordBar::finish() {
_showAnimation.stop(); _showAnimation.stop();
_listen = nullptr;
_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 });
_controller->widget()->setInnerFocus(); _controller->widget()->setInnerFocus();
} }
@ -645,12 +774,12 @@ void VoiceRecordBar::hideFast() {
hide(); hide();
_lock->hide(); _lock->hide();
_level->hide(); _level->hide();
stopRecording(false); stopRecording(StopType::Cancel);
} }
void VoiceRecordBar::stopRecording(bool send) { void VoiceRecordBar::stopRecording(StopType type) {
using namespace ::Media::Capture; using namespace ::Media::Capture;
if (!send) { if (type == StopType::Cancel) {
instance()->stop(); instance()->stop();
return; return;
} }
@ -661,7 +790,17 @@ void VoiceRecordBar::stopRecording(bool send) {
Window::ActivateWindow(_controller); Window::ActivateWindow(_controller);
const auto duration = Duration(data.samples); const auto duration = Duration(data.samples);
_sendVoiceRequests.fire({ data.bytes, data.waveform, duration }); if (type == StopType::Send) {
_sendVoiceRequests.fire({ data.bytes, data.waveform, duration });
} else if (type == StopType::Listen) {
_listen = std::make_unique<ListenWrap>(this, data);
_listen->stopRequests(
) | rpl::take(1) | rpl::start_with_next([=] {
visibilityAnimate(false, [=] { hide(); });
}, _listen->lifetime());
_lockShowing = false;
}
})); }));
} }
@ -693,11 +832,12 @@ void VoiceRecordBar::drawRedCircle(Painter &p) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::historyRecordVoiceFgInactive); p.setBrush(st::historyRecordVoiceFgInactive);
p.setOpacity(1. - _redCircleProgress); const auto opacity = p.opacity();
p.setOpacity(opacity * (1. - _redCircleProgress));
const int radii = st::historyRecordSignalRadius * showAnimationRatio(); const int radii = st::historyRecordSignalRadius * showAnimationRatio();
const auto center = _redCircleRect.center() + QPoint(1, 1); const auto center = _redCircleRect.center() + QPoint(1, 1);
p.drawEllipse(center, radii, radii); p.drawEllipse(center, radii, radii);
p.setOpacity(1.); p.setOpacity(opacity);
} }
void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) {
@ -761,6 +901,10 @@ float64 VoiceRecordBar::showAnimationRatio() const {
return _showAnimation.value(1.); return _showAnimation.value(1.);
} }
float64 VoiceRecordBar::showListenAnimationRatio() const {
return _showListenAnimation.value(_listen ? 1. : 0.);
}
void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
const auto localPos = mapFromGlobal(globalPos); const auto localPos = mapFromGlobal(globalPos);
const auto lower = _lock->height(); const auto lower = _lock->height();
@ -817,7 +961,7 @@ void VoiceRecordBar::installClickOutsideFilter() {
} else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) {
return Type::ShowBox; return Type::ShowBox;
} else if (type == QEvent::MouseButtonPress) { } else if (type == QEvent::MouseButtonPress) {
return (noBox && !_inField.current()) return (noBox && !_inField.current() && !_lock->underMouse())
? Type::ShowBox ? Type::ShowBox
: Type::Continue; : Type::Continue;
} }

View file

@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
struct VoiceData;
namespace Ui { namespace Ui {
class SendButton; class SendButton;
} // namespace Ui } // namespace Ui
@ -24,6 +26,7 @@ class SessionController;
namespace HistoryView::Controls { namespace HistoryView::Controls {
class VoiceRecordButton; class VoiceRecordButton;
class ListenWrap;
class RecordLock; class RecordLock;
class VoiceRecordBar final : public Ui::RpWidget { class VoiceRecordBar final : public Ui::RpWidget {
@ -64,6 +67,12 @@ public:
[[nodiscard]] bool isLockPresent() const; [[nodiscard]] bool isLockPresent() const;
private: private:
enum class StopType {
Cancel,
Send,
Listen,
};
void init(); void init();
void updateMessageGeometry(); void updateMessageGeometry();
@ -74,7 +83,7 @@ private:
bool recordingAnimationCallback(crl::time now); bool recordingAnimationCallback(crl::time now);
void stop(bool send); void stop(bool send);
void stopRecording(bool send); void stopRecording(StopType type);
void visibilityAnimate(bool show, Fn<void()> &&callback); void visibilityAnimate(bool show, Fn<void()> &&callback);
bool showRecordButton() const; bool showRecordButton() const;
@ -92,6 +101,7 @@ private:
void activeAnimate(bool active); void activeAnimate(bool active);
float64 showAnimationRatio() const; float64 showAnimationRatio() const;
float64 showListenAnimationRatio() const;
float64 activeAnimationRatio() const; float64 activeAnimationRatio() const;
void computeAndSetLockProgress(QPoint globalPos); void computeAndSetLockProgress(QPoint globalPos);
@ -101,6 +111,7 @@ private:
const std::shared_ptr<Ui::SendButton> _send; const std::shared_ptr<Ui::SendButton> _send;
const std::unique_ptr<RecordLock> _lock; const std::unique_ptr<RecordLock> _lock;
const std::unique_ptr<VoiceRecordButton> _level; const std::unique_ptr<VoiceRecordButton> _level;
std::unique_ptr<ListenWrap> _listen;
base::Timer _startTimer; base::Timer _startTimer;
@ -129,6 +140,7 @@ private:
rpl::variable<bool> _lockShowing = false; rpl::variable<bool> _lockShowing = false;
Ui::Animations::Simple _showLockAnimation; Ui::Animations::Simple _showLockAnimation;
Ui::Animations::Simple _showListenAnimation;
Ui::Animations::Simple _activeAnimation; Ui::Animations::Simple _activeAnimation;
Ui::Animations::Simple _showAnimation; Ui::Animations::Simple _showAnimation;

View file

@ -381,6 +381,11 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}
historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
historyRecordDelete: IconButton(historyAttach) {
icon: icon {{ "info_media_delete", historyComposeIconFg }};
iconOver: icon {{ "info_media_delete", historyComposeIconFgOver }};
}
historyRecordLockPosition: point(7px, 35px); historyRecordLockPosition: point(7px, 35px);
historySilentToggle: IconButton(historyBotKeyboardShow) { historySilentToggle: IconButton(historyBotKeyboardShow) {