diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index ba786876f..3d40ef02d 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -139,6 +139,29 @@ SendButton { sendDisabledFg: color; } +RecordBarLock { + ripple: RippleAnimation; + originTop: icon; + originBottom: icon; + originBody: icon; + shadowTop: icon; + shadowBottom: icon; + shadowBody: icon; + arrow: icon; + fg: color; +} + +RecordBar { + radius: pixels; + bg: color; + durationFg: color; + cancel: color; + cancelActive: color; + cancelRipple: RippleAnimation; + lock: RecordBarLock; + remove: IconButton; +} + ComposeControls { bg: color; radius: pixels; @@ -149,6 +172,7 @@ ComposeControls { emoji: EmojiButton; suggestions: EmojiSuggestions; tabbed: EmojiPan; + record: RecordBar; } switchPmButton: RoundButton(defaultBoxButton) { @@ -875,7 +899,6 @@ historyRecordTextRight: 25px; historyRecordLockShowDuration: historyToDownDuration; historyRecordLockSize: size(75px, 133px); -historyRecordLockIconFg: historyToDownFg; historyRecordLockIconSize: size(14px, 17px); historyRecordLockIconBottomHeight: 9px; historyRecordLockIconLineHeight: 2px; @@ -916,6 +939,29 @@ historySilentToggle: IconButton(historyBotKeyboardShow) { historySilentToggleOn: icon {{ "chat/input_silent_on", historyComposeIconFg }}; historySilentToggleOnOver: icon {{ "chat/input_silent_on", historyComposeIconFgOver }}; +defaultRecordBarLock: RecordBarLock { + ripple: defaultRippleAnimation; + originTop: historyRecordLockTop; + originBottom: historyRecordLockBottom; + originBody: historyRecordLockBody; + shadowTop: historyRecordLockTopShadow; + shadowBottom: historyRecordLockBottomShadow; + shadowBody: historyRecordLockBodyShadow; + arrow: historyRecordLockArrow; + fg: historyToDownFg; +} +defaultRecordBar: RecordBar { + bg: historyComposeAreaBg; + durationFg: historyRecordDurationFg; + cancel: historyRecordCancel; + cancelActive: historyRecordCancelActive; + cancelRipple: RippleAnimation(defaultRippleAnimation) { + color: lightButtonBgRipple; + } + lock: defaultRecordBarLock; + remove: historyRecordDelete; +} + historySend: SendButton { inner: IconButton(historyAttach) { icon: historySendIcon; @@ -936,4 +982,5 @@ defaultComposeControls: ComposeControls { emoji: historyAttachEmoji; suggestions: defaultEmojiSuggestions; tabbed: defaultEmojiPan; + record: defaultRecordBar; } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index ec4aa4d07..c6e81a086 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -943,18 +943,6 @@ void HistoryWidget::refreshTabbedPanel() { } void HistoryWidget::initVoiceRecordBar() { - { - auto scrollHeight = rpl::combine( - _scroll->topValue(), - _scroll->heightValue() - ) | rpl::map([](int top, int height) { - return top + height - st::historyRecordLockPosition.y(); - }); - _voiceRecordBar->setLockBottom(std::move(scrollHeight)); - } - - _voiceRecordBar->setSendButtonGeometryValue(_send->geometryValue()); - _voiceRecordBar->setStartRecordingFilter([=] { const auto error = [&]() -> std::optional { if (_peer) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 1f4fb79b0..50fe6c136 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -989,10 +989,14 @@ ComposeControls::ComposeControls( , _header(std::make_unique(_wrap.get(), _show)) , _voiceRecordBar(std::make_unique( _wrap.get(), - parent, - _show, - _send, - st::historySendSize.height())) + Controls::VoiceRecordBarDescriptor{ + .outerContainer = parent, + .show = _show, + .send = _send, + .stOverride = &_st.record, + .recorderHeight = st::historySendSize.height(), + .lockFromBottom = descriptor.voiceLockFromBottom, + })) , _sendMenuType(descriptor.sendMenuType) , _unavailableEmojiPasted(std::move(descriptor.unavailableEmojiPasted)) , _saveDraftTimer([=] { saveDraft(); }) @@ -2278,26 +2282,6 @@ void ComposeControls::initVoiceRecordBar() { return false; }); - { - auto geometry = rpl::merge( - _wrap->geometryValue(), - _send->geometryValue() - ) | rpl::map([=](QRect geometry) { - auto r = _send->geometry(); - r.setY(r.y() + _wrap->y()); - return r; - }); - _voiceRecordBar->setSendButtonGeometryValue(std::move(geometry)); - } - - { - auto bottom = _wrap->geometryValue( - ) | rpl::map([=](QRect geometry) { - return geometry.y() - st::historyRecordLockPosition.y(); - }); - _voiceRecordBar->setLockBottom(std::move(bottom)); - } - _voiceRecordBar->updateSendButtonTypeRequests( ) | rpl::start_with_next([=] { updateSendButtonType(); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 223940671..d0696bef6 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -102,6 +102,7 @@ struct ComposeControlsDescriptor { SendMenu::Type sendMenuType = {}; Window::SessionController *regularWindow = nullptr; rpl::producer stickerOrEmojiChosen; + bool voiceLockFromBottom = false; ChatHelpers::ComposeFeatures features; }; 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 b80310d3b..63e4f6ee8 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 @@ -27,17 +27,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_capture.h" #include "media/player/media_player_button.h" #include "media/player/media_player_instance.h" -#include "styles/style_chat.h" -#include "styles/style_layers.h" -#include "styles/style_media_player.h" #include "ui/controls/send_button.h" #include "ui/effects/animation_value.h" #include "ui/effects/ripple_animation.h" #include "ui/text/format_values.h" #include "ui/painter.h" +#include "styles/style_chat.h" +#include "styles/style_chat_helpers.h" +#include "styles/style_layers.h" +#include "styles/style_media_player.h" namespace HistoryView::Controls { - namespace { using SendActionUpdate = VoiceRecordBar::SendActionUpdate; @@ -206,6 +206,7 @@ class ListenWrap final { public: ListenWrap( not_null parent, + const style::RecordBar &st, not_null session, ::Media::Capture::Result &&data, const style::font &font); @@ -231,12 +232,12 @@ private: not_null _parent; + const style::RecordBar &_st; const not_null _session; const not_null _document; const std::unique_ptr _voiceData; const std::shared_ptr _mediaView; const std::unique_ptr<::Media::Capture::Result> _data; - const style::IconButton &_stDelete; const base::unique_qptr _delete; const style::font &_durationFont; const QString _duration; @@ -264,17 +265,18 @@ private: ListenWrap::ListenWrap( not_null parent, + const style::RecordBar &st, not_null session, ::Media::Capture::Result &&data, const style::font &font) : _parent(parent) +, _st(st) , _session(session) , _document(DummyDocument(&session->data())) , _voiceData(ProcessCaptureResult(data)) , _mediaView(_document->createMediaView()) , _data(std::make_unique<::Media::Capture::Result>(std::move(data))) -, _stDelete(st::historyRecordDelete) -, _delete(base::make_unique_q(parent, _stDelete)) +, _delete(base::make_unique_q(parent, _st.remove)) , _durationFont(font) , _duration(Ui::FormatDurationText( float64(_data->samples) / ::Media::Player::kDefaultFrequency)) @@ -299,7 +301,7 @@ void ListenWrap::init() { _waveformBgRect = QRect({ 0, 0 }, size) .marginsRemoved(st::historyRecordWaveformBgMargins); { - const auto m = _stDelete.width + _waveformBgRect.height() / 2; + const auto m = _st.remove.width + _waveformBgRect.height() / 2; _waveformBgFinalCenterRect = _waveformBgRect.marginsRemoved( style::margins(m, 0, m, 0)); } @@ -319,22 +321,23 @@ void ListenWrap::init() { PainterHighQualityEnabler hq(p); const auto progress = _showProgress.current(); p.setOpacity(progress); + const auto &remove = _st.remove; if (progress > 0. && progress < 1.) { - _stDelete.icon.paint(p, _stDelete.iconPosition, _parent->width()); + remove.icon.paint(p, remove.iconPosition, _parent->width()); } { const auto hideOffset = _isShowAnimation ? 0 : anim::interpolate(kHideWaveformBgOffset, 0, progress); - const auto deleteIconLeft = _stDelete.iconPosition.x(); + const auto deleteIconLeft = remove.iconPosition.x(); const auto bgRectRight = anim::interpolate( deleteIconLeft, - _stDelete.width, + remove.width, _isShowAnimation ? progress : 1.); const auto bgRectLeft = anim::interpolate( _parent->width() - deleteIconLeft - _waveformBgRect.height(), - _stDelete.width, + remove.width, _isShowAnimation ? progress : 1.); const auto bgRectMargins = style::margins( bgRectLeft - hideOffset, @@ -357,7 +360,7 @@ void ListenWrap::init() { p.setOpacity(progress); } p.setPen(Qt::NoPen); - p.setBrush(st::historyRecordCancelActive); + p.setBrush(_st.cancelActive); QPainterPath path; path.setFillRule(Qt::WindingFill); path.addEllipse(bgLeftCircleRect); @@ -605,10 +608,13 @@ rpl::lifetime &ListenWrap::lifetime() { class RecordLock final : public Ui::RippleButton { public: - RecordLock(not_null parent); + RecordLock( + not_null parent, + const style::RecordBarLock &st); void requestPaintProgress(float64 progress); void requestPaintLockToStopProgress(float64 progress); + void setVisibleTopPart(int part); [[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] bool isLocked() const; @@ -627,6 +633,7 @@ private: void setProgress(float64 progress); void startLockingAnimation(float64 to); + const style::RecordBarLock &_st; const QRect _rippleRect; const QPen _arcPen; @@ -634,10 +641,15 @@ private: float64 _lockToStopProgress = 0.; rpl::variable _progress = 0.; + int _visibleTopPart = -1; + }; -RecordLock::RecordLock(not_null parent) -: RippleButton(parent, st::defaultRippleAnimation) +RecordLock::RecordLock( + not_null parent, + const style::RecordBarLock &st) +: RippleButton(parent, st.ripple) +, _st(st) , _rippleRect(QRect( 0, 0, @@ -653,6 +665,10 @@ RecordLock::RecordLock(not_null parent) init(); } +void RecordLock::setVisibleTopPart(int part) { + _visibleTopPart = part; +} + void RecordLock::init() { shownValue( ) | rpl::start_with_next([=](bool shown) { @@ -670,7 +686,13 @@ void RecordLock::init() { paintRequest( ) | rpl::start_with_next([=](const QRect &clip) { + if (!_visibleTopPart) { + return; + } Painter p(this); + if (_visibleTopPart > 0 && _visibleTopPart < height()) { + p.setClipRect(0, 0, width(), _visibleTopPart); + } if (isLocked()) { const auto top = anim::interpolate( 0, @@ -687,12 +709,12 @@ void RecordLock::init() { void RecordLock::drawProgress(Painter &p) { const auto progress = _progress.current(); - const auto &originTop = st::historyRecordLockTop; - const auto &originBottom = st::historyRecordLockBottom; - const auto &originBody = st::historyRecordLockBody; - const auto &shadowTop = st::historyRecordLockTopShadow; - const auto &shadowBottom = st::historyRecordLockBottomShadow; - const auto &shadowBody = st::historyRecordLockBodyShadow; + const auto &originTop = _st.originTop; + const auto &originBottom = _st.originBottom; + const auto &originBody = _st.originBody; + const auto &shadowTop = _st.shadowTop; + const auto &shadowBottom = _st.shadowBottom; + const auto &shadowBody = _st.shadowBody; const auto &shadowMargins = st::historyRecordLockMargin; const auto bottomMargin = anim::interpolate( @@ -739,7 +761,7 @@ void RecordLock::drawProgress(Painter &p) { originBody.fill(p, content); } { - const auto &arrow = st::historyRecordLockArrow; + const auto &arrow = _st.arrow; const auto arrowRect = QRect( inner.x(), content.y() + content.height() - arrow.height() / 2, @@ -791,7 +813,7 @@ void RecordLock::drawProgress(Painter &p) { PainterHighQualityEnabler hq(p); p.translate(inner.topLeft() + lockTranslation); p.setPen(Qt::NoPen); - p.setBrush(st::historyRecordLockIconFg); + p.setBrush(_st.fg); p.drawRoundedRect(blockRect, xRadius, 3); } else { // Paint an animation frame. @@ -844,7 +866,7 @@ void RecordLock::drawProgress(Painter &p) { p.drawImage( inner.topLeft(), - style::colorizeImage(frame, st::historyRecordLockIconFg)); + style::colorizeImage(frame, _st.fg)); } } } @@ -914,7 +936,10 @@ QPoint RecordLock::prepareRippleStartPosition() const { class CancelButton final : public Ui::RippleButton { public: - CancelButton(not_null parent, int height); + CancelButton( + not_null parent, + const style::RecordBar &st, + int height); void requestPaintProgress(float64 progress); @@ -925,6 +950,7 @@ protected: private: void init(); + const style::RecordBar &_st; const int _width; const QRect _rippleRect; @@ -934,8 +960,12 @@ private: }; -CancelButton::CancelButton(not_null parent, int height) -: Ui::RippleButton(parent, st::defaultLightButton.ripple) +CancelButton::CancelButton( + not_null parent, + const style::RecordBar &st, + int height) +: Ui::RippleButton(parent, st.cancelRipple) +, _st(st) , _width(st::historyRecordCancelButtonWidth) , _rippleRect(QRect(0, (height - _width) / 2, _width, _width)) , _text(st::semiboldTextStyle, tr::lng_selected_clear(tr::now)) { @@ -958,7 +988,7 @@ void CancelButton::init() { paintRipple(p, _rippleRect.x(), _rippleRect.y()); - p.setPen(st::historyRecordCancelButtonFg); + p.setPen(_st.cancelActive); _text.draw( p, 0, @@ -983,24 +1013,23 @@ void CancelButton::requestPaintProgress(float64 progress) { VoiceRecordBar::VoiceRecordBar( not_null parent, - not_null sectionWidget, - std::shared_ptr show, - std::shared_ptr send, - int recorderHeight) + VoiceRecordBarDescriptor &&descriptor) : RpWidget(parent) -, _sectionWidget(sectionWidget) -, _show(std::move(show)) -, _send(send) -, _lock(std::make_unique(sectionWidget)) -, _level(std::make_unique(sectionWidget)) -, _cancel(std::make_unique(this, recorderHeight)) +, _st(descriptor.stOverride ? *descriptor.stOverride : st::defaultRecordBar) +, _outerContainer(descriptor.outerContainer) +, _show(std::move(descriptor.show)) +, _send(std::move(descriptor.send)) +, _lock(std::make_unique(_outerContainer, _st.lock)) +, _level(std::make_unique(_outerContainer, _st)) +, _cancel(std::make_unique(this, _st, descriptor.recorderHeight)) , _startTimer([=] { startRecording(); }) , _message( st::historyRecordTextStyle, tr::lng_record_cancel(tr::now), TextParseOptions{ TextParseMultiline, 0, 0, Qt::LayoutDirectionAuto }) +, _lockFromBottom(descriptor.lockFromBottom) , _cancelFont(st::historyRecordFont) { - resize(QSize(parent->width(), recorderHeight)); + resize(QSize(parent->width(), descriptor.recorderHeight)); init(); hideFast(); } @@ -1010,7 +1039,12 @@ VoiceRecordBar::VoiceRecordBar( std::shared_ptr show, std::shared_ptr send, int recorderHeight) -: VoiceRecordBar(parent, parent, std::move(show), send, recorderHeight) { +: VoiceRecordBar(parent, { + .outerContainer = parent, + .show = std::move(show), + .send = std::move(send), + .recorderHeight = recorderHeight, +}) { } VoiceRecordBar::~VoiceRecordBar() { @@ -1040,14 +1074,32 @@ void VoiceRecordBar::updateMessageGeometry() { } void VoiceRecordBar::updateLockGeometry() { - const auto right = anim::interpolate( - -_lock->width(), - st::historyRecordLockPosition.x(), - _showLockAnimation.value(_lockShowing.current() ? 1. : 0.)); - _lock->moveToRight(right, _lock->y()); + const auto parent = parentWidget(); + const auto me = Ui::MapFrom(_outerContainer, parent, geometry()); + const auto finalTop = me.y() + - st::historyRecordLockPosition.y() + - _lock->height(); + const auto finalRight = _outerContainer->width() + - (me.x() + me.width()) + + st::historyRecordLockPosition.x(); + const auto progress = _showLockAnimation.value( + _lockShowing.current() ? 1. : 0.); + if (_lockFromBottom) { + const auto top = anim::interpolate(me.y(), finalTop, progress); + _lock->moveToRight(finalRight, top); + _lock->setVisibleTopPart(me.y() - top); + } else { + const auto from = -_lock->width(); + const auto right = anim::interpolate(from, finalRight, progress); + _lock->moveToRight(right, finalTop); + } } void VoiceRecordBar::init() { + if (_st.radius > 0) { + _backgroundRect.emplace(_st.radius, _st.bg); + } + // Keep VoiceRecordBar behind SendButton. rpl::single( ) | rpl::then( @@ -1087,7 +1139,6 @@ void VoiceRecordBar::init() { } _cancel->moveToLeft((size.width() - _cancel->width()) / 2, 0); updateMessageGeometry(); - updateLockGeometry(); }, lifetime()); paintRequest( @@ -1096,7 +1147,11 @@ void VoiceRecordBar::init() { if (_showAnimation.animating()) { p.setOpacity(showAnimationRatio()); } - p.fillRect(clip, st::historyComposeAreaBg); + if (_backgroundRect) { + _backgroundRect->paint(p, rect()); + } else { + p.fillRect(clip, _st.bg); + } p.setOpacity(std::min(p.opacity(), 1. - showListenAnimationRatio())); const auto opacity = p.opacity(); @@ -1237,6 +1292,9 @@ void VoiceRecordBar::init() { _cancel->setClickedCallback([=] { hideAnimated(); }); + + initLockGeometry(); + initLevelGeometry(); } void VoiceRecordBar::activeAnimate(bool active) { @@ -1278,22 +1336,25 @@ void VoiceRecordBar::setStartRecordingFilter(Fn &&callback) { _startRecordingFilter = std::move(callback); } -void VoiceRecordBar::setLockBottom(rpl::producer &&bottom) { +void VoiceRecordBar::initLockGeometry() { rpl::combine( - std::move(bottom), - _lock->sizeValue() | rpl::map_to(true) // Dummy value. - ) | rpl::start_with_next([=](int value, bool dummy) { - _lock->moveToLeft(_lock->x(), value - _lock->height()); + _lock->heightValue(), + geometryValue(), + static_cast(parentWidget())->geometryValue() + ) | rpl::start_with_next([=] { + updateLockGeometry(); }, lifetime()); } -void VoiceRecordBar::setSendButtonGeometryValue( - rpl::producer &&geometry) { - std::move( - geometry - ) | rpl::start_with_next([=](QRect r) { - const auto center = (r.width() - _level->width()) / 2; - _level->moveToLeft(r.x() + center, r.y() + center); +void VoiceRecordBar::initLevelGeometry() { + rpl::combine( + _send->geometryValue(), + geometryValue(), + static_cast(parentWidget())->geometryValue() + ) | rpl::start_with_next([=](QRect send, QRect me, QRect parent) { + const auto mapped = Ui::MapFrom(_outerContainer, this, send); + const auto center = (send.width() - _level->width()) / 2; + _level->moveToLeft(mapped.x() + center, mapped.y() + center); }, lifetime()); } @@ -1431,6 +1492,7 @@ void VoiceRecordBar::stopRecording(StopType type) { } else if (type == StopType::Listen) { _listen = std::make_unique( this, + _st, &_show->session(), std::move(data), _cancelFont); @@ -1444,7 +1506,7 @@ void VoiceRecordBar::stopRecording(StopType type) { void VoiceRecordBar::drawDuration(Painter &p) { const auto duration = FormatVoiceDuration(_recordingSamples); p.setFont(_cancelFont); - p.setPen(st::historyRecordDurationFg); + p.setPen(_st.durationFg); p.drawText(_durationRect, style::al_left, duration); } @@ -1478,11 +1540,7 @@ void VoiceRecordBar::drawRedCircle(Painter &p) { } void VoiceRecordBar::drawMessage(Painter &p, float64 recordActive) { - p.setPen( - anim::pen( - st::historyRecordCancel, - st::historyRecordCancelActive, - 1. - recordActive)); + p.setPen(anim::pen(_st.cancel, _st.cancelActive, 1. - recordActive)); const auto opacity = p.opacity(); p.setOpacity(opacity * (1. - _lock->lockToStopProgress())); @@ -1621,8 +1679,8 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) { void VoiceRecordBar::orderControls() { stackUnder(_send.get()); - _level->raise(); _lock->raise(); + _level->raise(); } void VoiceRecordBar::installListenStateFilter() { 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 ea48e887a..97fed00d9 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 @@ -11,10 +11,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer.h" #include "history/view/controls/compose_controls_common.h" #include "ui/effects/animations.h" +#include "ui/round_rect.h" #include "ui/rp_widget.h" struct VoiceData; +namespace style { +struct RecordBar; +} // namespace style + namespace Ui { class SendButton; } // namespace Ui @@ -34,6 +39,15 @@ class ListenWrap; class RecordLock; class CancelButton; +struct VoiceRecordBarDescriptor { + not_null outerContainer; + std::shared_ptr show; + std::shared_ptr send; + const style::RecordBar *stOverride = nullptr; + int recorderHeight = 0; + bool lockFromBottom = false; +}; + class VoiceRecordBar final : public Ui::RpWidget { public: using SendActionUpdate = Controls::SendActionUpdate; @@ -41,10 +55,7 @@ public: VoiceRecordBar( not_null parent, - not_null sectionWidget, - std::shared_ptr show, - std::shared_ptr send, - int recorderHeight); + VoiceRecordBarDescriptor &&descriptor); VoiceRecordBar( not_null parent, std::shared_ptr show, @@ -75,8 +86,6 @@ public: void requestToSendWithOptions(Api::SendOptions options); - void setLockBottom(rpl::producer &&bottom); - void setSendButtonGeometryValue(rpl::producer &&geometry); void setStartRecordingFilter(Fn &&callback); [[nodiscard]] bool isRecording() const; @@ -93,6 +102,8 @@ private: }; void init(); + void initLockGeometry(); + void initLevelGeometry(); void updateMessageGeometry(); void updateLockGeometry(); @@ -125,7 +136,8 @@ private: void computeAndSetLockProgress(QPoint globalPos); - const not_null _sectionWidget; + const style::RecordBar &_st; + const not_null _outerContainer; const std::shared_ptr _show; const std::shared_ptr _send; const std::unique_ptr _lock; @@ -159,11 +171,13 @@ private: rpl::event_stream<> _recordingTipRequests; bool _recordingTipRequired = false; + bool _lockFromBottom = false; const style::font &_cancelFont; rpl::lifetime _recordingLifetime; + std::optional _backgroundRect; Ui::Animations::Simple _showLockAnimation; Ui::Animations::Simple _lockToStopAnimation; Ui::Animations::Simple _showListenAnimation; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index f05b3ed44..71a652f4d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -47,8 +47,11 @@ auto Blobs() { } // namespace -VoiceRecordButton::VoiceRecordButton(not_null parent) +VoiceRecordButton::VoiceRecordButton( + not_null parent, + const style::RecordBar &st) : AbstractButton(parent) +, _st(st) , _blobs(std::make_unique( Blobs(), kLevelDuration, diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h index 750c8abfb..fc477b485 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.h @@ -11,17 +11,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/rp_widget.h" -namespace Ui { -namespace Paint { +namespace style { +struct RecordBar; +} // namespace style + +namespace Ui::Paint { class Blobs; -} // namespace Paint -} // namespace Ui +} // namespace Ui::Paint namespace HistoryView::Controls { class VoiceRecordButton final : public Ui::AbstractButton { public: - explicit VoiceRecordButton(not_null parent); + VoiceRecordButton( + not_null parent, + const style::RecordBar &st); ~VoiceRecordButton(); enum class Type { @@ -43,6 +47,7 @@ public: private: void init(); + const style::RecordBar &_st; std::unique_ptr _blobs; crl::time _lastUpdateTime = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index 1b44b8fa2..1f6aca80a 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -35,6 +35,7 @@ ReplyArea::ReplyArea(not_null controller) .mode = HistoryView::ComposeControlsMode::Normal, .sendMenuType = SendMenu::Type::SilentOnly, .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), + .voiceLockFromBottom = true, .features = { .sendAs = false, .ttlInfo = false, diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index aa9377f09..98ff6dd4f 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -448,24 +448,23 @@ storiesComposeWhiteText: groupCallMembersFg; storiesComposeGrayText: groupCallMemberNotJoinedStatus; storiesComposeGrayIcon: groupCallMemberInactiveIcon; storiesComposeBlue: groupCallActiveFg; +storiesComposeRippleLight: RippleAnimation(defaultRippleAnimation) { + color: storiesComposeBgOver; +} storiesComposeRipple: RippleAnimation(defaultRippleAnimation) { color: groupCallMembersBgRipple; } storiesAttach: IconButton(historyAttach) { icon: icon {{ "chat/input_attach", storiesComposeGrayIcon }}; iconOver: icon {{ "chat/input_attach", storiesComposeGrayIcon }}; - ripple: RippleAnimation(defaultRippleAnimation) { - color: storiesComposeBgOver; - } + ripple: storiesComposeRippleLight; } storiesRecordVoice: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRecordVoiceOver: icon {{ "chat/input_record", storiesComposeGrayIcon }}; storiesRemoveSet: IconButton(stickerPanRemoveSet) { icon: icon {{ "simple_close", storiesComposeGrayIcon }}; iconOver: icon {{ "simple_close", storiesComposeGrayIcon }}; - ripple: RippleAnimation(defaultRippleAnimation) { - color: storiesComposeBgOver; - } + ripple: storiesComposeRippleLight; } storiesMenu: Menu(defaultMenu) { itemBg: groupCallMenuBg; @@ -641,4 +640,25 @@ storiesComposeControls: ComposeControls(defaultComposeControls) { } autocompleteBottomSkip: 10px; } + record: RecordBar(defaultRecordBar) { + bg: storiesComposeBg; + durationFg: storiesComposeWhiteText; + radius: storiesRadius; + cancel: storiesComposeGrayText; + cancelActive: storiesComposeBlue; + cancelRipple: storiesComposeRippleLight; + lock: RecordBarLock(defaultRecordBarLock) { + ripple: storiesComposeRipple; + originTop: icon {{ "voice_lock/record_lock_top", storiesComposeBg }}; + originBottom: icon {{ "voice_lock/record_lock_bottom", storiesComposeBg }}; + originBody: icon {{ "voice_lock/record_lock_body", storiesComposeBg }}; + arrow: icon {{ "voice_lock/voice_arrow", storiesComposeGrayIcon }}; + fg: storiesComposeGrayIcon; + } + remove: IconButton(storiesAttach) { + icon: icon {{ "info/info_media_delete", storiesComposeGrayIcon }}; + iconOver: icon {{ "info/info_media_delete", storiesComposeGrayIcon }}; + iconPosition: point(10px, 11px); + } + } }