From f74dd3ca1e8449a3aedcbae4de0a04cfcaa9f0e4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 18 Oct 2024 11:32:08 +0400 Subject: [PATCH] Add author to the top of Reply in Another Chat. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/boxes/boxes.style | 7 + Telegram/SourceFiles/boxes/peer_list_box.cpp | 7 + Telegram/SourceFiles/boxes/peer_list_box.h | 13 ++ .../controls/history_view_draft_options.cpp | 137 +++++++++++++++++- .../history_view_voice_record_bar.cpp | 3 + .../media/audio/media_audio_capture.h | 2 + .../ui/controls/round_video_recorder.cpp | 43 ++++-- 8 files changed, 201 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3d576fe7c..70ad59585 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3684,6 +3684,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_reply_in_another_title" = "Reply in..."; "lng_reply_in_another_chat" = "Reply in Another Chat"; +"lng_reply_in_author" = "Message author"; +"lng_reply_in_chats_list" = "Your chats"; "lng_reply_show_in_chat" = "Show in Chat"; "lng_reply_remove" = "Do Not Reply"; "lng_reply_about_quote" = "You can select a specific part to quote."; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 845098628..827b0c0b3 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -878,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) { peerListSingleRow: PeerList(peerListBox) { padding: margins(0px, 0px, 0px, 0px); } +peerListSmallSkips: PeerList(peerListBox) { + padding: margins( + 0px, + defaultVerticalListSkip, + 0px, + defaultVerticalListSkip); +} scheduleHeight: 95px; scheduleDateTop: 38px; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index b0f3c0a50..b47d4b203 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -1944,6 +1944,13 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) { } } + if (_controller->overrideKeyboardNavigation( + direction, + _selected.index.value, + newSelectedIndex)) { + return { _selected.index.value, _selected.index.value }; + } + _selected.index.value = newSelectedIndex; _selected.element = 0; if (newSelectedIndex >= 0) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 4c4374b84..ce1cc1220 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -357,6 +357,8 @@ public: virtual int peerListPartitionRows(Fn border) = 0; virtual std::shared_ptr peerListUiShow() = 0; + virtual void peerListSelectSkip(int direction) = 0; + virtual void peerListPressLeftToContextMenu(bool shown) = 0; virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0; @@ -573,6 +575,13 @@ public: Unexpected("PeerListController::customRowRippleMaskGenerator."); } + virtual bool overrideKeyboardNavigation( + int direction, + int fromIndex, + int toIndex) { + return false; + } + [[nodiscard]] rpl::lifetime &lifetime() { return _lifetime; } @@ -1016,6 +1025,10 @@ public: bool highlightRow, Fn)> destroyed = nullptr) override; + void peerListSelectSkip(int direction) override { + _content->selectSkip(direction); + } + void peerListPressLeftToContextMenu(bool shown) override { _content->pressLeftToContextMenu(shown); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp index d0c8ca2b8..8b0429c28 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer_rpl.h" #include "base/unixtime.h" +#include "boxes/filters/edit_filter_chats_list.h" #include "boxes/peer_list_box.h" #include "boxes/peer_list_controllers.h" #include "chat_helpers/compose/compose_show.h" @@ -41,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme.h" #include "window/section_widget.h" #include "window/window_session_controller.h" +#include "styles/style_boxes.h" #include "styles/style_chat.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" @@ -911,6 +913,117 @@ void DraftOptionsBox( }, box->lifetime()); } } + +struct AuthorSelector { + object_ptr content = { nullptr }; + Fn overrideKey; +}; +[[nodiscard]] AuthorSelector AuthorRowSelector( + not_null session, + FullReplyTo reply, + Fn)> chosen) { + const auto item = session->data().message(reply.messageId); + if (!item) { + return {}; + } + const auto displayFrom = item->displayFrom(); + const auto from = displayFrom ? displayFrom : item->from().get(); + if (!from->isUser() || from == item->history()->peer || from->isSelf()) { + return {}; + } + + class AuthorController final : public PeerListController { + public: + AuthorController(not_null peer, Fn click) + : _peer(peer) + , _click(std::move(click)) { + } + + void prepare() override { + delegate()->peerListAppendRow( + std::make_unique( + _peer->owner().history(_peer), + &computeListSt().item)); + delegate()->peerListRefreshRows(); + TrackPremiumRequiredChanges(this, _lifetime); + } + void loadMoreRows() override { + } + void rowClicked(not_null row) override { + if (RecipientRow::ShowLockedError(this, row, WritePremiumRequiredError)) { + return; + } else if (const auto onstack = _click) { + onstack(); + } + } + Main::Session &session() const override { + return _peer->session(); + } + + private: + const not_null _peer; + Fn _click; + rpl::lifetime _lifetime; + + }; + + auto result = object_ptr((QWidget*)nullptr); + const auto container = result.data(); + + container->add(CreatePeerListSectionSubtitle( + container, + tr::lng_reply_in_author())); + Ui::AddSkip(container); + + const auto delegate = container->lifetime().make_state< + PeerListContentDelegateSimple + >(); + const auto controller = container->lifetime().make_state< + AuthorController + >(from, [=] { chosen(from->owner().history(from)); }); + controller->setStyleOverrides(&st::peerListSingleRow); + const auto content = container->add(object_ptr( + container, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + + Ui::AddSkip(container); + container->add(CreatePeerListSectionSubtitle( + container, + tr::lng_reply_in_chats_list())); + + const auto overrideKey = [=](int direction, int from, int to) { + if (!content->isVisible()) { + return false; + } else if (direction > 0 && from < 0 && to >= 0) { + if (content->hasSelection()) { + const auto was = content->selectedIndex(); + const auto now = content->selectSkip(1).reallyMovedTo; + if (was != now) { + return true; + } + content->clearSelection(); + } else { + content->selectSkip(1); + return true; + } + } else if (direction < 0 && to < 0) { + if (!content->hasSelection()) { + content->selectLast(); + } else if (from >= 0 || content->hasSelection()) { + content->selectSkip(-1); + } + } + return false; + }; + + return { + .content = std::move(result), + .overrideKey = overrideKey, + }; +} + } // namespace void ShowReplyToChatBox( @@ -921,7 +1034,7 @@ void ShowReplyToChatBox( public: using Chosen = not_null; - Controller(not_null session) + Controller(not_null session, FullReplyTo reply) : ChooseRecipientBoxController({ .session = session, .callback = [=](Chosen thread) { @@ -929,6 +1042,13 @@ void ShowReplyToChatBox( }, .premiumRequiredError = WritePremiumRequiredError, }) { + _authorRow = AuthorRowSelector( + session, + reply, + [=](Chosen thread) { _singleChosen.fire_copy(thread); }); + if (_authorRow.content) { + setStyleOverrides(&st::peerListSmallSkips); + } } [[nodiscard]] rpl::producer singleChosen() const { @@ -939,13 +1059,26 @@ void ShowReplyToChatBox( return tr::lng_saved_quote_here(tr::now); } + bool overrideKeyboardNavigation( + int direction, + int fromIndex, + int toIndex) override { + return _authorRow.overrideKey + && _authorRow.overrideKey(direction, fromIndex, toIndex); + } + private: void prepareViewHook() override { + if (_authorRow.content) { + delegate()->peerListSetAboveWidget( + std::move(_authorRow.content)); + } ChooseRecipientBoxController::prepareViewHook(); delegate()->peerListSetTitle(tr::lng_reply_in_another_title()); } rpl::event_stream _singleChosen; + AuthorSelector _authorRow; }; @@ -956,7 +1089,7 @@ void ShowReplyToChatBox( }; const auto session = &show->session(); const auto state = [&] { - auto controller = std::make_unique(session); + auto controller = std::make_unique(session, reply); const auto controllerRaw = controller.get(); auto box = Box(std::move(controller), [=]( not_null box) { 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 dd5968335..bd12a4521 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 @@ -1704,6 +1704,9 @@ void VoiceRecordBar::startRecording() { ) | rpl::start_with_next_error([=](const Update &update) { _recordingTipRequired = (update.samples < kMinSamples); recordUpdated(update.level, update.samples); + if (update.finished) { + stop(true); + } }, [=] { stop(false); }, _recordingLifetime); diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.h b/Telegram/SourceFiles/media/audio/media_audio_capture.h index fe03dd89c..5a48d16a4 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.h +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.h @@ -16,6 +16,8 @@ namespace Capture { struct Update { int samples = 0; ushort level = 0; + + bool finished = false; }; struct Chunk { diff --git a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp index a828566e7..b903a27af 100644 --- a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp +++ b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp @@ -24,6 +24,7 @@ constexpr auto kUpdateEach = crl::time(100); constexpr auto kAudioFrequency = 48'000; constexpr auto kAudioBitRate = 32'000; constexpr auto kVideoBitRate = 3 * 1024 * 1024; +constexpr auto kMaxDuration = 10 * crl::time(1000); AssertIsDebug(); using namespace FFmpeg; @@ -52,6 +53,7 @@ private: void initEncoding(); bool initVideo(); bool initAudio(); + void notifyFinished(); void deinitEncoding(); void finishEncoding(); void fail(); @@ -66,6 +68,9 @@ private: void updateMaxLevel(const Media::Capture::Chunk &chunk); void updateResultDuration(int64 pts, AVRational timeBase); + void cutCircleFromYUV420P(not_null frame); + void initCircleMask(); + const crl::weak_on_queue _weak; FormatPointer _format; @@ -95,14 +100,12 @@ private: QByteArray _result; int64_t _resultOffset = 0; crl::time _resultDuration = 0; + bool _finished = false; ushort _maxLevelSinceLastUpdate = 0; crl::time _lastUpdateDuration = 0; rpl::event_stream _updates; - void cutCircleFromYUV420P(not_null frame); - void initCircleMask(); - std::vector _circleMask; // Always nice to use vector! :D }; @@ -400,7 +403,7 @@ void RoundVideoRecorder::Private::deinitEncoding() { void RoundVideoRecorder::Private::push( int64 mcstimestamp, const QImage &frame) { - if (!_format) { + if (!_format || _finished) { return; } else if (!_firstAudioChunkFinished) { // Skip frames while we didn't start receiving audio. @@ -412,7 +415,7 @@ void RoundVideoRecorder::Private::push( } void RoundVideoRecorder::Private::push(const Media::Capture::Chunk &chunk) { - if (!_format) { + if (!_format || _finished) { return; } else if (!_firstAudioChunkFinished || !_firstVideoFrameTime) { _firstAudioChunkFinished = chunk.finished; @@ -425,6 +428,11 @@ void RoundVideoRecorder::Private::push(const Media::Capture::Chunk &chunk) { void RoundVideoRecorder::Private::encodeVideoFrame( int64 mcstimestamp, const QImage &frame) { + Expects(!_finished); + + if (_videoFirstTimestamp == -1) { + _videoFirstTimestamp = mcstimestamp; + } const auto fwidth = frame.width(); const auto fheight = frame.height(); const auto fmin = std::min(fwidth, fheight); @@ -443,10 +451,6 @@ void RoundVideoRecorder::Private::encodeVideoFrame( return; } - if (_videoFirstTimestamp == -1) { - _videoFirstTimestamp = mcstimestamp; - } - const auto cdata = frame.constBits() + (frame.bytesPerLine() * fy) + (fx * frame.depth() / 8); @@ -466,7 +470,10 @@ void RoundVideoRecorder::Private::encodeVideoFrame( cutCircleFromYUV420P(_videoFrame.get()); _videoFrame->pts = mcstimestamp - _videoFirstTimestamp; - if (!writeFrame(_videoFrame, _videoCodec, _videoStream)) { + if (_videoFrame->pts >= kMaxDuration * int64(1000)) { + notifyFinished(); + return; + } else if (!writeFrame(_videoFrame, _videoCodec, _videoStream)) { return; } } @@ -534,6 +541,8 @@ void RoundVideoRecorder::Private::cutCircleFromYUV420P( void RoundVideoRecorder::Private::encodeAudioFrame( const Media::Capture::Chunk &chunk) { + Expects(!_finished); + updateMaxLevel(chunk); if (_audioTail.isEmpty()) { @@ -578,7 +587,10 @@ void RoundVideoRecorder::Private::encodeAudioFrame( _audioFrame->pts = _audioPts; _audioPts += _audioFrame->nb_samples; - if (!writeFrame(_audioFrame, _audioCodec, _audioStream)) { + if (_audioPts >= kMaxDuration * int64(kAudioFrequency) / 1000) { + notifyFinished(); + return; + } else if (!writeFrame(_audioFrame, _audioCodec, _audioStream)) { return; } @@ -593,6 +605,15 @@ void RoundVideoRecorder::Private::encodeAudioFrame( } } +void RoundVideoRecorder::Private::notifyFinished() { + _finished = true; + _updates.fire({ + .samples = int(_resultDuration * 48), + .level = base::take(_maxLevelSinceLastUpdate), + .finished = true, + }); +} + bool RoundVideoRecorder::Private::writeFrame( const FramePointer &frame, const CodecPointer &codec,