Save local drafts in scheduled / replies sections.

Fix inline bot switch inline in scheduled / replies sections.
This commit is contained in:
John Preston 2020-11-13 20:27:08 +03:00
parent 4a0efb9114
commit b3eb7858e6
15 changed files with 1025 additions and 390 deletions

View file

@ -2441,16 +2441,14 @@ void ApiWrap::saveCurrentDraftToCloud() {
Core::App().saveCurrentDraftsToHistories(); Core::App().saveCurrentDraftsToHistories();
for (const auto controller : _session->windows()) { for (const auto controller : _session->windows()) {
if (const auto peer = controller->activeChatCurrent().peer()) { if (const auto history = controller->activeChatCurrent().history()) {
if (const auto history = _session->data().historyLoaded(peer)) { _session->local().writeDrafts(history);
_session->local().writeDrafts(history);
const auto localDraft = history->localDraft(); const auto localDraft = history->localDraft();
const auto cloudDraft = history->cloudDraft(); const auto cloudDraft = history->cloudDraft();
if (!Data::draftsAreEqual(localDraft, cloudDraft) if (!Data::draftsAreEqual(localDraft, cloudDraft)
&& !_session->supportMode()) { && !_session->supportMode()) {
saveDraftToCloudDelayed(history); saveDraftToCloudDelayed(history);
}
} }
} }
} }

View file

@ -48,6 +48,77 @@ struct Draft {
mtpRequestId saveRequestId = 0; mtpRequestId saveRequestId = 0;
}; };
class DraftKey {
public:
[[nodiscard]] static DraftKey None() {
return 0;
}
[[nodiscard]] static DraftKey Local() {
return kLocalDraftIndex;
}
[[nodiscard]] static DraftKey LocalEdit() {
return kLocalDraftIndex + kEditDraftShift;
}
[[nodiscard]] static DraftKey Cloud() {
return kCloudDraftIndex;
}
[[nodiscard]] static DraftKey Scheduled() {
return kScheduledDraftIndex;
}
[[nodiscard]] static DraftKey ScheduledEdit() {
return kScheduledDraftIndex + kEditDraftShift;
}
[[nodiscard]] static DraftKey Replies(MsgId rootId) {
return rootId;
}
[[nodiscard]] static DraftKey RepliesEdit(MsgId rootId) {
return rootId + kEditDraftShift;
}
[[nodiscard]] static DraftKey FromSerialized(int32 value) {
return value;
}
[[nodiscard]] int32 serialize() const {
return _value;
}
inline bool operator<(const DraftKey &other) const {
return _value < other._value;
}
inline bool operator==(const DraftKey &other) const {
return _value == other._value;
}
inline bool operator>(const DraftKey &other) const {
return (other < *this);
}
inline bool operator<=(const DraftKey &other) const {
return !(other < *this);
}
inline bool operator>=(const DraftKey &other) const {
return !(*this < other);
}
inline bool operator!=(const DraftKey &other) const {
return !(*this == other);
}
inline explicit operator bool() const {
return _value != 0;
}
private:
DraftKey(int value) : _value(value) {
}
static constexpr auto kLocalDraftIndex = -1;
static constexpr auto kCloudDraftIndex = -2;
static constexpr auto kScheduledDraftIndex = -3;
static constexpr auto kEditDraftShift = ServerMaxMsgId;
int _value = 0;
};
using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
inline bool draftStringIsEmpty(const QString &text) { inline bool draftStringIsEmpty(const QString &text) {
for_const (auto ch, text) { for_const (auto ch, text) {
if (!ch.isSpace()) { if (!ch.isSpace()) {

View file

@ -179,22 +179,22 @@ void History::itemVanished(not_null<HistoryItem*> item) {
} }
} }
void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) { void History::takeLocalDraft(not_null<History*> from) {
_localDraft = std::move(draft); const auto i = from->_drafts.find(Data::DraftKey::Local());
} if (i == end(from->_drafts)) {
return;
void History::takeLocalDraft(History *from) {
if (auto &draft = from->_localDraft) {
if (!draft->textWithTags.text.isEmpty() && !_localDraft) {
_localDraft = std::move(draft);
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
_localDraft->msgId = 0;
}
from->clearLocalDraft();
session().api().saveDraftToCloudDelayed(from);
} }
auto &draft = i->second;
if (!draft->textWithTags.text.isEmpty()
&& !_drafts.contains(Data::DraftKey::Local())) {
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
draft->msgId = 0;
setLocalDraft(std::move(draft));
}
from->clearLocalDraft();
session().api().saveDraftToCloudDelayed(from);
} }
void History::createLocalDraftFromCloud() { void History::createLocalDraftFromCloud() {
@ -227,9 +227,51 @@ void History::createLocalDraftFromCloud() {
} }
} }
void History::setCloudDraft(std::unique_ptr<Data::Draft> &&draft) { Data::Draft *History::draft(Data::DraftKey key) const {
_cloudDraft = std::move(draft); if (!key) {
cloudDraftTextCache.clear(); return nullptr;
}
const auto i = _drafts.find(key);
return (i != _drafts.end()) ? i->second.get() : nullptr;
}
void History::setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft) {
if (!key) {
return;
}
const auto changingCloudDraft = (key == Data::DraftKey::Cloud());
if (changingCloudDraft) {
cloudDraftTextCache.clear();
}
if (draft) {
_drafts[key] = std::move(draft);
} else if (_drafts.remove(key) && changingCloudDraft) {
updateChatListSortPosition();
}
}
const Data::HistoryDrafts &History::draftsMap() const {
return _drafts;
}
void History::setDraftsMap(Data::HistoryDrafts &&map) {
for (auto &[key, draft] : _drafts) {
map[key] = std::move(draft);
}
_drafts = std::move(map);
}
void History::clearDraft(Data::DraftKey key) {
setDraft(key, nullptr);
}
void History::clearDrafts() {
const auto changingCloudDraft = _drafts.contains(Data::DraftKey::Cloud());
_drafts.clear();
if (changingCloudDraft) {
cloudDraftTextCache.clear();
updateChatListSortPosition();
}
} }
Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) { Data::Draft *History::createCloudDraft(const Data::Draft *fromDraft) {
@ -287,22 +329,6 @@ void History::clearSentDraftText(const QString &text) {
accumulate_max(_lastSentDraftTime, base::unixtime::now()); accumulate_max(_lastSentDraftTime, base::unixtime::now());
} }
void History::setEditDraft(std::unique_ptr<Data::Draft> &&draft) {
_editDraft = std::move(draft);
}
void History::clearLocalDraft() {
_localDraft = nullptr;
}
void History::clearCloudDraft() {
if (_cloudDraft) {
_cloudDraft = nullptr;
cloudDraftTextCache.clear();
updateChatListSortPosition();
}
}
void History::applyCloudDraft() { void History::applyCloudDraft() {
if (session().supportMode()) { if (session().supportMode()) {
updateChatListEntry(); updateChatListEntry();
@ -314,10 +340,6 @@ void History::applyCloudDraft() {
} }
} }
void History::clearEditDraft() {
_editDraft = nullptr;
}
void History::draftSavedToCloud() { void History::draftSavedToCloud() {
updateChatListEntry(); updateChatListEntry();
session().local().writeDrafts(this); session().local().writeDrafts(this);

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_types.h" #include "data/data_types.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_drafts.h"
#include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_entry.h"
#include "history/view/history_view_send_action.h" #include "history/view/history_view_send_action.h"
#include "base/observer.h" #include "base/observer.h"
@ -302,31 +303,48 @@ public:
void eraseFromUnreadMentions(MsgId msgId); void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result); void addUnreadMentionsSlice(const MTPmessages_Messages &result);
Data::Draft *draft(Data::DraftKey key) const;
void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft);
void clearDraft(Data::DraftKey key);
[[nodiscard]] const Data::HistoryDrafts &draftsMap() const;
void setDraftsMap(Data::HistoryDrafts &&map);
Data::Draft *localDraft() const { Data::Draft *localDraft() const {
return _localDraft.get(); return draft(Data::DraftKey::Local());
}
Data::Draft *localEditDraft() const {
return draft(Data::DraftKey::LocalEdit());
} }
Data::Draft *cloudDraft() const { Data::Draft *cloudDraft() const {
return _cloudDraft.get(); return draft(Data::DraftKey::Cloud());
} }
Data::Draft *editDraft() const { void setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
return _editDraft.get(); setDraft(Data::DraftKey::Local(), std::move(draft));
} }
void setLocalDraft(std::unique_ptr<Data::Draft> &&draft); void setLocalEditDraft(std::unique_ptr<Data::Draft> &&draft) {
void takeLocalDraft(History *from); setDraft(Data::DraftKey::LocalEdit(), std::move(draft));
void setCloudDraft(std::unique_ptr<Data::Draft> &&draft); }
void setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
setDraft(Data::DraftKey::Cloud(), std::move(draft));
}
void clearLocalDraft() {
clearDraft(Data::DraftKey::Local());
}
void clearCloudDraft() {
clearDraft(Data::DraftKey::Cloud());
}
void clearLocalEditDraft() {
clearDraft(Data::DraftKey::LocalEdit());
}
void clearDrafts();
Data::Draft *createCloudDraft(const Data::Draft *fromDraft); Data::Draft *createCloudDraft(const Data::Draft *fromDraft);
bool skipCloudDraft(const QString &text, MsgId replyTo, TimeId date) const; bool skipCloudDraft(const QString &text, MsgId replyTo, TimeId date) const;
void setSentDraftText(const QString &text); void setSentDraftText(const QString &text);
void clearSentDraftText(const QString &text); void clearSentDraftText(const QString &text);
void setEditDraft(std::unique_ptr<Data::Draft> &&draft); void takeLocalDraft(not_null<History*> from);
void clearLocalDraft();
void clearCloudDraft();
void applyCloudDraft(); void applyCloudDraft();
void clearEditDraft();
void draftSavedToCloud(); void draftSavedToCloud();
Data::Draft *draft() {
return _editDraft ? editDraft() : localDraft();
}
const MessageIdsList &forwardDraft() const { const MessageIdsList &forwardDraft() const {
return _forwardDraft; return _forwardDraft;
@ -560,8 +578,7 @@ private:
}; };
std::unique_ptr<BuildingBlock> _buildingFrontBlock; std::unique_ptr<BuildingBlock> _buildingFrontBlock;
std::unique_ptr<Data::Draft> _localDraft, _cloudDraft; Data::HistoryDrafts _drafts;
std::unique_ptr<Data::Draft> _editDraft;
std::optional<QString> _lastSentDraftText; std::optional<QString> _lastSentDraftText;
TimeId _lastSentDraftTime = 0; TimeId _lastSentDraftTime = 0;
MessageIdsList _forwardDraft; MessageIdsList _forwardDraft;

