Add author to the top of Reply in Another Chat.

This commit is contained in:
John Preston 2024-10-18 11:32:08 +04:00
parent 511cfc524f
commit f74dd3ca1e
8 changed files with 201 additions and 13 deletions

View file

@ -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.";

View file

@ -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;

View file

@ -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) {

View file

@ -357,6 +357,8 @@ public:
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
virtual std::shared_ptr<Main::SessionShow> 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<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
void peerListSelectSkip(int direction) override {
_content->selectSkip(direction);
}
void peerListPressLeftToContextMenu(bool shown) override {
_content->pressLeftToContextMenu(shown);
}

View file

@ -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<Ui::RpWidget> content = { nullptr };
Fn<bool(int, int, int)> overrideKey;
};
[[nodiscard]] AuthorSelector AuthorRowSelector(
not_null<Main::Session*> session,
FullReplyTo reply,
Fn<void(not_null<Data::Thread*>)> 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<PeerData*> peer, Fn<void()> click)
: _peer(peer)
, _click(std::move(click)) {
}
void prepare() override {
delegate()->peerListAppendRow(
std::make_unique<ChatsListBoxController::Row>(
_peer->owner().history(_peer),
&computeListSt().item));
delegate()->peerListRefreshRows();
TrackPremiumRequiredChanges(this, _lifetime);
}
void loadMoreRows() override {
}
void rowClicked(not_null<PeerListRow*> 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<PeerData*> _peer;
Fn<void()> _click;
rpl::lifetime _lifetime;
};
auto result = object_ptr<Ui::VerticalLayout>((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<PeerListContent>(
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<Data::Thread*>;
Controller(not_null<Main::Session*> session)
Controller(not_null<Main::Session*> 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<Chosen> 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<Chosen> _singleChosen;
AuthorSelector _authorRow;
};
@ -956,7 +1089,7 @@ void ShowReplyToChatBox(
};
const auto session = &show->session();
const auto state = [&] {
auto controller = std::make_unique<Controller>(session);
auto controller = std::make_unique<Controller>(session, reply);
const auto controllerRaw = controller.get();
auto box = Box<PeerListBox>(std::move(controller), [=](
not_null<PeerListBox*> box) {

View file

@ -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);

View file

@ -16,6 +16,8 @@ namespace Capture {
struct Update {
int samples = 0;
ushort level = 0;
bool finished = false;
};
struct Chunk {

View file

@ -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<AVFrame*> frame);
void initCircleMask();
const crl::weak_on_queue<Private> _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<Update, rpl::empty_error> _updates;
void cutCircleFromYUV420P(not_null<AVFrame*> frame);
void initCircleMask();
std::vector<bool> _circleMask; // Always nice to use vector<bool>! :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,