mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-06 15:13:57 +02:00
Allow sending voice messages in Replies / Scheduled.
This commit is contained in:
parent
62da24c20b
commit
405c8125da
6 changed files with 369 additions and 33 deletions
|
@ -22,33 +22,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_web_page.h"
|
#include "data/data_web_page.h"
|
||||||
#include "facades.h"
|
#include "facades.h"
|
||||||
|
#include "boxes/confirm_box.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/view/history_view_webpage_preview.h"
|
#include "history/view/history_view_webpage_preview.h"
|
||||||
#include "inline_bots/inline_results_widget.h"
|
#include "inline_bots/inline_results_widget.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "media/audio/media_audio_capture.h"
|
||||||
|
#include "media/audio/media_audio.h"
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
#include "ui/special_buttons.h"
|
#include "ui/special_buttons.h"
|
||||||
#include "ui/text_options.h"
|
#include "ui/text_options.h"
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
#include "ui/widgets/input_fields.h"
|
#include "ui/widgets/input_fields.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using FileChosen = ComposeControls::FileChosen;
|
constexpr auto kRecordingUpdateDelta = crl::time(100);
|
||||||
using PhotoChosen = ComposeControls::PhotoChosen;
|
constexpr auto kMouseEvents = {
|
||||||
using MessageToEdit = ComposeControls::MessageToEdit;
|
|
||||||
|
|
||||||
constexpr auto kMouseEvent = {
|
|
||||||
QEvent::MouseMove,
|
QEvent::MouseMove,
|
||||||
QEvent::MouseButtonPress,
|
QEvent::MouseButtonPress,
|
||||||
QEvent::MouseButtonRelease
|
QEvent::MouseButtonRelease
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using FileChosen = ComposeControls::FileChosen;
|
||||||
|
using PhotoChosen = ComposeControls::PhotoChosen;
|
||||||
|
using MessageToEdit = ComposeControls::MessageToEdit;
|
||||||
|
using VoiceToSend = ComposeControls::VoiceToSend;
|
||||||
|
using SendActionUpdate = ComposeControls::SendActionUpdate;
|
||||||
|
|
||||||
[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) {
|
[[nodiscard]] auto ShowWebPagePreview(WebPageData *page) {
|
||||||
return page && (page->pendingTill >= 0);
|
return page && (page->pendingTill >= 0);
|
||||||
}
|
}
|
||||||
|
@ -223,7 +229,7 @@ void FieldHeader::init() {
|
||||||
const auto inClickable = lifetime().make_state<bool>(false);
|
const auto inClickable = lifetime().make_state<bool>(false);
|
||||||
events(
|
events(
|
||||||
) | rpl::filter([=](not_null<QEvent*> event) {
|
) | rpl::filter([=](not_null<QEvent*> event) {
|
||||||
return ranges::contains(kMouseEvent, event->type())
|
return ranges::contains(kMouseEvents, event->type())
|
||||||
&& isEditingMessage();
|
&& isEditingMessage();
|
||||||
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||||
const auto type = event->type();
|
const auto type = event->type();
|
||||||
|
@ -502,11 +508,18 @@ ComposeControls::ComposeControls(
|
||||||
, _header(std::make_unique<FieldHeader>(
|
, _header(std::make_unique<FieldHeader>(
|
||||||
_wrap.get(),
|
_wrap.get(),
|
||||||
&_window->session().data()))
|
&_window->session().data()))
|
||||||
, _textUpdateEvents(TextUpdateEvent::SendTyping) {
|
, _textUpdateEvents(TextUpdateEvent::SendTyping)
|
||||||
|
, _recordCancelWidth(st::historyRecordFont->width(tr::lng_record_cancel(tr::now)))
|
||||||
|
, _recordingAnimation([=](crl::time now) {
|
||||||
|
return recordingAnimationCallback(now);
|
||||||
|
}) {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
ComposeControls::~ComposeControls() {
|
ComposeControls::~ComposeControls() {
|
||||||
|
if (_recording) {
|
||||||
|
stopRecording(false);
|
||||||
|
}
|
||||||
setTabbedPanel(nullptr);
|
setTabbedPanel(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,8 +554,89 @@ int ComposeControls::heightCurrent() const {
|
||||||
return _wrap->height();
|
return _wrap->height();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::focus() {
|
bool ComposeControls::focus() {
|
||||||
|
if (_recording) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
_field->setFocus();
|
_field->setFocus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::updateControlsVisibility() {
|
||||||
|
if (_recording) {
|
||||||
|
_field->hide();
|
||||||
|
_tabbedSelectorToggle->hide();
|
||||||
|
//_botKeyboardShow->hide();
|
||||||
|
//_botKeyboardHide->hide();
|
||||||
|
//_botCommandStart->hide();
|
||||||
|
_attachToggle->hide();
|
||||||
|
//if (_silent) {
|
||||||
|
// _silent->hide();
|
||||||
|
//}
|
||||||
|
//if (_scheduled) {
|
||||||
|
// _scheduled->hide();
|
||||||
|
//}
|
||||||
|
//if (_kbShown) {
|
||||||
|
// _kbScroll->show();
|
||||||
|
//} else {
|
||||||
|
// _kbScroll->hide();
|
||||||
|
//}
|
||||||
|
} else {
|
||||||
|
_field->show();
|
||||||
|
//if (_kbShown) {
|
||||||
|
// _kbScroll->show();
|
||||||
|
// _tabbedSelectorToggle->hide();
|
||||||
|
// _botKeyboardHide->show();
|
||||||
|
// _botKeyboardShow->hide();
|
||||||
|
// _botCommandStart->hide();
|
||||||
|
//} else if (_kbReplyTo) {
|
||||||
|
// _kbScroll->hide();
|
||||||
|
// _tabbedSelectorToggle->show();
|
||||||
|
// _botKeyboardHide->hide();
|
||||||
|
// _botKeyboardShow->hide();
|
||||||
|
// _botCommandStart->hide();
|
||||||
|
//} else {
|
||||||
|
// _kbScroll->hide();
|
||||||
|
// _tabbedSelectorToggle->show();
|
||||||
|
// _botKeyboardHide->hide();
|
||||||
|
// if (_keyboard->hasMarkup()) {
|
||||||
|
// _botKeyboardShow->show();
|
||||||
|
// _botCommandStart->hide();
|
||||||
|
// } else {
|
||||||
|
// _botKeyboardShow->hide();
|
||||||
|
// if (_cmdStartShown) {
|
||||||
|
// _botCommandStart->show();
|
||||||
|
// } else {
|
||||||
|
// _botCommandStart->hide();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
_attachToggle->show();
|
||||||
|
//if (_silent) {
|
||||||
|
// _silent->show();
|
||||||
|
//}
|
||||||
|
//if (_scheduled) {
|
||||||
|
// _scheduled->show();
|
||||||
|
//}
|
||||||
|
//updateFieldPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComposeControls::recordingAnimationCallback(crl::time now) {
|
||||||
|
const auto dt = anim::Disabled()
|
||||||
|
? 1.
|
||||||
|
: ((now - _recordingAnimation.started())
|
||||||
|
/ float64(kRecordingUpdateDelta));
|
||||||
|
if (dt >= 1.) {
|
||||||
|
_recordingLevel.finish();
|
||||||
|
} else {
|
||||||
|
_recordingLevel.update(dt, anim::linear);
|
||||||
|
}
|
||||||
|
if (!anim::Disabled()) {
|
||||||
|
_wrap->update(_attachToggle->geometry());
|
||||||
|
}
|
||||||
|
return (dt < 1.);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> ComposeControls::cancelRequests() const {
|
rpl::producer<> ComposeControls::cancelRequests() const {
|
||||||
|
@ -573,6 +667,10 @@ rpl::producer<> ComposeControls::sendRequests() const {
|
||||||
std::move(submits) | filter | rpl::to_empty);
|
std::move(submits) | filter | rpl::to_empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<VoiceToSend> ComposeControls::sendVoiceRequests() const {
|
||||||
|
return _sendVoiceRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<MessageToEdit> ComposeControls::editRequests() const {
|
rpl::producer<MessageToEdit> ComposeControls::editRequests() const {
|
||||||
auto toValue = rpl::map([=] { return _header->queryToEdit(); });
|
auto toValue = rpl::map([=] { return _header->queryToEdit(); });
|
||||||
auto filter = rpl::filter([=] {
|
auto filter = rpl::filter([=] {
|
||||||
|
@ -668,6 +766,25 @@ void ComposeControls::init() {
|
||||||
initTabbedSelector();
|
initTabbedSelector();
|
||||||
initSendButton();
|
initSendButton();
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
::Media::Capture::instance(),
|
||||||
|
&::Media::Capture::Instance::error,
|
||||||
|
_wrap.get(),
|
||||||
|
[=] { recordError(); });
|
||||||
|
QObject::connect(
|
||||||
|
::Media::Capture::instance(),
|
||||||
|
&::Media::Capture::Instance::updated,
|
||||||
|
_wrap.get(),
|
||||||
|
[=](quint16 level, int samples) { recordUpdated(level, samples); });
|
||||||
|
qRegisterMetaType<VoiceWaveform>();
|
||||||
|
QObject::connect(
|
||||||
|
::Media::Capture::instance(),
|
||||||
|
&::Media::Capture::Instance::done,
|
||||||
|
_wrap.get(),
|
||||||
|
[=](QByteArray result, VoiceWaveform waveform, int samples) {
|
||||||
|
recordDone(result, waveform, samples);
|
||||||
|
});
|
||||||
|
|
||||||
_wrap->sizeValue(
|
_wrap->sizeValue(
|
||||||
) | rpl::start_with_next([=](QSize size) {
|
) | rpl::start_with_next([=](QSize size) {
|
||||||
updateControlsGeometry(size);
|
updateControlsGeometry(size);
|
||||||
|
@ -686,7 +803,7 @@ void ComposeControls::init() {
|
||||||
_header->editMsgId(
|
_header->editMsgId(
|
||||||
) | rpl::start_with_next([=](const auto &id) {
|
) | rpl::start_with_next([=](const auto &id) {
|
||||||
if (_header->isEditingMessage()) {
|
if (_header->isEditingMessage()) {
|
||||||
setTextFromEditingMessage(_window->session().data().message(id));
|
setTextFromEditingMessage(session().data().message(id));
|
||||||
} else {
|
} else {
|
||||||
setText(_localSavedText);
|
setText(_localSavedText);
|
||||||
_localSavedText = {};
|
_localSavedText = {};
|
||||||
|
@ -709,7 +826,7 @@ void ComposeControls::init() {
|
||||||
*lastMsgId = id;
|
*lastMsgId = id;
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
_window->session().data().itemRemoved(
|
session().data().itemRemoved(
|
||||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||||
return item->id && ((*lastMsgId) == item->fullId());
|
return item->id && ((*lastMsgId) == item->fullId());
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
|
@ -718,6 +835,120 @@ void ComposeControls::init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComposeControls::recordError() {
|
||||||
|
stopRecording(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::recordDone(
|
||||||
|
QByteArray result,
|
||||||
|
VoiceWaveform waveform,
|
||||||
|
int samples) {
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window::ActivateWindow(_window);
|
||||||
|
const auto duration = samples / ::Media::Player::kDefaultFrequency;
|
||||||
|
_sendVoiceRequests.fire({ result, waveform, duration });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::recordUpdated(quint16 level, int samples) {
|
||||||
|
if (!_recording) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_recordingLevel.start(level);
|
||||||
|
_recordingAnimation.start();
|
||||||
|
_recordingSamples = samples;
|
||||||
|
if (samples < 0 || samples >= ::Media::Player::kDefaultFrequency * AudioVoiceMsgMaxLength) {
|
||||||
|
stopRecording(samples > 0 && _inField);
|
||||||
|
}
|
||||||
|
Core::App().updateNonIdle();
|
||||||
|
_wrap->update();
|
||||||
|
_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::recordStartCallback() {
|
||||||
|
//const auto error = _peer // #TODO restrictions
|
||||||
|
// ? Data::RestrictionError(_peer, ChatRestriction::f_send_media)
|
||||||
|
// : std::nullopt;
|
||||||
|
const auto error = std::optional<QString>();
|
||||||
|
if (error) {
|
||||||
|
Ui::show(Box<InformBox>(*error));
|
||||||
|
return;
|
||||||
|
//} else if (showSlowmodeError()) { // #TODO slowmode
|
||||||
|
// return;
|
||||||
|
} else if (!::Media::Capture::instance()->available()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit ::Media::Capture::instance()->start();
|
||||||
|
|
||||||
|
_recording = _inField = true;
|
||||||
|
updateControlsVisibility();
|
||||||
|
_window->widget()->setInnerFocus();
|
||||||
|
|
||||||
|
_wrap->update();
|
||||||
|
|
||||||
|
_send->setRecordActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::recordStopCallback(bool active) {
|
||||||
|
stopRecording(active);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::recordUpdateCallback(QPoint globalPos) {
|
||||||
|
updateOverStates(_wrap->mapFromGlobal(globalPos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::stopRecording(bool send) {
|
||||||
|
emit ::Media::Capture::instance()->stop(send);
|
||||||
|
|
||||||
|
_recordingLevel = anim::value();
|
||||||
|
_recordingAnimation.stop();
|
||||||
|
|
||||||
|
_recording = false;
|
||||||
|
_recordingSamples = 0;
|
||||||
|
_sendActionUpdates.fire({ Api::SendProgressType::RecordVoice, -1 });
|
||||||
|
|
||||||
|
updateControlsVisibility();
|
||||||
|
_window->widget()->setInnerFocus();
|
||||||
|
|
||||||
|
_wrap->update();
|
||||||
|
_send->setRecordActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComposeControls::showRecordButton() const {
|
||||||
|
return ::Media::Capture::instance()->available()
|
||||||
|
&& !HasSendText(_field)
|
||||||
|
//&& !readyToForward()
|
||||||
|
&& !isEditingMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeControls::drawRecording(Painter &p, float64 recordActive) {
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush(st::historyRecordSignalColor);
|
||||||
|
|
||||||
|
auto delta = qMin(_recordingLevel.current() / 0x4000, 1.);
|
||||||
|
auto d = 2 * qRound(st::historyRecordSignalMin + (delta * (st::historyRecordSignalMax - st::historyRecordSignalMin)));
|
||||||
|
{
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.drawEllipse(_attachToggle->x() + (_tabbedSelectorToggle->width() - d) / 2, _attachToggle->y() + (_attachToggle->height() - d) / 2, d, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto duration = formatDurationText(_recordingSamples / ::Media::Player::kDefaultFrequency);
|
||||||
|
p.setFont(st::historyRecordFont);
|
||||||
|
|
||||||
|
p.setPen(st::historyRecordDurationFg);
|
||||||
|
p.drawText(_attachToggle->x() + _tabbedSelectorToggle->width(), _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, duration);
|
||||||
|
|
||||||
|
int32 left = _attachToggle->x() + _tabbedSelectorToggle->width() + st::historyRecordFont->width(duration) + ((_send->width() - st::historyRecordVoice.width()) / 2);
|
||||||
|
int32 right = _wrap->width() - _send->width();
|
||||||
|
|
||||||
|
p.setPen(anim::pen(st::historyRecordCancel, st::historyRecordCancelActive, 1. - recordActive));
|
||||||
|
p.drawText(left + (right - left - _recordCancelWidth) / 2, _attachToggle->y() + st::historyRecordTextTop + st::historyRecordFont->ascent, tr::lng_record_cancel(tr::now));
|
||||||
|
}
|
||||||
|
|
||||||
void ComposeControls::setTextFromEditingMessage(not_null<HistoryItem*> item) {
|
void ComposeControls::setTextFromEditingMessage(not_null<HistoryItem*> item) {
|
||||||
if (!_header->isEditingMessage()) {
|
if (!_header->isEditingMessage()) {
|
||||||
return;
|
return;
|
||||||
|
@ -752,14 +983,15 @@ void ComposeControls::fieldChanged() {
|
||||||
if (/*!_inlineBot
|
if (/*!_inlineBot
|
||||||
&& */!_header->isEditingMessage()
|
&& */!_header->isEditingMessage()
|
||||||
&& (_textUpdateEvents & TextUpdateEvent::SendTyping)) {
|
&& (_textUpdateEvents & TextUpdateEvent::SendTyping)) {
|
||||||
_sendActionUpdates.fire(Api::SendProgress{
|
_sendActionUpdates.fire({ Api::SendProgressType::Typing });
|
||||||
Api::SendProgressType::Typing,
|
}
|
||||||
crl::now() + 5 * crl::time(1000),
|
updateSendButtonType();
|
||||||
});
|
if (showRecordButton()) {
|
||||||
|
//_previewCancelled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<Api::SendProgress> ComposeControls::sendActionUpdates() const {
|
rpl::producer<SendActionUpdate> ComposeControls::sendActionUpdates() const {
|
||||||
return _sendActionUpdates.events();
|
return _sendActionUpdates.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,8 +1043,8 @@ void ComposeControls::updateSendButtonType() {
|
||||||
return Type::Save;
|
return Type::Save;
|
||||||
//} else if (_isInlineBot) {
|
//} else if (_isInlineBot) {
|
||||||
// return Type::Cancel;
|
// return Type::Cancel;
|
||||||
//} else if (showRecordButton()) {
|
} else if (showRecordButton()) {
|
||||||
// return Type::Record;
|
return Type::Record;
|
||||||
}
|
}
|
||||||
return (_mode == Mode::Normal) ? Type::Send : Type::Schedule;
|
return (_mode == Mode::Normal) ? Type::Send : Type::Schedule;
|
||||||
}();
|
}();
|
||||||
|
@ -835,6 +1067,11 @@ void ComposeControls::updateSendButtonType() {
|
||||||
// this,
|
// this,
|
||||||
// [=] { updateSendButtonType(); });
|
// [=] { updateSendButtonType(); });
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
_send->setRecordStartCallback([=] { recordStartCallback(); });
|
||||||
|
_send->setRecordStopCallback([=](bool active) { recordStopCallback(active); });
|
||||||
|
_send->setRecordUpdateCallback([=](QPoint globalPos) { recordUpdateCallback(globalPos); });
|
||||||
|
_send->setRecordAnimationCallback([=] { _wrap->update(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::updateControlsGeometry(QSize size) {
|
void ComposeControls::updateControlsGeometry(QSize size) {
|
||||||
|
@ -879,10 +1116,26 @@ void ComposeControls::updateOuterGeometry(QRect rect) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComposeControls::updateOverStates(QPoint pos) {
|
||||||
|
const auto inField = _wrap->rect().contains(pos);
|
||||||
|
if (inField != _inField && _recording) {
|
||||||
|
_inField = inField;
|
||||||
|
_send->setRecordActive(_inField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ComposeControls::paintBackground(QRect clip) {
|
void ComposeControls::paintBackground(QRect clip) {
|
||||||
Painter p(_wrap.get());
|
Painter p(_wrap.get());
|
||||||
|
|
||||||
p.fillRect(clip, st::historyComposeAreaBg);
|
p.fillRect(clip, st::historyComposeAreaBg);
|
||||||
|
if (!_field->isHidden() || _recording) {
|
||||||
|
//drawField(p, clip);
|
||||||
|
if (!_send->isHidden() && _recording) {
|
||||||
|
drawRecording(p, _send->recordActiveRatio());
|
||||||
|
}
|
||||||
|
//} else if (const auto error = writeRestriction()) {
|
||||||
|
// drawRestrictedWrite(p, *error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeControls::escape() {
|
void ComposeControls::escape() {
|
||||||
|
|
|
@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "api/api_common.h"
|
#include "api/api_common.h"
|
||||||
#include "api/api_send_progress.h"
|
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
@ -49,6 +48,10 @@ class SessionController;
|
||||||
struct SectionShow;
|
struct SectionShow;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
enum class SendProgressType;
|
||||||
|
} // namespace Api
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class FieldHeader;
|
class FieldHeader;
|
||||||
|
@ -67,6 +70,15 @@ public:
|
||||||
Api::SendOptions options;
|
Api::SendOptions options;
|
||||||
TextWithTags textWithTags;
|
TextWithTags textWithTags;
|
||||||
};
|
};
|
||||||
|
struct VoiceToSend {
|
||||||
|
QByteArray bytes;
|
||||||
|
VoiceWaveform waveform;
|
||||||
|
int duration = 0;
|
||||||
|
};
|
||||||
|
struct SendActionUpdate {
|
||||||
|
Api::SendProgressType type = Api::SendProgressType();
|
||||||
|
int progress = 0;
|
||||||
|
};
|
||||||
|
|
||||||
ComposeControls(
|
ComposeControls(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
|
@ -83,9 +95,10 @@ public:
|
||||||
[[nodiscard]] rpl::producer<int> height() const;
|
[[nodiscard]] rpl::producer<int> height() const;
|
||||||
[[nodiscard]] int heightCurrent() const;
|
[[nodiscard]] int heightCurrent() const;
|
||||||
|
|
||||||
void focus();
|
bool focus();
|
||||||
[[nodiscard]] rpl::producer<> cancelRequests() const;
|
[[nodiscard]] rpl::producer<> cancelRequests() const;
|
||||||
[[nodiscard]] rpl::producer<> sendRequests() const;
|
[[nodiscard]] rpl::producer<> sendRequests() const;
|
||||||
|
[[nodiscard]] rpl::producer<VoiceToSend> sendVoiceRequests() const;
|
||||||
[[nodiscard]] rpl::producer<MessageToEdit> editRequests() const;
|
[[nodiscard]] rpl::producer<MessageToEdit> editRequests() const;
|
||||||
[[nodiscard]] rpl::producer<> attachRequests() const;
|
[[nodiscard]] rpl::producer<> attachRequests() const;
|
||||||
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
|
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
|
||||||
|
@ -94,7 +107,7 @@ public:
|
||||||
[[nodiscard]] rpl::producer<not_null<QKeyEvent*>> keyEvents() const;
|
[[nodiscard]] rpl::producer<not_null<QKeyEvent*>> keyEvents() const;
|
||||||
[[nodiscard]] auto inlineResultChosen() const
|
[[nodiscard]] auto inlineResultChosen() const
|
||||||
-> rpl::producer<ChatHelpers::TabbedSelector::InlineChosen>;
|
-> rpl::producer<ChatHelpers::TabbedSelector::InlineChosen>;
|
||||||
[[nodiscard]] rpl::producer<Api::SendProgress> sendActionUpdates() const;
|
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
|
||||||
|
|
||||||
using MimeDataHook = Fn<bool(
|
using MimeDataHook = Fn<bool(
|
||||||
not_null<const QMimeData*> data,
|
not_null<const QMimeData*> data,
|
||||||
|
@ -140,6 +153,7 @@ private:
|
||||||
void initWebpageProcess();
|
void initWebpageProcess();
|
||||||
void updateSendButtonType();
|
void updateSendButtonType();
|
||||||
void updateHeight();
|
void updateHeight();
|
||||||
|
void updateControlsVisibility();
|
||||||
void updateControlsGeometry(QSize size);
|
void updateControlsGeometry(QSize size);
|
||||||
void updateOuterGeometry(QRect rect);
|
void updateOuterGeometry(QRect rect);
|
||||||
void paintBackground(QRect clip);
|
void paintBackground(QRect clip);
|
||||||
|
@ -152,6 +166,21 @@ private:
|
||||||
|
|
||||||
void setTextFromEditingMessage(not_null<HistoryItem*> item);
|
void setTextFromEditingMessage(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
|
void recordError();
|
||||||
|
void recordUpdated(quint16 level, int samples);
|
||||||
|
void recordDone(QByteArray result, VoiceWaveform waveform, int samples);
|
||||||
|
|
||||||
|
bool recordingAnimationCallback(crl::time now);
|
||||||
|
void stopRecording(bool send);
|
||||||
|
|
||||||
|
void recordStartCallback();
|
||||||
|
void recordStopCallback(bool active);
|
||||||
|
void recordUpdateCallback(QPoint globalPos);
|
||||||
|
|
||||||
|
bool showRecordButton() const;
|
||||||
|
void drawRecording(Painter &p, float64 recordActive);
|
||||||
|
void updateOverStates(QPoint pos);
|
||||||
|
|
||||||
const not_null<QWidget*> _parent;
|
const not_null<QWidget*> _parent;
|
||||||
const not_null<Window::SessionController*> _window;
|
const not_null<Window::SessionController*> _window;
|
||||||
History *_history = nullptr;
|
History *_history = nullptr;
|
||||||
|
@ -173,17 +202,18 @@ private:
|
||||||
rpl::event_stream<FileChosen> _fileChosen;
|
rpl::event_stream<FileChosen> _fileChosen;
|
||||||
rpl::event_stream<PhotoChosen> _photoChosen;
|
rpl::event_stream<PhotoChosen> _photoChosen;
|
||||||
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
|
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
|
||||||
rpl::event_stream<Api::SendProgress> _sendActionUpdates;
|
rpl::event_stream<SendActionUpdate> _sendActionUpdates;
|
||||||
|
rpl::event_stream<VoiceToSend> _sendVoiceRequests;
|
||||||
|
|
||||||
TextWithTags _localSavedText;
|
TextWithTags _localSavedText;
|
||||||
TextUpdateEvents _textUpdateEvents;
|
TextUpdateEvents _textUpdateEvents;
|
||||||
|
|
||||||
//bool _recording = false;
|
bool _recording = false;
|
||||||
//bool _inField = false;
|
bool _inField = false;
|
||||||
//bool _inReplyEditForward = false;
|
//bool _inReplyEditForward = false;
|
||||||
//bool _inClickable = false;
|
//bool _inClickable = false;
|
||||||
//int _recordingSamples = 0;
|
int _recordingSamples = 0;
|
||||||
//int _recordCancelWidth;
|
int _recordCancelWidth;
|
||||||
|
|
||||||
rpl::lifetime _uploaderSubscriptions;
|
rpl::lifetime _uploaderSubscriptions;
|
||||||
|
|
||||||
|
|
|
@ -204,11 +204,12 @@ RepliesWidget::RepliesWidget(
|
||||||
}, _inner->lifetime());
|
}, _inner->lifetime());
|
||||||
|
|
||||||
_composeControls->sendActionUpdates(
|
_composeControls->sendActionUpdates(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=](ComposeControls::SendActionUpdate &&data) {
|
||||||
session().sendProgressManager().update(
|
session().sendProgressManager().update(
|
||||||
_history,
|
_history,
|
||||||
_rootId,
|
_rootId,
|
||||||
Api::SendProgressType::Typing);
|
data.type,
|
||||||
|
data.progress);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_history->session().changes().messageUpdates(
|
_history->session().changes().messageUpdates(
|
||||||
|
@ -423,6 +424,11 @@ void RepliesWidget::setupComposeControls() {
|
||||||
send();
|
send();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_composeControls->sendVoiceRequests(
|
||||||
|
) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) {
|
||||||
|
sendVoice(data.bytes, data.waveform, data.duration);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
||||||
_composeControls->editRequests(
|
_composeControls->editRequests(
|
||||||
) | rpl::start_with_next([=](auto data) {
|
) | rpl::start_with_next([=](auto data) {
|
||||||
|
@ -816,6 +822,15 @@ void RepliesWidget::send() {
|
||||||
// Ui::LayerOption::KeepOther);
|
// Ui::LayerOption::KeepOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RepliesWidget::sendVoice(
|
||||||
|
QByteArray bytes,
|
||||||
|
VoiceWaveform waveform,
|
||||||
|
int duration) {
|
||||||
|
auto action = Api::SendAction(_history);
|
||||||
|
action.replyTo = replyToId();
|
||||||
|
session().api().sendVoiceMessage(bytes, waveform, duration, action);
|
||||||
|
}
|
||||||
|
|
||||||
void RepliesWidget::send(Api::SendOptions options) {
|
void RepliesWidget::send(Api::SendOptions options) {
|
||||||
const auto webPageId = _composeControls->webPageId();/* _previewCancelled
|
const auto webPageId = _composeControls->webPageId();/* _previewCancelled
|
||||||
? CancelledWebPageId
|
? CancelledWebPageId
|
||||||
|
@ -875,7 +890,7 @@ void RepliesWidget::edit(
|
||||||
if (item) {
|
if (item) {
|
||||||
Ui::show(Box<DeleteMessagesBox>(item, false));
|
Ui::show(Box<DeleteMessagesBox>(item, false));
|
||||||
} else {
|
} else {
|
||||||
_composeControls->focus();
|
doSetInnerFocus();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (!left.text.isEmpty()) {
|
} else if (!left.text.isEmpty()) {
|
||||||
|
@ -908,7 +923,7 @@ void RepliesWidget::edit(
|
||||||
} else if (err == u"MESSAGE_NOT_MODIFIED"_q) {
|
} else if (err == u"MESSAGE_NOT_MODIFIED"_q) {
|
||||||
_composeControls->cancelEditMessage();
|
_composeControls->cancelEditMessage();
|
||||||
} else if (err == u"MESSAGE_EMPTY"_q) {
|
} else if (err == u"MESSAGE_EMPTY"_q) {
|
||||||
_composeControls->focus();
|
doSetInnerFocus();
|
||||||
} else {
|
} else {
|
||||||
Ui::show(Box<InformBox>(tr::lng_edit_error(tr::now)));
|
Ui::show(Box<InformBox>(tr::lng_edit_error(tr::now)));
|
||||||
}
|
}
|
||||||
|
@ -924,7 +939,7 @@ void RepliesWidget::edit(
|
||||||
crl::guard(this, fail));
|
crl::guard(this, fail));
|
||||||
|
|
||||||
_composeControls->hidePanelsAnimated();
|
_composeControls->hidePanelsAnimated();
|
||||||
_composeControls->focus();
|
doSetInnerFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepliesWidget::sendExistingDocument(
|
void RepliesWidget::sendExistingDocument(
|
||||||
|
@ -1096,7 +1111,7 @@ void RepliesWidget::showAtEnd() {
|
||||||
void RepliesWidget::finishSending() {
|
void RepliesWidget::finishSending() {
|
||||||
_composeControls->hidePanelsAnimated();
|
_composeControls->hidePanelsAnimated();
|
||||||
//if (_previewData && _previewData->pendingTill) previewCancel();
|
//if (_previewData && _previewData->pendingTill) previewCancel();
|
||||||
_composeControls->focus();
|
doSetInnerFocus();
|
||||||
showAtEnd();
|
showAtEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1223,7 +1238,11 @@ QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &pa
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepliesWidget::doSetInnerFocus() {
|
void RepliesWidget::doSetInnerFocus() {
|
||||||
_composeControls->focus();
|
if (!_inner->getSelectedText().rich.text.isEmpty()
|
||||||
|
|| !_inner->getSelectedItems().empty()
|
||||||
|
|| !_composeControls->focus()) {
|
||||||
|
_inner->setFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RepliesWidget::showInternal(
|
bool RepliesWidget::showInternal(
|
||||||
|
|
|
@ -177,6 +177,7 @@ private:
|
||||||
|
|
||||||
void send();
|
void send();
|
||||||
void send(Api::SendOptions options);
|
void send(Api::SendOptions options);
|
||||||
|
void sendVoice(QByteArray bytes, VoiceWaveform waveform, int duration);
|
||||||
void edit(
|
void edit(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
|
|
|
@ -180,6 +180,11 @@ void ScheduledWidget::setupComposeControls() {
|
||||||
send();
|
send();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
_composeControls->sendVoiceRequests(
|
||||||
|
) | rpl::start_with_next([=](ComposeControls::VoiceToSend &&data) {
|
||||||
|
sendVoice(data.bytes, data.waveform, data.duration);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
const auto saveEditMsgRequestId = lifetime().make_state<mtpRequestId>(0);
|
||||||
_composeControls->editRequests(
|
_composeControls->editRequests(
|
||||||
) | rpl::start_with_next([=](auto data) {
|
) | rpl::start_with_next([=](auto data) {
|
||||||
|
@ -559,6 +564,28 @@ void ScheduledWidget::send(Api::SendOptions options) {
|
||||||
_composeControls->focus();
|
_composeControls->focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScheduledWidget::sendVoice(
|
||||||
|
QByteArray bytes,
|
||||||
|
VoiceWaveform waveform,
|
||||||
|
int duration) {
|
||||||
|
const auto callback = [=](Api::SendOptions options) {
|
||||||
|
sendVoice(bytes, waveform, duration, options);
|
||||||
|
};
|
||||||
|
Ui::show(
|
||||||
|
PrepareScheduleBox(this, sendMenuType(), callback),
|
||||||
|
Ui::LayerOption::KeepOther);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScheduledWidget::sendVoice(
|
||||||
|
QByteArray bytes,
|
||||||
|
VoiceWaveform waveform,
|
||||||
|
int duration,
|
||||||
|
Api::SendOptions options) {
|
||||||
|
auto action = Api::SendAction(_history);
|
||||||
|
action.options = options;
|
||||||
|
session().api().sendVoiceMessage(bytes, waveform, duration, action);
|
||||||
|
}
|
||||||
|
|
||||||
void ScheduledWidget::edit(
|
void ScheduledWidget::edit(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
|
|
|
@ -149,6 +149,12 @@ private:
|
||||||
|
|
||||||
void send();
|
void send();
|
||||||
void send(Api::SendOptions options);
|
void send(Api::SendOptions options);
|
||||||
|
void sendVoice(QByteArray bytes, VoiceWaveform waveform, int duration);
|
||||||
|
void sendVoice(
|
||||||
|
QByteArray bytes,
|
||||||
|
VoiceWaveform waveform,
|
||||||
|
int duration,
|
||||||
|
Api::SendOptions options);
|
||||||
void edit(
|
void edit(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
Api::SendOptions options,
|
Api::SendOptions options,
|
||||||
|
|
Loading…
Add table
Reference in a new issue