View file

@ -1331,8 +1331,9 @@ void HistoryWidget::saveDraftDelayed() {
} }
void HistoryWidget::saveDraft(bool delayed) { void HistoryWidget::saveDraft(bool delayed) {
if (!_peer) return; if (!_peer) {
if (delayed) { return;
} else if (delayed) {
auto ms = crl::now(); auto ms = crl::now();
if (!_saveDraftStart) { if (!_saveDraftStart) {
_saveDraftStart = ms; _saveDraftStart = ms;
@ -1341,21 +1342,21 @@ void HistoryWidget::saveDraft(bool delayed) {
return _saveDraftTimer.callOnce(kSaveDraftTimeout); return _saveDraftTimer.callOnce(kSaveDraftTimeout);
} }
} }
writeDrafts(nullptr, nullptr); writeDrafts();
} }
void HistoryWidget::saveFieldToHistoryLocalDraft() { void HistoryWidget::saveFieldToHistoryLocalDraft() {
if (!_history) return; if (!_history) return;
if (_editMsgId) { if (_editMsgId) {
_history->setEditDraft(std::make_unique<Data::Draft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId)); _history->setLocalEditDraft(std::make_unique<Data::Draft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
} else { } else {
if (_replyToId || !_field->empty()) { if (_replyToId || !_field->empty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled)); _history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
} else { } else {
_history->clearLocalDraft(); _history->clearLocalDraft();
} }
_history->clearEditDraft(); _history->clearLocalEditDraft();
} }
} }
@ -1363,74 +1364,47 @@ void HistoryWidget::saveCloudDraft() {
controller()->session().api().saveCurrentDraftToCloud(); controller()->session().api().saveCurrentDraftToCloud();
} }
void HistoryWidget::writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft) { void HistoryWidget::writeDraftTexts() {
Data::Draft *historyLocalDraft = _history ? _history->localDraft() : nullptr; Expects(_history != nullptr);
if (!localDraft && _editMsgId) localDraft = &historyLocalDraft;
bool save = _peer && (_saveDraftStart > 0); session().local().writeDrafts(
_history,
_editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
Storage::MessageDraft{
_editMsgId ? _editMsgId : _replyToId,
_field->getTextWithTags(),
_previewCancelled,
});
if (_migrated) {
_migrated->clearDrafts();
session().local().writeDrafts(_migrated);
}
}
void HistoryWidget::writeDraftCursors() {
Expects(_history != nullptr);
session().local().writeDraftCursors(
_history,
_editMsgId ? Data::DraftKey::LocalEdit() : Data::DraftKey::Local(),
MessageCursor(_field));
if (_migrated) {
_migrated->clearDrafts();
session().local().writeDraftCursors(_migrated);
}
}
void HistoryWidget::writeDrafts() {
const auto save = (_history != nullptr) && (_saveDraftStart > 0);
_saveDraftStart = 0; _saveDraftStart = 0;
_saveDraftTimer.cancel(); _saveDraftTimer.cancel();
if (_saveDraftText) {
if (save) {
Storage::MessageDraft storedLocalDraft, storedEditDraft;
if (localDraft) {
if (*localDraft) {
storedLocalDraft = Storage::MessageDraft{
(*localDraft)->msgId,
(*localDraft)->textWithTags,
(*localDraft)->previewCancelled
};
}
} else {
storedLocalDraft = Storage::MessageDraft{
_replyToId,
_field->getTextWithTags(),
_previewCancelled
};
}
if (editDraft) {
if (*editDraft) {
storedEditDraft = Storage::MessageDraft{
(*editDraft)->msgId,
(*editDraft)->textWithTags,
(*editDraft)->previewCancelled
};
}
} else if (_editMsgId) {
storedEditDraft = Storage::MessageDraft{
_editMsgId,
_field->getTextWithTags(),
_previewCancelled
};
}
session().local().writeDrafts(_peer->id, storedLocalDraft, storedEditDraft);
if (_migrated) {
session().local().writeDrafts(_migrated->peer->id, {}, {});
}
}
_saveDraftText = false;
}
if (save) { if (save) {
MessageCursor localCursor, editCursor; if (_saveDraftText) {
if (localDraft) { writeDraftTexts();
if (*localDraft) {
localCursor = (*localDraft)->cursor;
}
} else {
localCursor = MessageCursor(_field);
}
if (editDraft) {
if (*editDraft) {
editCursor = (*editDraft)->cursor;
}
} else if (_editMsgId) {
editCursor = MessageCursor(_field);
}
session().local().writeDraftCursors(_peer->id, localCursor, editCursor);
if (_migrated) {
session().local().writeDraftCursors(_migrated->peer->id, {}, {});
} }
writeDraftCursors();
} }
_saveDraftText = false;
if (!_editMsgId && !_inlineBot) { if (!_editMsgId && !_inlineBot) {
_saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout); _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout);
@ -1498,13 +1472,17 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U
false); false);
if (to.section == Section::Replies) { if (to.section == Section::Replies) {
history->setDraft(
Data::DraftKey::Replies(to.rootId),
std::move(draft));
controller()->showRepliesForMessage(history, to.rootId); controller()->showRepliesForMessage(history, to.rootId);
} else if (to.section == Section::Scheduled) {
history->setDraft(Data::DraftKey::Scheduled(), std::move(draft));
controller()->showSection(
HistoryView::ScheduledMemento(history));
} else { } else {
history->setLocalDraft(std::move(draft)); history->setLocalDraft(std::move(draft));
if (to.section == Section::Scheduled) { if (history == _history) {
controller()->showSection(
HistoryView::ScheduledMemento(history));
} else if (history == _history) {
applyDraft(); applyDraft();
} else { } else {
Ui::showPeerHistory(history->peer, ShowAtUnreadMsgId); Ui::showPeerHistory(history->peer, ShowAtUnreadMsgId);
@ -1619,9 +1597,13 @@ void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) { void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
InvokeQueued(this, [=] { updateStickersByEmoji(); }); InvokeQueued(this, [=] { updateStickersByEmoji(); });
auto draft = _history ? _history->draft() : nullptr; auto draft = !_history
? nullptr
: _history->localEditDraft()
? _history->localEditDraft()
: _history->localDraft();
auto fieldAvailable = canWriteMessage(); auto fieldAvailable = canWriteMessage();
if (!draft || (!_history->editDraft() && !fieldAvailable)) { if (!draft || (!_history->localEditDraft() && !fieldAvailable)) {
auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0); auto fieldWillBeHiddenAfterEdit = (!fieldAvailable && _editMsgId != 0);
clearFieldText(0, fieldHistoryAction); clearFieldText(0, fieldHistoryAction);
_field->setFocus(); _field->setFocus();
@ -1642,7 +1624,7 @@ void HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
_previewCancelled = draft->previewCancelled; _previewCancelled = draft->previewCancelled;
_replyEditMsg = nullptr; _replyEditMsg = nullptr;
if (auto editDraft = _history->editDraft()) { if (const auto editDraft = _history->localEditDraft()) {
_editMsgId = editDraft->msgId; _editMsgId = editDraft->msgId;
_replyToId = 0; _replyToId = 0;
} else { } else {
@ -1778,8 +1760,7 @@ void HistoryWidget::showHistory(
} }
controller()->session().api().saveCurrentDraftToCloud(); controller()->session().api().saveCurrentDraftToCloud();
if (_migrated) { if (_migrated) {
_migrated->clearLocalDraft(); // use migrated draft only once _migrated->clearDrafts(); // use migrated draft only once
_migrated->clearEditDraft();
} }
_history->showAtMsgId = _showAtMsgId; _history->showAtMsgId = _showAtMsgId;
@ -1910,11 +1891,6 @@ void HistoryWidget::showHistory(
handlePeerUpdate(); handlePeerUpdate();
session().local().readDraftsWithCursors(_history); session().local().readDraftsWithCursors(_history);
if (_migrated) {
session().local().readDraftsWithCursors(_migrated);
_migrated->clearEditDraft();
_history->takeLocalDraft(_migrated);
}
applyDraft(); applyDraft();
_send->finishAnimating(); _send->finishAnimating();
@ -3006,16 +2982,16 @@ void HistoryWidget::saveEditMsg() {
cancelEdit(); cancelEdit();
} }
})(); })();
if (auto editDraft = history->editDraft()) { if (const auto editDraft = history->localEditDraft()) {
if (editDraft->saveRequestId == requestId) { if (editDraft->saveRequestId == requestId) {
history->clearEditDraft(); history->clearLocalEditDraft();
history->session().local().writeDrafts(history); history->session().local().writeDrafts(history);
} }
} }
}; };
const auto fail = [=](const RPCError &error, mtpRequestId requestId) { const auto fail = [=](const RPCError &error, mtpRequestId requestId) {
if (const auto editDraft = history->editDraft()) { if (const auto editDraft = history->localEditDraft()) {
if (editDraft->saveRequestId == requestId) { if (editDraft->saveRequestId == requestId) {
editDraft->saveRequestId = 0; editDraft->saveRequestId = 0;
} }
@ -5563,7 +5539,7 @@ void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
editData.text.size(), editData.text.size(),
QFIXED_MAX QFIXED_MAX
}; };
_history->setEditDraft(std::make_unique<Data::Draft>( _history->setLocalEditDraft(std::make_unique<Data::Draft>(
editData, editData,
item->id, item->id,
cursor, cursor,
@ -5693,7 +5669,7 @@ void HistoryWidget::cancelEdit() {
_replyEditMsg = nullptr; _replyEditMsg = nullptr;
_editMsgId = 0; _editMsgId = 0;
_history->clearEditDraft(); _history->clearLocalEditDraft();
applyDraft(); applyDraft();
if (_saveEditMsgRequestId) { if (_saveEditMsgRequestId) {

View file

@ -41,10 +41,6 @@ class Widget;
struct ResultSelected; struct ResultSelected;
} // namespace InlineBots } // namespace InlineBots
namespace Data {
struct Draft;
} // namespace Data
namespace Support { namespace Support {
class Autocomplete; class Autocomplete;
struct Contact; struct Contact;
@ -526,8 +522,9 @@ private:
// This one is syntetic. // This one is syntetic.
void synteticScrollToY(int y); void synteticScrollToY(int y);
void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); void writeDrafts();
void writeDrafts(History *history); void writeDraftTexts();
void writeDraftCursors();
void setFieldText( void setFieldText(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
TextUpdateEvents events = 0, TextUpdateEvents events = 0,

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_drafts.h"
#include "data/data_messages.h" #include "data/data_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -54,6 +55,8 @@ namespace HistoryView {
namespace { namespace {
constexpr auto kRecordingUpdateDelta = crl::time(100); constexpr auto kRecordingUpdateDelta = crl::time(100);
constexpr auto kSaveDraftTimeout = crl::time(1000);
constexpr auto kSaveDraftAnywayTimeout = 5 * crl::time(1000);
constexpr auto kMouseEvents = { constexpr auto kMouseEvents = {
QEvent::MouseMove, QEvent::MouseMove,
QEvent::MouseButtonPress, QEvent::MouseButtonPress,
@ -107,12 +110,24 @@ public:
[[nodiscard]] MessageToEdit queryToEdit(); [[nodiscard]] MessageToEdit queryToEdit();
[[nodiscard]] WebPageId webPageId() const; [[nodiscard]] WebPageId webPageId() const;
[[nodiscard]] MsgId getDraftMessageId() const;
[[nodiscard]] rpl::producer<> editCancelled() const {
return _editCancelled.events();
}
[[nodiscard]] rpl::producer<> replyCancelled() const {
return _replyCancelled.events();
}
[[nodiscard]] rpl::producer<> previewCancelled() const {
return _previewCancelled.events();
}
[[nodiscard]] rpl::producer<bool> visibleChanged(); [[nodiscard]] rpl::producer<bool> visibleChanged();
private: private:
void updateControlsGeometry(QSize size); void updateControlsGeometry(QSize size);
void updateVisible(); void updateVisible();
void setShownMessage(HistoryItem *message); void setShownMessage(HistoryItem *message);
void resolveMessageData();
void updateShownMessageText(); void updateShownMessageText();
void paintWebPage(Painter &p); void paintWebPage(Painter &p);
@ -122,12 +137,16 @@ private:
WebPageData *data = nullptr; WebPageData *data = nullptr;
Ui::Text::String title; Ui::Text::String title;
Ui::Text::String description; Ui::Text::String description;
bool cancelled = false;
}; };
rpl::variable<QString> _title; rpl::variable<QString> _title;
rpl::variable<QString> _description; rpl::variable<QString> _description;
Preview _preview; Preview _preview;
rpl::event_stream<> _editCancelled;
rpl::event_stream<> _replyCancelled;
rpl::event_stream<> _previewCancelled;
bool hasPreview() const; bool hasPreview() const;
@ -202,10 +221,10 @@ void FieldHeader::init() {
}) | rpl::start_with_next([=](const Data::MessageUpdate &update) { }) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.flags & Data::MessageUpdate::Flag::Destroyed) { if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
if (_editMsgId.current() == update.item->fullId()) { if (_editMsgId.current() == update.item->fullId()) {
editMessage({}); _editCancelled.fire({});
} }
if (_replyToId.current() == update.item->fullId()) { if (_replyToId.current() == update.item->fullId()) {
replyToMessage({}); _replyCancelled.fire({});
} }
} else { } else {
updateShownMessageText(); updateShownMessageText();
@ -215,13 +234,14 @@ void FieldHeader::init() {
_cancel->addClickHandler([=] { _cancel->addClickHandler([=] {
if (hasPreview()) { if (hasPreview()) {
_preview = {}; _preview = {};
update(); _previewCancelled.fire({});
} else if (_editMsgId.current()) { } else if (_editMsgId.current()) {
editMessage({}); _editCancelled.fire({});
} else if (_replyToId.current()) { } else if (_replyToId.current()) {
replyToMessage({}); _replyCancelled.fire({});
} }
updateVisible(); updateVisible();
update();
}); });
_title.value( _title.value(
@ -308,6 +328,7 @@ void FieldHeader::setShownMessage(HistoryItem *item) {
} }
} else { } else {
_shownMessageText.clear(); _shownMessageText.clear();
resolveMessageData();
} }
if (isEditingMessage()) { if (isEditingMessage()) {
_shownMessageName.setText( _shownMessageName.setText(
@ -322,6 +343,34 @@ void FieldHeader::setShownMessage(HistoryItem *item) {
update(); update();
} }
void FieldHeader::resolveMessageData() {
const auto id = (isEditingMessage() ? _editMsgId : _replyToId).current();
if (!id) {
return;
}
const auto channel = id.channel
? _data->channel(id.channel).get()
: nullptr;
const auto callback = [=](ChannelData *channel, MsgId msgId) {
const auto now = (isEditingMessage()
? _editMsgId
: _replyToId).current();
if (now == id && !_shownMessage) {
if (const auto message = _data->message(channel, msgId)) {
setShownMessage(message);
} else if (isEditingMessage()) {
_editCancelled.fire({});
} else {
_replyCancelled.fire({});
}
}
};
_data->session().api().requestMessageData(
channel,
id.msg,
crl::guard(this, callback));
}
void FieldHeader::previewRequested( void FieldHeader::previewRequested(
rpl::producer<QString> title, rpl::producer<QString> title,
rpl::producer<QString> description, rpl::producer<QString> description,
@ -329,19 +378,25 @@ void FieldHeader::previewRequested(
std::move( std::move(
title title
) | rpl::start_with_next([=](const QString &t) { ) | rpl::filter([=] {
return !_preview.cancelled;
}) | start_with_next([=](const QString &t) {
_title = t; _title = t;
}, lifetime()); }, lifetime());
std::move( std::move(
description description
) | rpl::start_with_next([=](const QString &d) { ) | rpl::filter([=] {
return !_preview.cancelled;
}) | rpl::start_with_next([=](const QString &d) {
_description = d; _description = d;
}, lifetime()); }, lifetime());
std::move( std::move(
page page
) | rpl::start_with_next([=](WebPageData *p) { ) | rpl::filter([=] {
return !_preview.cancelled;
}) | rpl::start_with_next([=](WebPageData *p) {
_preview.data = p; _preview.data = p;
updateVisible(); updateVisible();
}, lifetime()); }, lifetime());
@ -392,14 +447,26 @@ void FieldHeader::paintWebPage(Painter &p) {
} }
void FieldHeader::paintEditOrReplyToMessage(Painter &p) { void FieldHeader::paintEditOrReplyToMessage(Painter &p) {
Expects(_shownMessage != nullptr);
const auto replySkip = st::historyReplySkip; const auto replySkip = st::historyReplySkip;
const auto availableWidth = width() const auto availableWidth = width()
- replySkip - replySkip
- _cancel->width() - _cancel->width()
- st::msgReplyPadding.right(); - st::msgReplyPadding.right();
if (!_shownMessage) {
p.setFont(st::msgDateFont);
p.setPen(st::historyComposeAreaFgService);
const auto top = (st::msgReplyPadding.top()
+ (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2);
p.drawText(
replySkip,
top + st::msgDateFont->ascent,
st::msgDateFont->elided(
tr::lng_profile_loading(tr::now),
availableWidth));
return;
}
if (!isEditingMessage()) { if (!isEditingMessage()) {
const auto user = _shownMessage->displayFrom() const auto user = _shownMessage->displayFrom()
? _shownMessage->displayFrom() ? _shownMessage->displayFrom()
@ -460,6 +527,10 @@ WebPageId FieldHeader::webPageId() const {
return hasPreview() ? _preview.data->id : CancelledWebPageId; return hasPreview() ? _preview.data->id : CancelledWebPageId;
} }
MsgId FieldHeader::getDraftMessageId() const {
return (isEditingMessage() ? _editMsgId : _replyToId).current().msg;
}
void FieldHeader::updateControlsGeometry(QSize size) { void FieldHeader::updateControlsGeometry(QSize size) {
_cancel->moveToRight(0, 0); _cancel->moveToRight(0, 0);
_clickableRect = QRect( _clickableRect = QRect(
@ -538,11 +609,12 @@ ComposeControls::ComposeControls(
window, window,
_send, _send,
st::historySendSize.height())) st::historySendSize.height()))
, _textUpdateEvents(TextUpdateEvent::SendTyping) { , _saveDraftTimer([=] { saveDraft(); }) {
init(); init();
} }
ComposeControls::~ComposeControls() { ComposeControls::~ComposeControls() {
saveFieldToHistoryLocalDraft();
setTabbedPanel(nullptr); setTabbedPanel(nullptr);
session().api().request(_inlineBotResolveRequestId).cancel(); session().api().request(_inlineBotResolveRequestId).cancel();
} }
@ -582,6 +654,8 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) {
session().api().requestBots(channel); session().api().requestBots(channel);
} }
} }
session().local().readDraftsWithCursors(_history);
applyDraft();
} }
void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) {
@ -752,21 +826,52 @@ TextWithTags ComposeControls::getTextWithAppliedMarkdown() const {
} }
void ComposeControls::clear() { void ComposeControls::clear() {
setText(TextWithTags()); setText({});
cancelReplyMessage(); cancelReplyMessage();
} }
void ComposeControls::setText(const TextWithTags &textWithTags) { void ComposeControls::setText(const TextWithTags &textWithTags) {
_textUpdateEvents = TextUpdateEvents(); setFieldText(textWithTags);
_field->setTextWithTags(textWithTags, Ui::InputField::HistoryAction::Clear/*fieldHistoryAction*/); }
void ComposeControls::setFieldText(
const TextWithTags &textWithTags,
TextUpdateEvents events,
FieldHistoryAction fieldHistoryAction) {
_textUpdateEvents = events;
_field->setTextWithTags(textWithTags, fieldHistoryAction);
auto cursor = _field->textCursor(); auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor); _field->setTextCursor(cursor);
_textUpdateEvents = TextUpdateEvent::SaveDraft _textUpdateEvents = TextUpdateEvent::SaveDraft
| TextUpdateEvent::SendTyping; | TextUpdateEvent::SendTyping;
//previewCancel(); _previewCancel();
//_previewCancelled = false; _previewCancelled = false;
}
void ComposeControls::saveFieldToHistoryLocalDraft() {
const auto key = draftKeyCurrent();
if (!_history || key == Data::DraftKey::None()) {
return;
}
const auto id = _header->getDraftMessageId();
if (id || !_field->empty()) {
_history->setDraft(
draftKeyCurrent(),
std::make_unique<Data::Draft>(
_field,
_header->getDraftMessageId(),
_previewCancelled));
} else {
_history->clearDraft(draftKeyCurrent());
}
}
void ComposeControls::clearFieldText(
TextUpdateEvents events,
FieldHistoryAction fieldHistoryAction) {
setFieldText({}, events, fieldHistoryAction);
} }
void ComposeControls::hidePanelsAnimated() { void ComposeControls::hidePanelsAnimated() {
@ -836,15 +941,27 @@ void ComposeControls::init() {
_header->editMsgId( _header->editMsgId(
) | rpl::start_with_next([=](const auto &id) { ) | rpl::start_with_next([=](const auto &id) {
if (_header->isEditingMessage()) {
setTextFromEditingMessage(session().data().message(id));
} else {
setText(_localSavedText);
_localSavedText = {};
}
updateSendButtonType(); updateSendButtonType();
}, _wrap->lifetime()); }, _wrap->lifetime());
_header->previewCancelled(
) | rpl::start_with_next([=] {
_previewCancelled = true;
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
}, _wrap->lifetime());
_header->editCancelled(
) | rpl::start_with_next([=] {
cancelEditMessage();
}, _wrap->lifetime());
_header->replyCancelled(
) | rpl::start_with_next([=] {
cancelReplyMessage();
}, _wrap->lifetime());
_header->visibleChanged( _header->visibleChanged(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updateHeight(); updateHeight();
@ -894,19 +1011,6 @@ void ComposeControls::drawRestrictedWrite(Painter &p, const QString &error) {
style::al_center); style::al_center);
} }
void ComposeControls::setTextFromEditingMessage(not_null<HistoryItem*> item) {
if (!_header->isEditingMessage()) {
return;
}
_localSavedText = getTextWithAppliedMarkdown();
const auto t = item->originalText();
const auto text = TextWithTags{
t.text,
TextUtilities::ConvertEntitiesToTextTags(t.entities)
};
setText(text);
}
void ComposeControls::initField() { void ComposeControls::initField() {
_field->setMaxHeight(st::historyComposeFieldMaxHeight); _field->setMaxHeight(st::historyComposeFieldMaxHeight);
updateSubmitSettings(); updateSubmitSettings();
@ -924,6 +1028,16 @@ void ComposeControls::initField() {
&_window->session()); &_window->session());
_raiseEmojiSuggestions = [=] { suggestions->raise(); }; _raiseEmojiSuggestions = [=] { suggestions->raise(); };
InitSpellchecker(_window, _field); InitSpellchecker(_window, _field);
const auto rawTextEdit = _field->rawTextEdit().get();
rpl::merge(
_field->scrollTop().changes() | rpl::to_empty,
base::qt_signal_producer(
rawTextEdit,
&QTextEdit::cursorPositionChanged)
) | rpl::start_with_next([=] {
saveDraftDelayed();
}, _field->lifetime());
} }
void ComposeControls::updateSubmitSettings() { void ComposeControls::updateSubmitSettings() {
@ -1077,7 +1191,7 @@ void ComposeControls::fieldChanged() {
} }
updateSendButtonType(); updateSendButtonType();
if (showRecordButton()) { if (showRecordButton()) {
//_previewCancelled = false; _previewCancelled = false;
} }
if (updateBotCommandShown()) { if (updateBotCommandShown()) {
updateControlsVisibility(); updateControlsVisibility();
@ -1087,6 +1201,133 @@ void ComposeControls::fieldChanged() {
updateInlineBotQuery(); updateInlineBotQuery();
updateStickersByEmoji(); updateStickersByEmoji();
}); });
if (!(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
return;
}
_saveDraftText = true;
saveDraft(true);
}
void ComposeControls::saveDraftDelayed() {
if (!(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
return;
}
saveDraft(true);
}
Data::DraftKey ComposeControls::draftKey(DraftType type) const {
using Section = Dialogs::EntryState::Section;
using Key = Data::DraftKey;
switch (_currentDialogsEntryState.section) {
case Section::History:
return (type == DraftType::Edit) ? Key::LocalEdit() : Key::Local();
case Section::Scheduled:
return (type == DraftType::Edit)
? Key::ScheduledEdit()
: Key::Scheduled();
case Section::Replies:
return (type == DraftType::Edit)
? Key::RepliesEdit(_currentDialogsEntryState.rootId)
: Key::Replies(_currentDialogsEntryState.rootId);
}
return Key::None();
}
Data::DraftKey ComposeControls::draftKeyCurrent() const {
return draftKey(isEditingMessage() ? DraftType::Edit : DraftType::Normal);
}
void ComposeControls::saveDraft(bool delayed) {
if (delayed) {
const auto now = crl::now();
if (!_saveDraftStart) {
_saveDraftStart = now;
return _saveDraftTimer.callOnce(kSaveDraftTimeout);
} else if (now - _saveDraftStart < kSaveDraftAnywayTimeout) {
return _saveDraftTimer.callOnce(kSaveDraftTimeout);
}
}
writeDrafts();
}
void ComposeControls::writeDraftTexts() {
Expects(_history != nullptr);
session().local().writeDrafts(
_history,
draftKeyCurrent(),
Storage::MessageDraft{
_header->getDraftMessageId(),
_field->getTextWithTags(),
_previewCancelled,
});
}
void ComposeControls::writeDraftCursors() {
Expects(_history != nullptr);
session().local().writeDraftCursors(
_history,
draftKeyCurrent(),
MessageCursor(_field));
}
void ComposeControls::writeDrafts() {
const auto save = (_history != nullptr)
&& (_saveDraftStart > 0)
&& (draftKeyCurrent() != Data::DraftKey::None());
_saveDraftStart = 0;
_saveDraftTimer.cancel();
if (save) {
if (_saveDraftText) {
writeDraftTexts();
}
writeDraftCursors();
}
_saveDraftText = false;
//if (!isEditingMessage() && !_inlineBot) {
// _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout);
//}
}
void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
Expects(_history != nullptr);
InvokeQueued(_autocomplete.get(), [=] { updateStickersByEmoji(); });
const auto guard = gsl::finally([&] {
updateSendButtonType();
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
});
const auto editDraft = _history->draft(draftKey(DraftType::Edit));
const auto draft = editDraft
? editDraft
: _history->draft(draftKey(DraftType::Normal));
if (!draft) {
clearFieldText(0, fieldHistoryAction);
_field->setFocus();
_header->editMessage({});
_header->replyToMessage({});
return;
}
_textUpdateEvents = 0;
setFieldText(draft->textWithTags, 0, fieldHistoryAction);
_field->setFocus();
draft->cursor.applyTo(_field);
_textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping;
_previewCancelled = draft->previewCancelled;
if (draft == editDraft) {
_header->editMessage({ _history->channelId(), draft->msgId });
_header->replyToMessage({});
} else {
_header->replyToMessage({ _history->channelId(), draft->msgId });
_header->editMessage({});
}
} }
void ComposeControls::fieldTabbed() { void ComposeControls::fieldTabbed() {
@ -1192,11 +1433,16 @@ void ComposeControls::inlineBotResolveFail(
} }
void ComposeControls::cancelInlineBot() { void ComposeControls::cancelInlineBot() {
auto &textWithTags = _field->getTextWithTags(); const auto &textWithTags = _field->getTextWithTags();
if (textWithTags.text.size() > _inlineBotUsername.size() + 2) { if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
setText({ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() }); setFieldText(
{ '@' + _inlineBotUsername + ' ', TextWithTags::Tags() },
TextUpdateEvent::SaveDraft,
Ui::InputField::HistoryAction::NewEntry);
} else { } else {
setText({}); clearFieldText(
TextUpdateEvent::SaveDraft,
Ui::InputField::HistoryAction::NewEntry);
} }
} }
@ -1487,29 +1733,101 @@ void ComposeControls::updateHeight() {
} }
void ComposeControls::editMessage(FullMsgId id) { void ComposeControls::editMessage(FullMsgId id) {
cancelEditMessage(); if (const auto item = session().data().message(id)) {
_header->editMessage(id); editMessage(item);
}
}
void ComposeControls::editMessage(not_null<HistoryItem*> item) {
Expects(_history != nullptr);
Expects(draftKeyCurrent() != Data::DraftKey::None());
if (!isEditingMessage()) {
saveFieldToHistoryLocalDraft();
}
const auto editData = PrepareEditText(item);
const auto cursor = MessageCursor{
editData.text.size(),
editData.text.size(),
QFIXED_MAX
};
_history->setDraft(
draftKey(DraftType::Edit),
std::make_unique<Data::Draft>(
editData,
item->id,
cursor,
false));
applyDraft();
if (_autocomplete) { if (_autocomplete) {
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); }); InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
} }
updateFieldPlaceholder();
} }
void ComposeControls::cancelEditMessage() { void ComposeControls::cancelEditMessage() {
_header->editMessage({}); Expects(_history != nullptr);
if (_autocomplete) { Expects(draftKeyCurrent() != Data::DraftKey::None());
InvokeQueued(_autocomplete.get(), [=] { checkAutocomplete(); });
} _history->clearDraft(draftKey(DraftType::Edit));
updateFieldPlaceholder(); applyDraft();
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
} }
void ComposeControls::replyToMessage(FullMsgId id) { void ComposeControls::replyToMessage(FullMsgId id) {
cancelReplyMessage(); Expects(_history != nullptr);
_header->replyToMessage(id); Expects(draftKeyCurrent() != Data::DraftKey::None());
if (!id) {
cancelReplyMessage();
return;
}
if (isEditingMessage()) {
const auto key = draftKey(DraftType::Normal);
if (const auto localDraft = _history->draft(key)) {
localDraft->msgId = id.msg;
} else {
_history->setDraft(
key,
std::make_unique<Data::Draft>(
TextWithTags(),
id.msg,
MessageCursor(),
false));
}
} else {
_header->replyToMessage(id);
}
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
} }
void ComposeControls::cancelReplyMessage() { void ComposeControls::cancelReplyMessage() {
Expects(_history != nullptr);
Expects(draftKeyCurrent() != Data::DraftKey::None());
const auto wasReply = replyingToMessage();
_header->replyToMessage({}); _header->replyToMessage({});
const auto key = draftKey(DraftType::Normal);
if (const auto localDraft = _history->draft(key)) {
if (localDraft->msgId) {
if (localDraft->textWithTags.text.isEmpty()) {
_history->clearDraft(key);
} else {
localDraft->msgId = 0;
}
}
}
if (wasReply) {
_saveDraftText = true;
_saveDraftStart = crl::now();
saveDraft();
}
} }
bool ComposeControls::handleCancelRequest() { bool ComposeControls::handleCancelRequest() {
@ -1544,7 +1862,6 @@ void ComposeControls::initWebpageProcess() {
using PreviewCache = std::map<QString, WebPageId>; using PreviewCache = std::map<QString, WebPageId>;
const auto previewCache = lifetime.make_state<PreviewCache>(); const auto previewCache = lifetime.make_state<PreviewCache>();
const auto previewRequest = lifetime.make_state<mtpRequestId>(0); const auto previewRequest = lifetime.make_state<mtpRequestId>(0);
const auto previewCancelled = lifetime.make_state<bool>(false);
const auto mtpSender = const auto mtpSender =
lifetime.make_state<MTP::Sender>(&_window->session().mtp()); lifetime.make_state<MTP::Sender>(&_window->session().mtp());
@ -1591,7 +1908,7 @@ void ComposeControls::initWebpageProcess() {
if (till > 0 && till <= base::unixtime::now()) { if (till > 0 && till <= base::unixtime::now()) {
till = -1; till = -1;
} }
if (links == *previewLinks && !*previewCancelled) { if (links == *previewLinks && !_previewCancelled) {
*previewData = (page->id && page->pendingTill >= 0) *previewData = (page->id && page->pendingTill >= 0)
? page.get() ? page.get()
: nullptr; : nullptr;
@ -1599,7 +1916,7 @@ void ComposeControls::initWebpageProcess() {
} }
}, [=](const MTPDmessageMediaEmpty &d) { }, [=](const MTPDmessageMediaEmpty &d) {
previewCache->insert({ links, 0 }); previewCache->insert({ links, 0 });
if (links == *previewLinks && !*previewCancelled) { if (links == *previewLinks && !_previewCancelled) {
*previewData = nullptr; *previewData = nullptr;
updatePreview(); updatePreview();
} }
@ -1607,7 +1924,7 @@ void ComposeControls::initWebpageProcess() {
}); });
}); });
const auto previewCancel = [=] { _previewCancel = [=] {
mtpSender->request(base::take(*previewRequest)).cancel(); mtpSender->request(base::take(*previewRequest)).cancel();
*previewData = nullptr; *previewData = nullptr;
previewLinks->clear(); previewLinks->clear();
@ -1628,8 +1945,8 @@ void ComposeControls::initWebpageProcess() {
const auto checkPreview = crl::guard(_wrap.get(), [=] { const auto checkPreview = crl::guard(_wrap.get(), [=] {
const auto previewRestricted = peer const auto previewRestricted = peer
&& peer->amRestricted(ChatRestriction::f_embed_links); && peer->amRestricted(ChatRestriction::f_embed_links);
if (/*_previewCancelled ||*/ previewRestricted) { if (_previewCancelled || previewRestricted) {
previewCancel(); _previewCancel();
return; return;
} }
const auto newLinks = parsedLinks->join(' '); const auto newLinks = parsedLinks->join(' ');
@ -1640,7 +1957,7 @@ void ComposeControls::initWebpageProcess() {
*previewLinks = newLinks; *previewLinks = newLinks;
if (previewLinks->isEmpty()) { if (previewLinks->isEmpty()) {
if (ShowWebPagePreview(*previewData)) { if (ShowWebPagePreview(*previewData)) {
previewCancel(); _previewCancel();
} }
} else { } else {
const auto i = previewCache->find(*previewLinks); const auto i = previewCache->find(*previewLinks);
@ -1650,7 +1967,7 @@ void ComposeControls::initWebpageProcess() {
*previewData = _history->owner().webpage(i->second); *previewData = _history->owner().webpage(i->second);
updatePreview(); updatePreview();
} else if (ShowWebPagePreview(*previewData)) { } else if (ShowWebPagePreview(*previewData)) {
previewCancel(); _previewCancel();
} }
} }
}); });

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/required.h" #include "base/required.h"
#include "api/api_common.h" #include "api/api_common.h"
#include "base/unique_qptr.h" #include "base/unique_qptr.h"
#include "base/timer.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "history/view/controls/compose_controls_common.h" #include "history/view/controls/compose_controls_common.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
@ -27,6 +28,8 @@ class TabbedSelector;
namespace Data { namespace Data {
struct MessagePosition; struct MessagePosition;
struct Draft;
class DraftKey;
} // namespace Data } // namespace Data
namespace InlineBots { namespace InlineBots {
@ -74,6 +77,7 @@ public:
using VoiceToSend = Controls::VoiceToSend; using VoiceToSend = Controls::VoiceToSend;
using SendActionUpdate = Controls::SendActionUpdate; using SendActionUpdate = Controls::SendActionUpdate;
using SetHistoryArgs = Controls::SetHistoryArgs; using SetHistoryArgs = Controls::SetHistoryArgs;
using FieldHistoryAction = Ui::InputField::HistoryAction;
enum class Mode { enum class Mode {
Normal, Normal,
@ -148,11 +152,18 @@ public:
[[nodiscard]] bool isLockPresent() const; [[nodiscard]] bool isLockPresent() const;
[[nodiscard]] bool isRecording() const; [[nodiscard]] bool isRecording() const;
void applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
private: private:
enum class TextUpdateEvent { enum class TextUpdateEvent {
SaveDraft = (1 << 0), SaveDraft = (1 << 0),
SendTyping = (1 << 1), SendTyping = (1 << 1),
}; };
enum class DraftType {
Normal,
Edit,
};
using TextUpdateEvents = base::flags<TextUpdateEvent>; using TextUpdateEvents = base::flags<TextUpdateEvent>;
friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
@ -177,6 +188,7 @@ private:
void checkAutocomplete(); void checkAutocomplete();
void updateStickersByEmoji(); void updateStickersByEmoji();
void updateFieldPlaceholder(); void updateFieldPlaceholder();
void editMessage(not_null<HistoryItem*> item);
void escape(); void escape();
void fieldChanged(); void fieldChanged();
@ -185,11 +197,8 @@ private:
void createTabbedPanel(); void createTabbedPanel();
void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel); void setTabbedPanel(std::unique_ptr<ChatHelpers::TabbedPanel> panel);
void setTextFromEditingMessage(not_null<HistoryItem*> item);
bool showRecordButton() const; bool showRecordButton() const;
void drawRestrictedWrite(Painter &p, const QString &error); void drawRestrictedWrite(Painter &p, const QString &error);
void updateOverStates(QPoint pos);
bool updateBotCommandShown(); bool updateBotCommandShown();
void cancelInlineBot(); void cancelInlineBot();
@ -205,6 +214,24 @@ private:
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result); void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
void inlineBotResolveFail(const RPCError &error, const QString &username); void inlineBotResolveFail(const RPCError &error, const QString &username);
[[nodiscard]] Data::DraftKey draftKey(
DraftType type = DraftType::Normal) const;
[[nodiscard]] Data::DraftKey draftKeyCurrent() const;
void saveDraft(bool delayed = false);
void saveDraftDelayed();
void writeDrafts();
void writeDraftTexts();
void writeDraftCursors();
void setFieldText(
const TextWithTags &textWithTags,
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void clearFieldText(
TextUpdateEvents events = 0,
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void saveFieldToHistoryLocalDraft();
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;
@ -238,12 +265,14 @@ private:
rpl::event_stream<SendActionUpdate> _sendActionUpdates; rpl::event_stream<SendActionUpdate> _sendActionUpdates;
rpl::event_stream<QString> _sendCommandRequests; rpl::event_stream<QString> _sendCommandRequests;
TextWithTags _localSavedText; TextUpdateEvents _textUpdateEvents = TextUpdateEvents()
TextUpdateEvents _textUpdateEvents; | TextUpdateEvent::SaveDraft
| TextUpdateEvent::SendTyping;
Dialogs::EntryState _currentDialogsEntryState; Dialogs::EntryState _currentDialogsEntryState;
//bool _inReplyEditForward = false; crl::time _saveDraftStart = 0;
//bool _inClickable = false; bool _saveDraftText = false;
base::Timer _saveDraftTimer;
UserData *_inlineBot = nullptr; UserData *_inlineBot = nullptr;
QString _inlineBotUsername; QString _inlineBotUsername;
@ -252,6 +281,9 @@ private:
bool _isInlineBot = false; bool _isInlineBot = false;
bool _botCommandShown = false; bool _botCommandShown = false;
Fn<void()> _previewCancel;
bool _previewCancelled = false;
rpl::lifetime _uploaderSubscriptions; rpl::lifetime _uploaderSubscriptions;
Fn<void()> _raiseEmojiSuggestions; Fn<void()> _raiseEmojiSuggestions;

View file

@ -1133,7 +1133,7 @@ void RepliesWidget::refreshTopBarActiveChat() {
.key = _history, .key = _history,
.section = Dialogs::EntryState::Section::Replies, .section = Dialogs::EntryState::Section::Replies,
.rootId = _rootId, .rootId = _rootId,
.currentReplyToId = replyToId(), .currentReplyToId = _composeControls->replyingToMessage().msg,
}; };
_topBar->setActiveChat(state, _sendAction.get()); _topBar->setActiveChat(state, _sendAction.get());
_composeControls->setCurrentDialogsEntryState(state); _composeControls->setCurrentDialogsEntryState(state);

View file

@ -533,7 +533,7 @@ bool MainWidget::shareUrl(
auto history = peer->owner().history(peer); auto history = peer->owner().history(peer);
history->setLocalDraft( history->setLocalDraft(
std::make_unique<Data::Draft>(textWithTags, 0, cursor, false)); std::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
history->clearEditDraft(); history->clearLocalEditDraft();
if (_history->peer() == peer) { if (_history->peer() == peer) {
_history->applyDraft(); _history->applyDraft();
} else { } else {
@ -562,7 +562,7 @@ bool MainWidget::inlineSwitchChosen(PeerId peerId, const QString &botAndQuery) {
TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() }; TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() };
MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX }; MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX };
h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false)); h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
h->clearEditDraft(); h->clearLocalEditDraft();
const auto opened = _history->peer() && (_history->peer() == peer); const auto opened = _history->peer() && (_history->peer() == peer);
if (opened) { if (opened) {
_history->applyDraft(); _history->applyDraft();

View file

@ -42,6 +42,8 @@ TitleWidget::TitleWidget(QWidget *parent)
}); });
_close->setPointerCursor(false); _close->setPointerCursor(false);
window()->windowHandle()->setFlag(Qt::FramelessWindowHint, true);
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
resize(width(), _st.height); resize(width(), _st.height);
} }

View file

@ -142,30 +142,42 @@ bool EventFilter::customWindowFrameEvent(
if (result) *result = 0; if (result) *result = 0;
} return true; } return true;
case WM_SHOWWINDOW: {
SetWindowLongPtr(
hWnd,
GWL_STYLE,
WS_POPUP
| WS_THICKFRAME
| WS_CAPTION
| WS_SYSMENU
| WS_MAXIMIZEBOX
| WS_MINIMIZEBOX);
} return false;
case WM_NCCALCSIZE: { case WM_NCCALCSIZE: {
WINDOWPLACEMENT wp; //WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT); //wp.length = sizeof(WINDOWPLACEMENT);
if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { //if (GetWindowPlacement(hWnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam; // LPNCCALCSIZE_PARAMS params = (LPNCCALCSIZE_PARAMS)lParam;
LPRECT r = (wParam == TRUE) ? &params->rgrc[0] : (LPRECT)lParam; // LPRECT r = (wParam == TRUE) ? &params->rgrc[0] : (LPRECT)lParam;
HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST); // HMONITOR hMonitor = MonitorFromPoint({ (r->left + r->right) / 2, (r->top + r->bottom) / 2 }, MONITOR_DEFAULTTONEAREST);
if (hMonitor) { // if (hMonitor) {
MONITORINFO mi; // MONITORINFO mi;
mi.cbSize = sizeof(mi); // mi.cbSize = sizeof(mi);
if (GetMonitorInfo(hMonitor, &mi)) { // if (GetMonitorInfo(hMonitor, &mi)) {
*r = mi.rcWork; // *r = mi.rcWork;
UINT uEdge = (UINT)-1; // UINT uEdge = (UINT)-1;
if (IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) { // if (IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) {
switch (uEdge) { // switch (uEdge) {
case ABE_LEFT: r->left += 1; break; // case ABE_LEFT: r->left += 1; break;
case ABE_RIGHT: r->right -= 1; break; // case ABE_RIGHT: r->right -= 1; break;
case ABE_TOP: r->top += 1; break; // case ABE_TOP: r->top += 1; break;
case ABE_BOTTOM: r->bottom -= 1; break; // case ABE_BOTTOM: r->bottom -= 1; break;
} // }
} // }
} // }
} // }
} //}
if (result) *result = 0; if (result) *result = 0;
return true; return true;
} }

View file

@ -51,6 +51,7 @@ constexpr auto kSinglePeerTypeChat = qint32(2);
constexpr auto kSinglePeerTypeChannel = qint32(3); constexpr auto kSinglePeerTypeChannel = qint32(3);
constexpr auto kSinglePeerTypeSelf = qint32(4); constexpr auto kSinglePeerTypeSelf = qint32(4);
constexpr auto kSinglePeerTypeEmpty = qint32(0); constexpr auto kSinglePeerTypeEmpty = qint32(0);
constexpr auto kMultiDraftTag = quint64(0xFFFFFFFFFFFFFF01ULL);
enum { // Local Storage Keys enum { // Local Storage Keys
lskUserMap = 0x00, lskUserMap = 0x00,
@ -936,80 +937,200 @@ std::unique_ptr<MTP::Config> Account::readMtpConfig() {
return MTP::Config::FromSerialized(serialized); return MTP::Config::FromSerialized(serialized);
} }
void Account::writeDrafts(not_null<History*> history) { template <typename Callback>
Storage::MessageDraft storedLocalDraft, storedEditDraft; void EnumerateDrafts(
MessageCursor localCursor, editCursor; const Data::HistoryDrafts &map,
if (const auto localDraft = history->localDraft()) { Data::Draft *cloudDraft,
if (_owner->session().supportMode() bool supportMode,
|| !Data::draftsAreEqual(localDraft, history->cloudDraft())) { Data::DraftKey replaceKey,
storedLocalDraft = Storage::MessageDraft{ const MessageDraft &replaceDraft,
localDraft->msgId, const MessageCursor &replaceCursor,
localDraft->textWithTags, Callback &&callback) {
localDraft->previewCancelled for (const auto &[key, draft] : map) {
}; if (key == Data::DraftKey::Cloud() || key == replaceKey) {
localCursor = localDraft->cursor; continue;
} else if (key == Data::DraftKey::Local()
&& !supportMode
&& Data::draftsAreEqual(draft.get(), cloudDraft)) {
continue;
} }
callback(
key,
draft->msgId,
draft->textWithTags,
draft->previewCancelled,
draft->cursor);
} }
if (const auto editDraft = history->editDraft()) { if (replaceKey
storedEditDraft = Storage::MessageDraft{ && (replaceDraft.msgId
editDraft->msgId, || !replaceDraft.textWithTags.text.isEmpty()
editDraft->textWithTags, || replaceCursor != MessageCursor())) {
editDraft->previewCancelled callback(
}; replaceKey,
editCursor = editDraft->cursor; replaceDraft.msgId,
replaceDraft.textWithTags,
replaceDraft.previewCancelled,
replaceCursor);
} }
writeDrafts(
history->peer->id,
storedLocalDraft,
storedEditDraft);
writeDraftCursors(history->peer->id, localCursor, editCursor);
} }
void Account::writeDrafts( void Account::writeDrafts(
const PeerId &peer, not_null<History*> history,
const MessageDraft &localDraft, Data::DraftKey replaceKey,
const MessageDraft &editDraft) { MessageDraft replaceDraft) {
if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { const auto peerId = history->peer->id;
auto i = _draftsMap.find(peer); const auto &map = history->draftsMap();
const auto cloudIt = map.find(Data::DraftKey::Cloud());
const auto cloudDraft = (cloudIt != end(map))
? cloudIt->second.get()
: nullptr;
const auto supportMode = _owner->session().supportMode();
auto count = 0;
EnumerateDrafts(
map,
cloudDraft,
supportMode,
replaceKey,
replaceDraft,
MessageCursor(),
[&](auto&&...) { ++count; });
if (!count) {
auto i = _draftsMap.find(peerId);
if (i != _draftsMap.cend()) { if (i != _draftsMap.cend()) {
ClearKey(i->second, _basePath); ClearKey(i->second, _basePath);
_draftsMap.erase(i); _draftsMap.erase(i);
writeMapDelayed(); writeMapDelayed();
} }
_draftsNotReadMap.remove(peer); _draftsNotReadMap.remove(peerId);
} else { return;
auto i = _draftsMap.find(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.emplace(peer, GenerateKey(_basePath)).first;
writeMapQueued();
}
auto msgTags = TextUtilities::SerializeTags(
localDraft.textWithTags.tags);
auto editTags = TextUtilities::SerializeTags(
editDraft.textWithTags.tags);
int size = sizeof(quint64);
size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32);
size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32);
EncryptedDescriptor data(size);
data.stream << quint64(peer);
data.stream << localDraft.textWithTags.text << msgTags;
data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0);
data.stream << editDraft.textWithTags.text << editTags;
data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0);
FileWriteDescriptor file(i->second, _basePath);
file.writeEncrypted(data, _localKey);
_draftsNotReadMap.remove(peer);
} }
auto i = _draftsMap.find(peerId);
if (i == _draftsMap.cend()) {
i = _draftsMap.emplace(peerId, GenerateKey(_basePath)).first;
writeMapQueued();
}
auto size = int(sizeof(quint64) * 2 + sizeof(quint32));
const auto sizeCallback = [&](
auto&&, // key
MsgId, // msgId
const TextWithTags &text,
bool, // previewCancelled
auto&&) { // cursor
size += sizeof(qint32) // key
+ Serialize::stringSize(text.text)
+ sizeof(quint32) + TextUtilities::SerializeTagsSize(text.tags)
+ 2 * sizeof(qint32); // msgId, previewCancelled
};
EnumerateDrafts(
map,
cloudDraft,
supportMode,
replaceKey,
replaceDraft,
MessageCursor(),
sizeCallback);
EncryptedDescriptor data(size);
data.stream
<< quint64(kMultiDraftTag)
<< quint64(peerId)
<< quint32(count);
const auto writeCallback = [&](
const Data::DraftKey &key,
MsgId msgId,
const TextWithTags &text,
bool previewCancelled,
auto&&) { // cursor
data.stream
<< key.serialize()
<< text.text
<< TextUtilities::SerializeTags(text.tags)
<< qint32(msgId)
<< qint32(previewCancelled ? 1 : 0);
};
EnumerateDrafts(
map,
cloudDraft,
supportMode,
replaceKey,
replaceDraft,
MessageCursor(),
writeCallback);
FileWriteDescriptor file(i->second, _basePath);
file.writeEncrypted(data, _localKey);
_draftsNotReadMap.remove(peerId);
} }
void Account::clearDraftCursors(const PeerId &peer) { void Account::writeDraftCursors(
const auto i = _draftCursorsMap.find(peer); not_null<History*> history,
Data::DraftKey replaceKey,
MessageCursor replaceCursor) {
const auto peerId = history->peer->id;
const auto &map = history->draftsMap();
const auto cloudIt = map.find(Data::DraftKey::Cloud());
const auto cloudDraft = (cloudIt != end(map))
? cloudIt->second.get()
: nullptr;
const auto supportMode = _owner->session().supportMode();
auto count = 0;
EnumerateDrafts(
map,
cloudDraft,
supportMode,
replaceKey,
MessageDraft(),
replaceCursor,
[&](auto&&...) { ++count; });
if (!count) {
clearDraftCursors(peerId);
return;
}
auto i = _draftCursorsMap.find(peerId);
if (i == _draftCursorsMap.cend()) {
i = _draftCursorsMap.emplace(peerId, GenerateKey(_basePath)).first;
writeMapQueued();
}
auto size = int(sizeof(quint64) * 2 + sizeof(quint32) * 4);
EncryptedDescriptor data(size);
data.stream
<< quint64(kMultiDraftTag)
<< quint64(peerId)
<< quint32(count);
const auto writeCallback = [&](
auto&&, // key
MsgId, // msgId
auto&&, // text
bool, // previewCancelled
const MessageCursor &cursor) { // cursor
data.stream
<< qint32(cursor.position)
<< qint32(cursor.anchor)
<< qint32(cursor.scroll);
};
EnumerateDrafts(
map,
cloudDraft,
supportMode,
replaceKey,
MessageDraft(),
replaceCursor,
writeCallback);
FileWriteDescriptor file(i->second, _basePath);
file.writeEncrypted(data, _localKey);
}
void Account::clearDraftCursors(PeerId peerId) {
const auto i = _draftCursorsMap.find(peerId);
if (i != _draftCursorsMap.cend()) { if (i != _draftCursorsMap.cend()) {
ClearKey(i->second, _basePath); ClearKey(i->second, _basePath);
_draftCursorsMap.erase(i); _draftCursorsMap.erase(i);
@ -1017,21 +1138,44 @@ void Account::clearDraftCursors(const PeerId &peer) {
} }
} }
void Account::readDraftCursors( void Account::readDraftCursors(PeerId peerId, Data::HistoryDrafts &map) {
const PeerId &peer, const auto j = _draftCursorsMap.find(peerId);
MessageCursor &localCursor,
MessageCursor &editCursor) {
const auto j = _draftCursorsMap.find(peer);
if (j == _draftCursorsMap.cend()) { if (j == _draftCursorsMap.cend()) {
return; return;
} }
FileReadDescriptor draft; FileReadDescriptor draft;
if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) { if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) {
clearDraftCursors(peer); clearDraftCursors(peerId);
return; return;
} }
quint64 draftPeer; quint64 tag = 0;
draft.stream >> tag;
if (tag != kMultiDraftTag) {
readDraftCursorsLegacy(peerId, draft, tag, map);
return;
}
quint64 draftPeer = 0;
quint32 count = 0;
draft.stream >> draftPeer >> count;
if (!count || count > 1000 || draftPeer != peerId) {
clearDraftCursors(peerId);
return;
}
for (auto i = 0; i != count; ++i) {
qint32 position = 0, anchor = 0, scroll = QFIXED_MAX;
draft.stream >> position >> anchor >> scroll;
if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) {
i->second->cursor = MessageCursor(position, anchor, scroll);
}
}
}
void Account::readDraftCursorsLegacy(
PeerId peerId,
details::FileReadDescriptor &draft,
quint64 draftPeer,
Data::HistoryDrafts &map) {
qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX; qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX;
qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX; qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX;
draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll; draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll;
@ -1039,40 +1183,109 @@ void Account::readDraftCursors(
draft.stream >> editPosition >> editAnchor >> editScroll; draft.stream >> editPosition >> editAnchor >> editScroll;
} }
if (draftPeer != peer) { if (draftPeer != peerId) {
clearDraftCursors(peer); clearDraftCursors(peerId);
return; return;
} }
localCursor = MessageCursor(localPosition, localAnchor, localScroll); if (const auto i = map.find(Data::DraftKey::Local()); i != end(map)) {
editCursor = MessageCursor(editPosition, editAnchor, editScroll); i->second->cursor = MessageCursor(
localPosition,
localAnchor,
localScroll);
}
if (const auto i = map.find(Data::DraftKey::LocalEdit()); i != end(map)) {
i->second->cursor = MessageCursor(
editPosition,
editAnchor,
editScroll);
}
} }
void Account::readDraftsWithCursors(not_null<History*> history) { void Account::readDraftsWithCursors(not_null<History*> history) {
PeerId peer = history->peer->id; const auto guard = gsl::finally([&] {
if (!_draftsNotReadMap.remove(peer)) { if (const auto migrated = history->migrateFrom()) {
clearDraftCursors(peer); readDraftsWithCursors(migrated);
migrated->clearLocalEditDraft();
history->takeLocalDraft(migrated);
}
});
PeerId peerId = history->peer->id;
if (!_draftsNotReadMap.remove(peerId)) {
clearDraftCursors(peerId);
return; return;
} }
const auto j = _draftsMap.find(peer); const auto j = _draftsMap.find(peerId);
if (j == _draftsMap.cend()) { if (j == _draftsMap.cend()) {
clearDraftCursors(peer); clearDraftCursors(peerId);
return; return;
} }
FileReadDescriptor draft; FileReadDescriptor draft;
if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) { if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) {
ClearKey(j->second, _basePath); ClearKey(j->second, _basePath);
_draftsMap.erase(j); _draftsMap.erase(j);
clearDraftCursors(peer); clearDraftCursors(peerId);
return; return;
} }
quint64 tag = 0;
draft.stream >> tag;
if (tag != kMultiDraftTag) {
readDraftsWithCursorsLegacy(history, draft, tag);
return;
}
quint32 count = 0;
quint64 draftPeer = 0; quint64 draftPeer = 0;
draft.stream >> draftPeer >> count;
if (!count || count > 1000 || draftPeer != peerId) {
ClearKey(j->second, _basePath);
_draftsMap.erase(j);
clearDraftCursors(peerId);
return;
}
auto map = Data::HistoryDrafts();
for (auto i = 0; i != count; ++i) {
TextWithTags data;
QByteArray tagsSerialized;
qint32 keyValue = 0, messageId = 0, previewCancelled = 0;
draft.stream
>> keyValue
>> data.text
>> tagsSerialized
>> messageId
>> previewCancelled;
data.tags = TextUtilities::DeserializeTags(
tagsSerialized,
data.text.size());
const auto key = Data::DraftKey::FromSerialized(keyValue);
if (key && key != Data::DraftKey::Cloud()) {
map.emplace(key, std::make_unique<Data::Draft>(
data,
messageId,
MessageCursor(),
previewCancelled));
}
}
if (draft.stream.status() != QDataStream::Ok) {
ClearKey(j->second, _basePath);
_draftsMap.erase(j);
clearDraftCursors(peerId);
return;
}
readDraftCursors(peerId, map);
history->setDraftsMap(std::move(map));
}
void Account::readDraftsWithCursorsLegacy(
not_null<History*> history,
details::FileReadDescriptor &draft,
quint64 draftPeer) {
TextWithTags msgData, editData; TextWithTags msgData, editData;
QByteArray msgTagsSerialized, editTagsSerialized; QByteArray msgTagsSerialized, editTagsSerialized;
qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0; qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0;
draft.stream >> draftPeer >> msgData.text; draft.stream >> msgData.text;
if (draft.version >= 9048) { if (draft.version >= 9048) {
draft.stream >> msgTagsSerialized; draft.stream >> msgTagsSerialized;
} }
@ -1089,10 +1302,14 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
} }
} }
} }
if (draftPeer != peer) { const auto peerId = history->peer->id;
ClearKey(j->second, _basePath); if (draftPeer != peerId) {
_draftsMap.erase(j); const auto j = _draftsMap.find(peerId);
clearDraftCursors(peer); if (j != _draftsMap.cend()) {
ClearKey(j->second, _basePath);
_draftsMap.erase(j);
}
clearDraftCursors(peerId);
return; return;
} }
@ -1103,65 +1320,30 @@ void Account::readDraftsWithCursors(not_null<History*> history) {
editTagsSerialized, editTagsSerialized,
editData.text.size()); editData.text.size());
MessageCursor msgCursor, editCursor; auto map = base::flat_map<Data::DraftKey, std::unique_ptr<Data::Draft>>();
readDraftCursors(peer, msgCursor, editCursor); if (!msgData.text.isEmpty() || msgReplyTo) {
map.emplace(Data::DraftKey::Local(), std::make_unique<Data::Draft>(
if (!history->localDraft()) { msgData,
if (msgData.text.isEmpty() && !msgReplyTo) { msgReplyTo,
history->clearLocalDraft(); MessageCursor(),
} else { msgPreviewCancelled));
history->setLocalDraft(std::make_unique<Data::Draft>(
msgData,
msgReplyTo,
msgCursor,
msgPreviewCancelled));
}
} }
if (!editMsgId) { if (editMsgId) {
history->clearEditDraft(); map.emplace(Data::DraftKey::LocalEdit(), std::make_unique<Data::Draft>(
} else {
history->setEditDraft(std::make_unique<Data::Draft>(
editData, editData,
editMsgId, editMsgId,
editCursor, MessageCursor(),
editPreviewCancelled)); editPreviewCancelled));
} }
readDraftCursors(peerId, map);
history->setDraftsMap(std::move(map));
} }
void Account::writeDraftCursors( bool Account::hasDraftCursors(PeerId peer) {
const PeerId &peer,
const MessageCursor &msgCursor,
const MessageCursor &editCursor) {
if (msgCursor == MessageCursor() && editCursor == MessageCursor()) {
clearDraftCursors(peer);
} else {
auto i = _draftCursorsMap.find(peer);
if (i == _draftCursorsMap.cend()) {
i = _draftCursorsMap.emplace(peer, GenerateKey(_basePath)).first;
writeMapQueued();
}
EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3);
data.stream
<< quint64(peer)
<< qint32(msgCursor.position)
<< qint32(msgCursor.anchor)
<< qint32(msgCursor.scroll);
data.stream
<< qint32(editCursor.position)
<< qint32(editCursor.anchor)
<< qint32(editCursor.scroll);
FileWriteDescriptor file(i->second, _basePath);
file.writeEncrypted(data, _localKey);
}
}
bool Account::hasDraftCursors(const PeerId &peer) {
return _draftCursorsMap.contains(peer); return _draftCursorsMap.contains(peer);
} }
bool Account::hasDraft(const PeerId &peer) { bool Account::hasDraft(PeerId peer) {
return _draftsMap.contains(peer); return _draftsMap.contains(peer);
} }

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include "storage/cache/storage_cache_database.h" #include "storage/cache/storage_cache_database.h"
#include "data/stickers/data_stickers_set.h" #include "data/stickers/data_stickers_set.h"
#include "data/data_drafts.h"
class History; class History;
@ -39,6 +40,7 @@ using AuthKeyPtr = std::shared_ptr<AuthKey>;
namespace Storage { namespace Storage {
namespace details { namespace details {
struct ReadSettingsContext; struct ReadSettingsContext;
struct FileReadDescriptor;
} // namespace details } // namespace details
class EncryptionKey; class EncryptionKey;
@ -76,18 +78,17 @@ public:
void writeMtpData(); void writeMtpData();
void writeMtpConfig(); void writeMtpConfig();
void writeDrafts(not_null<History*> history);
void writeDrafts( void writeDrafts(
const PeerId &peer, not_null<History*> history,
const MessageDraft &localDraft, Data::DraftKey replaceKey = Data::DraftKey::None(),
const MessageDraft &editDraft); MessageDraft replaceDraft = MessageDraft());
void readDraftsWithCursors(not_null<History*> history); void readDraftsWithCursors(not_null<History*> history);
void writeDraftCursors( void writeDraftCursors(
const PeerId &peer, not_null<History*> history,
const MessageCursor &localCursor, Data::DraftKey replaceKey = Data::DraftKey::None(),
const MessageCursor &editCursor); MessageCursor replaceCursor = MessageCursor());
[[nodiscard]] bool hasDraftCursors(const PeerId &peer); [[nodiscard]] bool hasDraftCursors(PeerId peerId);
[[nodiscard]] bool hasDraft(const PeerId &peer); [[nodiscard]] bool hasDraft(PeerId peerId);
void writeFileLocation(MediaKey location, const Core::FileLocation &local); void writeFileLocation(MediaKey location, const Core::FileLocation &local);
[[nodiscard]] Core::FileLocation readFileLocation(MediaKey location); [[nodiscard]] Core::FileLocation readFileLocation(MediaKey location);
@ -182,11 +183,17 @@ private:
std::unique_ptr<Main::SessionSettings> applyReadContext( std::unique_ptr<Main::SessionSettings> applyReadContext(
details::ReadSettingsContext &&context); details::ReadSettingsContext &&context);
void readDraftCursors( void readDraftCursors(PeerId peerId, Data::HistoryDrafts &map);
const PeerId &peer, void readDraftCursorsLegacy(
MessageCursor &localCursor, PeerId peerId,
MessageCursor &editCursor); details::FileReadDescriptor &draft,
void clearDraftCursors(const PeerId &peer); quint64 draftPeer,
Data::HistoryDrafts &map);
void clearDraftCursors(PeerId peerId);
void readDraftsWithCursorsLegacy(
not_null<History*> history,
details::FileReadDescriptor &draft,
quint64 draftPeer);
void writeStickerSet( void writeStickerSet(
QDataStream &stream, QDataStream &stream,

View file

@ -617,7 +617,9 @@ void Filler::addPollAction(not_null<PeerData*> peer) {
? Api::SendType::Scheduled ? Api::SendType::Scheduled
: Api::SendType::Normal; : Api::SendType::Normal;
const auto flag = PollData::Flags(); const auto flag = PollData::Flags();
const auto replyToId = _request.currentReplyToId; const auto replyToId = _request.currentReplyToId
? _request.currentReplyToId
: _request.rootId;
auto callback = [=] { auto callback = [=] {
PeerMenuCreatePoll( PeerMenuCreatePoll(
controller, controller,