diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 29a7653973..ec98faa410 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "boxes/confirm_box.h" #include "core/application.h" +#include "data/data_document.h" #include "history/view/controls/history_view_voice_record_button.h" #include "lang/lang_keys.h" #include "mainwindow.h" @@ -60,14 +61,93 @@ enum class FilterType { .arg(decimalPart); } +[[nodiscard]] std::unique_ptr ProcessCaptureResult( + const ::Media::Capture::Result &data) { + auto voiceData = std::make_unique(); + voiceData->duration = Duration(data.samples); + voiceData->waveform = data.waveform; + voiceData->wavemax = voiceData->waveform.empty() + ? uchar(0) + : *ranges::max_element(voiceData->waveform); + return voiceData; +} + } // namespace +class ListenWrap final { +public: + ListenWrap( + not_null parent, + const ::Media::Capture::Result &data); + + void requestPaintProgress(float64 progress); + rpl::producer<> stopRequests() const; + + rpl::lifetime &lifetime(); + +private: + void init(); + + not_null _parent; + + const std::unique_ptr _voiceData; + const style::IconButton &_stDelete; + base::unique_qptr _delete; + + rpl::variable _showProgress = 0.; + + rpl::lifetime _lifetime; + +}; + +ListenWrap::ListenWrap( + not_null parent, + const ::Media::Capture::Result &data) +: _parent(parent) +, _voiceData(ProcessCaptureResult(data)) +, _stDelete(st::historyRecordDelete) +, _delete(base::make_unique_q(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 { public: RecordLock(not_null parent); void requestPaintProgress(float64 progress); + [[nodiscard]] rpl::producer<> stops() const; [[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] bool isLocked() const; @@ -102,10 +182,11 @@ RecordLock::RecordLock(not_null parent) } void RecordLock::init() { - setAttribute(Qt::WA_TransparentForMouseEvents); shownValue( ) | rpl::start_with_next([=](bool shown) { if (!shown) { + setCursor(style::cur_default); + setAttribute(Qt::WA_TransparentForMouseEvents, true); _lockAnimation.stop(); _lockEnderAnimation.stop(); _progress = 0.; @@ -129,8 +210,17 @@ void RecordLock::init() { locks( ) | rpl::start_with_next([=] { - const auto duration = st::historyRecordVoiceShowDuration; - _lockAnimation.start([=] { update(); }, 0., 1., duration); + const auto &duration = st::historyRecordVoiceShowDuration; + 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()); } @@ -282,6 +372,15 @@ rpl::producer<> RecordLock::locks() const { ) | rpl::filter([=] { return isLocked(); }) | rpl::to_empty; } +rpl::producer<> RecordLock::stops() const { + return events( + ) | rpl::filter([=](not_null e) { + return isLocked() + && (_lockAnimation.value(1.) == 1.) + && (e->type() == QEvent::MouseButtonRelease); + }) | rpl::to_empty; +} + VoiceRecordBar::VoiceRecordBar( not_null parent, not_null sectionWidget, @@ -313,7 +412,7 @@ VoiceRecordBar::VoiceRecordBar( VoiceRecordBar::~VoiceRecordBar() { if (isRecording()) { - stopRecording(false); + stopRecording(StopType::Cancel); } } @@ -395,6 +494,7 @@ void VoiceRecordBar::init() { } p.fillRect(clip, st::historyComposeAreaBg); + p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio())); if (clip.intersects(_messageRect)) { // The message should be painted first to avoid flickering. drawMessage(p, activeAnimationRatio()); @@ -430,6 +530,29 @@ void VoiceRecordBar::init() { _showLockAnimation.start(std::move(callback), from, to, duration); }, 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( ) | rpl::start_with_next([=] { installClickOutsideFilter(); @@ -504,7 +627,11 @@ void VoiceRecordBar::visibilityAnimate(bool show, Fn &&callback) { const auto from = show ? 0. : 1.; const auto duration = st::historyRecordVoiceShowDuration; auto animationCallback = [=, callback = std::move(callback)](auto value) { - _level->requestPaintProgress(value); + if (!_listen) { + _level->requestPaintProgress(value); + } else { + _listen->requestPaintProgress(value); + } update(); if ((show && value == 1.) || (!show && value == 0.)) { if (callback) { @@ -621,7 +748,7 @@ void VoiceRecordBar::stop(bool send) { auto disappearanceCallback = [=] { hide(); - stopRecording(send); + stopRecording(send ? StopType::Send : StopType::Cancel); }; _lockShowing = false; visibilityAnimate(false, std::move(disappearanceCallback)); @@ -637,6 +764,8 @@ void VoiceRecordBar::finish() { _showAnimation.stop(); + _listen = nullptr; + _sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 }); _controller->widget()->setInnerFocus(); } @@ -645,12 +774,12 @@ void VoiceRecordBar::hideFast() { hide(); _lock->hide(); _level->hide(); - stopRecording(false); + stopRecording(StopType::Cancel); } -void VoiceRecordBar::stopRecording(bool send) { +void VoiceRecordBar::stopRecording(StopType type) { using namespace ::Media::Capture; - if (!send) { + if (type == StopType::Cancel) { instance()->stop(); return; } @@ -661,7 +790,17 @@ void VoiceRecordBar::stopRecording(bool send) { Window::ActivateWindow(_controller); 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(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.setBrush(st::historyRecordVoiceFgInactive); - p.setOpacity(1. - _redCircleProgress); + const auto opacity = p.opacity(); + p.setOpacity(opacity * (1. - _redCircleProgress)); const int radii = st::historyRecordSignalRadius * showAnimationRatio(); const auto center = _redCircleRect.center() + QPoint(1, 1); p.drawEllipse(center, radii, radii); - p.setOpacity(1.); + p.setOpacity(opacity); } void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { @@ -761,6 +901,10 @@ float64 VoiceRecordBar::showAnimationRatio() const { return _showAnimation.value(1.); } +float64 VoiceRecordBar::showListenAnimationRatio() const { + return _showListenAnimation.value(_listen ? 1. : 0.); +} + void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { const auto localPos = mapFromGlobal(globalPos); const auto lower = _lock->height(); @@ -817,7 +961,7 @@ void VoiceRecordBar::installClickOutsideFilter() { } else if (type == QEvent::ContextMenu || type == QEvent::Shortcut) { return Type::ShowBox; } else if (type == QEvent::MouseButtonPress) { - return (noBox && !_inField.current()) + return (noBox && !_inField.current() && !_lock->underMouse()) ? Type::ShowBox : Type::Continue; } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h index 16b7447d37..fe4d50f86a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.h @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/rp_widget.h" +struct VoiceData; + namespace Ui { class SendButton; } // namespace Ui @@ -24,6 +26,7 @@ class SessionController; namespace HistoryView::Controls { class VoiceRecordButton; +class ListenWrap; class RecordLock; class VoiceRecordBar final : public Ui::RpWidget { @@ -64,6 +67,12 @@ public: [[nodiscard]] bool isLockPresent() const; private: + enum class StopType { + Cancel, + Send, + Listen, + }; + void init(); void updateMessageGeometry(); @@ -74,7 +83,7 @@ private: bool recordingAnimationCallback(crl::time now); void stop(bool send); - void stopRecording(bool send); + void stopRecording(StopType type); void visibilityAnimate(bool show, Fn &&callback); bool showRecordButton() const; @@ -92,6 +101,7 @@ private: void activeAnimate(bool active); float64 showAnimationRatio() const; + float64 showListenAnimationRatio() const; float64 activeAnimationRatio() const; void computeAndSetLockProgress(QPoint globalPos); @@ -101,6 +111,7 @@ private: const std::shared_ptr _send; const std::unique_ptr _lock; const std::unique_ptr _level; + std::unique_ptr _listen; base::Timer _startTimer; @@ -129,6 +140,7 @@ private: rpl::variable _lockShowing = false; Ui::Animations::Simple _showLockAnimation; + Ui::Animations::Simple _showListenAnimation; Ui::Animations::Simple _activeAnimation; Ui::Animations::Simple _showAnimation; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index b3a0f30ee3..e5792f13e5 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -381,6 +381,11 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }} historyRecordLockMargin: margins(4px, 4px, 4px, 4px); 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); historySilentToggle: IconButton(historyBotKeyboardShow) {