Cloud stored message drafts support added.

This commit is contained in:
John Preston 2016-06-03 21:24:27 +03:00
parent 307e529ccf
commit cd2615d8d0
16 changed files with 431 additions and 151 deletions

View File

@ -705,6 +705,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_willbe_history" = "Please select a chat to start messaging"; "lng_willbe_history" = "Please select a chat to start messaging";
"lng_message_with_from" = "[c]{from}:[/c] {message}"; "lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "You"; "lng_from_you" = "You";
"lng_from_draft" = "Draft";
"lng_bot_description" = "What can this bot do?"; "lng_bot_description" = "What can this bot do?";
"lng_unblock_button" = "Unblock"; "lng_unblock_button" = "Unblock";
"lng_channel_join" = "Join Channel"; "lng_channel_join" = "Join Channel";

View File

@ -151,6 +151,7 @@ enum {
WriteMapTimeout = 1000, WriteMapTimeout = 1000,
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
SaveCloudDraftTimeout = 14000, // save draft to the cloud after 14 more seconds
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface

View File

@ -136,6 +136,10 @@ MTPint toServerTime(const TimeId &clientTime) {
return MTP_int(clientTime + unixtimeDelta); return MTP_int(clientTime + unixtimeDelta);
} }
QDateTime dateFromServerTime(TimeId time) {
return dateFromServerTime(MTP_int(time));
}
// Precise timing functions / rand init // Precise timing functions / rand init
struct CRYPTO_dynlock_value { struct CRYPTO_dynlock_value {

View File

@ -474,10 +474,16 @@ inline QDateTime date(int32 time = -1) {
return result; return result;
} }
inline QDateTime date(const MTPint &time) { inline QDateTime dateFromServerTime(const MTPint &time) {
return date(fromServerTime(time)); return date(fromServerTime(time));
} }
inline QDateTime date(const MTPint &time) {
return dateFromServerTime(time);
}
QDateTime dateFromServerTime(TimeId time);
inline void mylocaltime(struct tm * _Tm, const time_t * _Time) { inline void mylocaltime(struct tm * _Tm, const time_t * _Time) {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
localtime_s(_Tm, _Time); localtime_s(_Tm, _Time);

View File

@ -21,13 +21,31 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "stdafx.h" #include "stdafx.h"
#include "data/drafts.h" #include "data/drafts.h"
#include "historywidget.h"
#include "mainwidget.h"
namespace Data { namespace Data {
namespace { namespace {
} // namespace } // namespace
void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) { void applyPeerCloudDraft(PeerId peerId, const MTPDdraftMessage &draft) {
auto history = App::history(peerId);
auto text = qs(draft.vmessage);
auto entities = draft.has_entities() ? entitiesFromMTP(draft.ventities.c_vector().v) : EntitiesInText();
TextWithTags textWithTags = { textApplyEntities(text, entities), textTagsFromEntities(entities) };
MsgId replyTo = draft.has_reply_to_msg_id() ? draft.vreply_to_msg_id.v : 0;
auto cloudDraft = std_::make_unique<HistoryDraft>(textWithTags, replyTo, MessageCursor(QFIXED_MAX, QFIXED_MAX, QFIXED_MAX), draft.is_no_webpage());
cloudDraft->date = ::date(draft.vdate);
history->setCloudDraft(std_::move(cloudDraft));
history->createLocalDraftFromCloud();
history->updateChatListSortPosition();
history->updateChatListEntry();
if (auto main = App::main()) {
main->applyCloudDraft(history);
}
} }
} // namespace Data } // namespace Data

View File

@ -31,8 +31,26 @@ namespace Layout {
namespace { namespace {
void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool active) {
QDateTime now(QDateTime::currentDateTime()), lastTime(date);
QDate nowDate(now.date()), lastDate(lastTime.date());
QString dt;
if (lastDate == nowDate) {
dt = lastTime.toString(cTimeFormat());
} else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) {
dt = langDayOfWeek(lastDate);
} else {
dt = lastDate.toString(qsl("d.MM.yy"));
}
int32 dtWidth = st::dlgDateFont->width(dt);
rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip);
p.setFont(st::dlgDateFont);
p.setPen(active ? st::dlgActiveDateColor : st::dlgDateColor);
p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt);
}
template <typename PaintItemCallback> template <typename PaintItemCallback>
void paintRow(Painter &p, History *history, HistoryItem *item, int w, bool active, bool selected, bool onlyBackground, PaintItemCallback paintItemCallback) { void paintRow(Painter &p, History *history, HistoryItem *item, HistoryDraft *draft, int w, bool active, bool selected, bool onlyBackground, PaintItemCallback paintItemCallback) {
QRect fullRect(0, 0, w, st::dlgHeight); QRect fullRect(0, 0, w, st::dlgHeight);
p.fillRect(fullRect, active ? st::dlgActiveBG : (selected ? st::dlgHoverBG : st::dlgBG)); p.fillRect(fullRect, active ? st::dlgActiveBG : (selected ? st::dlgHoverBG : st::dlgBG));
if (onlyBackground) return; if (onlyBackground) return;
@ -53,31 +71,56 @@ void paintRow(Painter &p, History *history, HistoryItem *item, int w, bool activ
rectForName.setLeft(rectForName.left() + st::dlgImgSkip); rectForName.setLeft(rectForName.left() + st::dlgImgSkip);
} }
if (!item) { int texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep;
if (draft) {
paintRowDate(p, draft->date, rectForName, active);
// draw check
if (draft->saveRequestId) {
auto check = active ? &st::dlgActiveSendImg : &st::dlgSendImg;
rectForName.setWidth(rectForName.width() - check->pxWidth() - st::dlgCheckSkip);
p.drawSprite(QPoint(rectForName.left() + rectForName.width() + st::dlgCheckLeft, rectForName.top() + st::dlgCheckTop), *check);
}
bool hasDraftIcon = !active;
if (hasDraftIcon) {
QString counter;
bool mutedCounter = false;
int unreadRight = w - st::dlgPaddingHor;
int unreadTop = texttop + st::dlgHistFont->ascent - st::dlgUnreadFont->ascent - st::dlgUnreadTop;
int unreadWidth = 0;
paintUnreadCount(p, counter, unreadRight, unreadTop, style::al_right, active, mutedCounter, &unreadWidth);
st::dialogsDraft.paint(p, QPoint(w - st::dlgPaddingHor - st::dlgUnreadHeight, unreadTop), w);
namewidth -= unreadWidth + st::dlgUnreadPaddingHor;
}
p.setFont(st::dlgHistFont); p.setFont(st::dlgHistFont);
p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor); p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor);
if (history->typing.isEmpty() && history->sendActions.isEmpty()) { if (history->typing.isEmpty() && history->sendActions.isEmpty()) {
p.drawText(nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgFont->ascent + st::dlgSep, lang(lng_empty_history)); if (history->cloudDraftTextCache.isEmpty()) {
TextCustomTagsMap custom;
custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink()));
QString msg = lng_message_with_from(lt_from, textRichPrepare(lang(lng_from_draft)), lt_message, textRichPrepare(draft->textWithTags.text));
history->cloudDraftTextCache.setRichText(st::dlgHistFont, msg, _textDlgOptions, custom);
}
textstyleSet(&(active ? st::dlgActiveTextStyle : st::dlgTextStyle));
p.setFont(st::dlgHistFont);
p.setPen(active ? st::dlgActiveColor : st::dlgTextColor);
history->cloudDraftTextCache.drawElided(p, nameleft, texttop, namewidth, st::dlgFont->height / st::dlgHistFont->height);
textstyleRestore();
} else { } else {
history->typingText.drawElided(p, nameleft, st::dlgPaddingVer + st::dlgFont->height + st::dlgSep, namewidth); history->typingText.drawElided(p, nameleft, texttop, namewidth);
}
} else if (!item) {
p.setFont(st::dlgHistFont);
p.setPen(active ? st::dlgActiveColor : st::dlgSystemColor);
if (history->typing.isEmpty() && history->sendActions.isEmpty()) {
p.drawText(nameleft, texttop + st::dlgFont->ascent, lang(lng_empty_history));
} else {
history->typingText.drawElided(p, nameleft, texttop, namewidth);
} }
} else { } else {
// draw date paintRowDate(p, item->date, rectForName, active);
QDateTime now(QDateTime::currentDateTime()), lastTime(item->date);
QDate nowDate(now.date()), lastDate(lastTime.date());
QString dt;
if (lastDate == nowDate) {
dt = lastTime.toString(cTimeFormat());
} else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) {
dt = langDayOfWeek(lastDate);
} else {
dt = lastDate.toString(qsl("d.MM.yy"));
}
int32 dtWidth = st::dlgDateFont->width(dt);
rectForName.setWidth(rectForName.width() - dtWidth - st::dlgDateSkip);
p.setFont(st::dlgDateFont);
p.setPen(active ? st::dlgActiveDateColor : st::dlgDateColor);
p.drawText(rectForName.left() + rectForName.width() + st::dlgDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt);
// draw check // draw check
if (item->needCheck()) { if (item->needCheck()) {
@ -186,7 +229,11 @@ void paintUnreadCount(Painter &p, const QString &text, int x, int y, style::alig
void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) { void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) {
auto history = row->history(); auto history = row->history();
auto item = history->lastMsg; auto item = history->lastMsg;
paintRow(p, history, item, w, active, selected, onlyBackground, [&p, w, active, history](int nameleft, int namewidth, HistoryItem *item) { auto cloudDraft = history->cloudDraft();
if (item && cloudDraft && cloudDraft->date < item->date) {
cloudDraft = nullptr; // Draw item, if draft is older.
}
paintRow(p, history, item, cloudDraft, w, active, selected, onlyBackground, [&p, w, active, history](int nameleft, int namewidth, HistoryItem *item) {
int32 unread = history->unreadCount(); int32 unread = history->unreadCount();
if (history->peer->migrateFrom()) { if (history->peer->migrateFrom()) {
if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) { if (History *h = App::historyLoaded(history->peer->migrateFrom()->id)) {
@ -195,7 +242,8 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele
} }
int availableWidth = namewidth; int availableWidth = namewidth;
int texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep; int texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep;
bool hasDraftIcon = active ? false : Local::hasDraft(history->peer->id); auto cloudDraft = history->cloudDraft();
bool hasDraftIcon = active ? false : (cloudDraft && cloudDraft->date.isValid());
if (unread || hasDraftIcon) { if (unread || hasDraftIcon) {
QString counter; QString counter;
bool mutedCounter = false; bool mutedCounter = false;
@ -208,10 +256,10 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele
int unreadTop = texttop + st::dlgHistFont->ascent - st::dlgUnreadFont->ascent - st::dlgUnreadTop; int unreadTop = texttop + st::dlgHistFont->ascent - st::dlgUnreadFont->ascent - st::dlgUnreadTop;
int unreadWidth = 0; int unreadWidth = 0;
paintUnreadCount(p, counter, unreadRight, unreadTop, style::al_right, active, mutedCounter, &unreadWidth); paintUnreadCount(p, counter, unreadRight, unreadTop, style::al_right, active, mutedCounter, &unreadWidth);
availableWidth -= unreadWidth + st::dlgUnreadPaddingHor;
if (!showUnreadCounter) { if (!showUnreadCounter) {
st::dialogsDraft.paint(p, QPoint(w - st::dlgPaddingHor - st::dlgUnreadHeight, unreadTop), w); st::dialogsDraft.paint(p, QPoint(w - st::dlgPaddingHor - st::dlgUnreadHeight, unreadTop), w);
} }
availableWidth -= unreadWidth + st::dlgUnreadPaddingHor;
} }
if (history->typing.isEmpty() && history->sendActions.isEmpty()) { if (history->typing.isEmpty() && history->sendActions.isEmpty()) {
item->drawInDialog(p, QRect(nameleft, texttop, availableWidth, st::dlgFont->height), active, history->textCachedFor, history->lastItemTextCache); item->drawInDialog(p, QRect(nameleft, texttop, availableWidth, st::dlgFont->height), active, history->textCachedFor, history->lastItemTextCache);
@ -225,7 +273,7 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele
void RowPainter::paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground) { void RowPainter::paint(Painter &p, const FakeRow *row, int w, bool active, bool selected, bool onlyBackground) {
auto item = row->item(); auto item = row->item();
auto history = item->history(); auto history = item->history();
paintRow(p, history, item, w, active, selected, onlyBackground, [&p, row, active](int nameleft, int namewidth, HistoryItem *item) { paintRow(p, history, item, nullptr, w, active, selected, onlyBackground, [&p, row, active](int nameleft, int namewidth, HistoryItem *item) {
int lastWidth = namewidth, texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep; int lastWidth = namewidth, texttop = st::dlgPaddingVer + st::dlgFont->height + st::dlgSep;
item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dlgFont->height), active, row->_cacheFor, row->_cache); item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dlgFont->height), active, row->_cacheFor, row->_cache);
}); });

View File

@ -162,6 +162,59 @@ void History::setHasPendingResizedItems() {
Global::RefHandleHistoryUpdate().call(); Global::RefHandleHistoryUpdate().call();
} }
void History::createLocalDraftFromCloud() {
auto draft = cloudDraft();
if (historyDraftIsNull(draft) || !draft->date.isValid()) return;
auto existing = localDraft();
if (historyDraftIsNull(existing) || !existing->date.isValid() || draft->date > existing->date) {
if (!existing) {
setLocalDraft(std_::make_unique<HistoryDraft>(draft->textWithTags, draft->msgId, draft->cursor, draft->previewCancelled));
existing = localDraft();
} else if (existing != draft) {
existing->textWithTags = draft->textWithTags;
existing->msgId = draft->msgId;
existing->cursor = draft->cursor;
existing->previewCancelled = draft->previewCancelled;
}
existing->date = draft->date;
}
}
HistoryDraft *History::createCloudDraft(HistoryDraft *fromDraft) {
if (historyDraftIsNull(fromDraft)) {
setCloudDraft(std_::make_unique<HistoryDraft>(TextWithTags(), 0, MessageCursor(), false));
cloudDraft()->date = QDateTime();
} else {
auto existing = cloudDraft();
if (!existing) {
setCloudDraft(std_::make_unique<HistoryDraft>(fromDraft->textWithTags, fromDraft->msgId, fromDraft->cursor, fromDraft->previewCancelled));
existing = cloudDraft();
} else if (existing != fromDraft) {
existing->textWithTags = fromDraft->textWithTags;
existing->msgId = fromDraft->msgId;
existing->cursor = fromDraft->cursor;
existing->previewCancelled = fromDraft->previewCancelled;
}
existing->date = ::date(myunixtime());
}
cloudDraftTextCache.clear();
updateChatListSortPosition();
updateChatListEntry();
return cloudDraft();
}
void History::clearCloudDraft() {
if (_cloudDraft) {
_cloudDraft = nullptr;
cloudDraftTextCache.clear();
updateChatListSortPosition();
updateChatListEntry();
}
}
bool History::updateTyping(uint64 ms, bool force) { bool History::updateTyping(uint64 ms, bool force) {
bool changed = force; bool changed = force;
for (TypingUsers::iterator i = typing.begin(), e = typing.end(); i != e;) { for (TypingUsers::iterator i = typing.begin(), e = typing.end(); i != e;) {
@ -1037,6 +1090,7 @@ void History::newItemAdded(HistoryItem *item) {
if (!item->unread()) { if (!item->unread()) {
outboxRead(item); outboxRead(item);
} }
item->history()->clearCloudDraft();
} else if (item->unread()) { } else if (item->unread()) {
bool skip = false; bool skip = false;
if (!isChannel() || peer->asChannel()->amIn()) { if (!isChannel() || peer->asChannel()->amIn()) {
@ -1679,19 +1733,38 @@ void History::setLastMessage(HistoryItem *msg) {
updateChatListEntry(); updateChatListEntry();
} }
void History::setChatsListDate(const QDateTime &date) { bool History::needUpdateInChatList() const {
bool updateDialog = (App::main() && (!peer->isChannel() || peer->asChannel()->amIn() || inChatList(Dialogs::Mode::All))); if (inChatList(Dialogs::Mode::All)) {
if (peer->migrateTo() && !inChatList(Dialogs::Mode::All)) { return true;
updateDialog = false; } else if (peer->migrateTo()) {
return false;
} }
return (!peer->isChannel() || peer->asChannel()->amIn());
}
void History::setChatsListDate(const QDateTime &date) {
bool updateDialog = needUpdateInChatList();
if (!lastMsgDate.isNull() && lastMsgDate >= date) { if (!lastMsgDate.isNull() && lastMsgDate >= date) {
if (!updateDialog || !inChatList(Dialogs::Mode::All)) { if (!updateDialog || !inChatList(Dialogs::Mode::All)) {
return; return;
} }
} }
lastMsgDate = date; lastMsgDate = date;
_sortKeyInChatList = dialogPosFromDate(lastMsgDate); updateChatListSortPosition();
if (updateDialog) { }
void History::updateChatListSortPosition() {
auto chatListDate = [this]() {
if (auto draft = cloudDraft()) {
if (draft->date > lastMsgDate) {
return draft->date;
}
}
return lastMsgDate;
};
_sortKeyInChatList = dialogPosFromDate(chatListDate());
if (App::main() && needUpdateInChatList()) {
App::main()->createDialog(this); App::main()->createDialog(this);
} }
} }

View File

@ -155,40 +155,44 @@ struct SendAction {
using TextWithTags = FlatTextarea::TextWithTags; using TextWithTags = FlatTextarea::TextWithTags;
struct HistoryDraft { struct HistoryDraft {
HistoryDraft() : msgId(0), previewCancelled(false) { HistoryDraft() {
} }
HistoryDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled) HistoryDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequestId = 0)
: textWithTags(textWithTags) : textWithTags(textWithTags)
, msgId(msgId) , msgId(msgId)
, cursor(cursor) , cursor(cursor)
, previewCancelled(previewCancelled) { , previewCancelled(previewCancelled)
, saveRequestId(saveRequestId) {
} }
HistoryDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled) HistoryDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequestId = 0)
: textWithTags(field.getTextWithTags()) : textWithTags(field.getTextWithTags())
, msgId(msgId) , msgId(msgId)
, cursor(field) , cursor(field)
, previewCancelled(previewCancelled) { , previewCancelled(previewCancelled) {
} }
QDateTime date;
TextWithTags textWithTags; TextWithTags textWithTags;
MsgId msgId; // replyToId for message draft, editMsgId for edit draft MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft
MessageCursor cursor; MessageCursor cursor;
bool previewCancelled; bool previewCancelled = false;
mtpRequestId saveRequestId = 0;
}; };
struct HistoryEditDraft : public HistoryDraft {
HistoryEditDraft() inline bool historyDraftIsNull(HistoryDraft *draft) {
: HistoryDraft() return (!draft || (!draft->msgId && draft->textWithTags.text.isEmpty()));
, saveRequest(0) { }
inline bool historyDraftsAreEqual(HistoryDraft *a, HistoryDraft *b) {
bool aIsNull = historyDraftIsNull(a);
bool bIsNull = historyDraftIsNull(b);
if (aIsNull) {
return bIsNull;
} else if (bIsNull) {
return false;
} }
HistoryEditDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(textWithTags, msgId, cursor, previewCancelled) return (a->textWithTags == b->textWithTags) && (a->msgId == b->msgId) && (a->previewCancelled == b->previewCancelled);
, saveRequest(saveRequest) { }
}
HistoryEditDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(field, msgId, previewCancelled)
, saveRequest(saveRequest) {
}
mtpRequestId saveRequest;
};
class HistoryMedia; class HistoryMedia;
class HistoryMessage; class HistoryMessage;
@ -279,6 +283,8 @@ public:
void setLastMessage(HistoryItem *msg); void setLastMessage(HistoryItem *msg);
void fixLastMessage(bool wasAtBottom); void fixLastMessage(bool wasAtBottom);
bool needUpdateInChatList() const;
void updateChatListSortPosition();
void setChatsListDate(const QDateTime &date); void setChatsListDate(const QDateTime &date);
uint64 sortKeyInChatList() const { uint64 sortKeyInChatList() const {
return _sortKeyInChatList; return _sortKeyInChatList;
@ -367,35 +373,48 @@ public:
typedef QList<HistoryItem*> NotifyQueue; typedef QList<HistoryItem*> NotifyQueue;
NotifyQueue notifies; NotifyQueue notifies;
HistoryDraft *msgDraft() { HistoryDraft *localDraft() {
return _msgDraft.get(); return _localDraft.get();
} }
HistoryEditDraft *editDraft() { HistoryDraft *cloudDraft() {
return _cloudDraft.get();
}
HistoryDraft *editDraft() {
return _editDraft.get(); return _editDraft.get();
} }
void setMsgDraft(std_::unique_ptr<HistoryDraft> &&draft) { void setLocalDraft(std_::unique_ptr<HistoryDraft> &&draft) {
_msgDraft = std_::move(draft); _localDraft = std_::move(draft);
} }
void takeMsgDraft(History *from) { void takeLocalDraft(History *from) {
if (auto &draft = from->_msgDraft) { if (auto &draft = from->_localDraft) {
if (!draft->textWithTags.text.isEmpty() && !_msgDraft) { if (!draft->textWithTags.text.isEmpty() && !_localDraft) {
_msgDraft = std_::move(draft); _localDraft = std_::move(draft);
_msgDraft->msgId = 0; // edit and reply to drafts can't migrate
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
_localDraft->msgId = 0;
} }
from->clearMsgDraft(); from->clearLocalDraft();
} }
} }
void setEditDraft(std_::unique_ptr<HistoryEditDraft> &&draft) { void createLocalDraftFromCloud();
void setCloudDraft(std_::unique_ptr<HistoryDraft> &&draft) {
_cloudDraft = std_::move(draft);
cloudDraftTextCache.clear();
}
HistoryDraft *createCloudDraft(HistoryDraft *fromDraft);
void setEditDraft(std_::unique_ptr<HistoryDraft> &&draft) {
_editDraft = std_::move(draft); _editDraft = std_::move(draft);
} }
void clearMsgDraft() { void clearLocalDraft() {
_msgDraft = nullptr; _localDraft = nullptr;
} }
void clearCloudDraft();
void clearEditDraft() { void clearEditDraft() {
_editDraft = nullptr; _editDraft = nullptr;
} }
HistoryDraft *draft() { HistoryDraft *draft() {
return _editDraft ? editDraft() : msgDraft(); return _editDraft ? editDraft() : localDraft();
} }
// some fields below are a property of a currently displayed instance of this // some fields below are a property of a currently displayed instance of this
@ -485,6 +504,8 @@ public:
void changeMsgId(MsgId oldId, MsgId newId); void changeMsgId(MsgId oldId, MsgId newId);
Text cloudDraftTextCache = Text { int(st::dlgRichMinWidth) };
protected: protected:
void clearOnDestroy(); void clearOnDestroy();
@ -578,8 +599,8 @@ private:
// Depending on isBuildingFrontBlock() gets front or back block. // Depending on isBuildingFrontBlock() gets front or back block.
HistoryBlock *prepareBlockForAddingItem(); HistoryBlock *prepareBlockForAddingItem();
std_::unique_ptr<HistoryDraft> _msgDraft; std_::unique_ptr<HistoryDraft> _localDraft, _cloudDraft;
std_::unique_ptr<HistoryEditDraft> _editDraft; std_::unique_ptr<HistoryDraft> _editDraft;
}; };

View File

@ -2872,6 +2872,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
_saveDraftTimer.setSingleShot(true); _saveDraftTimer.setSingleShot(true);
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave())); connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
_saveCloudDraftTimer.setSingleShot(true);
connect(&_saveCloudDraftTimer, SIGNAL(timeout()), this, SLOT(onCloudDraftSave()));
connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed())); connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed())); connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection); connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onCheckFieldAutocomplete()), Qt::QueuedConnection);
@ -3074,7 +3076,9 @@ void HistoryWidget::onTextChange() {
update(); update();
} }
_saveCloudDraftTimer.stop();
if (!_peer || !(_textUpdateEvents.testFlag(TextUpdateEvent::SaveDraft))) return; if (!_peer || !(_textUpdateEvents.testFlag(TextUpdateEvent::SaveDraft))) return;
_saveDraftText = true; _saveDraftText = true;
onDraftSave(true); onDraftSave(true);
} }
@ -3103,31 +3107,52 @@ void HistoryWidget::onDraftSave(bool delayed) {
writeDrafts(nullptr, nullptr); writeDrafts(nullptr, nullptr);
} }
void HistoryWidget::writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **editDraft) { void HistoryWidget::saveFieldToHistoryLocalDraft() {
HistoryDraft *historyMsgDraft = _history ? _history->msgDraft() : nullptr; if (!_history) return;
if (!msgDraft && _editMsgId) msgDraft = &historyMsgDraft;
if (_editMsgId) {
_history->setEditDraft(std_::make_unique<HistoryDraft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
} else {
if (_replyToId || !_field.isEmpty()) {
_history->setLocalDraft(std_::make_unique<HistoryDraft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearLocalDraft();
}
_history->clearEditDraft();
}
}
void HistoryWidget::onCloudDraftSave() {
if (App::main()) {
App::main()->saveDraftToCloud();
}
}
void HistoryWidget::writeDrafts(HistoryDraft **localDraft, HistoryDraft **editDraft) {
HistoryDraft *historyLocalDraft = _history ? _history->localDraft() : nullptr;
if (!localDraft && _editMsgId) localDraft = &historyLocalDraft;
bool save = _peer && (_saveDraftStart > 0); bool save = _peer && (_saveDraftStart > 0);
_saveDraftStart = 0; _saveDraftStart = 0;
_saveDraftTimer.stop(); _saveDraftTimer.stop();
if (_saveDraftText) { if (_saveDraftText) {
if (save) { if (save) {
Local::MessageDraft localMsgDraft, localEditDraft; Local::MessageDraft storedLocalDraft, storedEditDraft;
if (msgDraft) { if (localDraft) {
if (*msgDraft) { if (*localDraft) {
localMsgDraft = Local::MessageDraft((*msgDraft)->msgId, (*msgDraft)->textWithTags, (*msgDraft)->previewCancelled); storedLocalDraft = Local::MessageDraft((*localDraft)->msgId, (*localDraft)->textWithTags, (*localDraft)->previewCancelled);
} }
} else { } else {
localMsgDraft = Local::MessageDraft(_replyToId, _field.getTextWithTags(), _previewCancelled); storedLocalDraft = Local::MessageDraft(_replyToId, _field.getTextWithTags(), _previewCancelled);
} }
if (editDraft) { if (editDraft) {
if (*editDraft) { if (*editDraft) {
localEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->textWithTags, (*editDraft)->previewCancelled); storedEditDraft = Local::MessageDraft((*editDraft)->msgId, (*editDraft)->textWithTags, (*editDraft)->previewCancelled);
} }
} else if (_editMsgId) { } else if (_editMsgId) {
localEditDraft = Local::MessageDraft(_editMsgId, _field.getTextWithTags(), _previewCancelled); storedEditDraft = Local::MessageDraft(_editMsgId, _field.getTextWithTags(), _previewCancelled);
} }
Local::writeDrafts(_peer->id, localMsgDraft, localEditDraft); Local::writeDrafts(_peer->id, storedLocalDraft, storedEditDraft);
if (_migrated) { if (_migrated) {
Local::writeDrafts(_migrated->peer->id, Local::MessageDraft(), Local::MessageDraft()); Local::writeDrafts(_migrated->peer->id, Local::MessageDraft(), Local::MessageDraft());
} }
@ -3135,13 +3160,13 @@ void HistoryWidget::writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **edit
_saveDraftText = false; _saveDraftText = false;
} }
if (save) { if (save) {
MessageCursor msgCursor, editCursor; MessageCursor localCursor, editCursor;
if (msgDraft) { if (localDraft) {
if (*msgDraft) { if (*localDraft) {
msgCursor = (*msgDraft)->cursor; localCursor = (*localDraft)->cursor;
} }
} else { } else {
msgCursor = MessageCursor(_field); localCursor = MessageCursor(_field);
} }
if (editDraft) { if (editDraft) {
if (*editDraft) { if (*editDraft) {
@ -3150,26 +3175,30 @@ void HistoryWidget::writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **edit
} else if (_editMsgId) { } else if (_editMsgId) {
editCursor = MessageCursor(_field); editCursor = MessageCursor(_field);
} }
Local::writeDraftCursors(_peer->id, msgCursor, editCursor); Local::writeDraftCursors(_peer->id, localCursor, editCursor);
if (_migrated) { if (_migrated) {
Local::writeDraftCursors(_migrated->peer->id, MessageCursor(), MessageCursor()); Local::writeDraftCursors(_migrated->peer->id, MessageCursor(), MessageCursor());
} }
} }
if (!_editMsgId) {
_saveCloudDraftTimer.start(SaveCloudDraftTimeout);
}
} }
void HistoryWidget::writeDrafts(History *history) { void HistoryWidget::writeDrafts(History *history) {
Local::MessageDraft localMsgDraft, localEditDraft; Local::MessageDraft storedLocalDraft, storedEditDraft;
MessageCursor msgCursor, editCursor; MessageCursor localCursor, editCursor;
if (auto msgDraft = history->msgDraft()) { if (auto localDraft = history->localDraft()) {
localMsgDraft = Local::MessageDraft(msgDraft->msgId, msgDraft->textWithTags, msgDraft->previewCancelled); storedLocalDraft = Local::MessageDraft(localDraft->msgId, localDraft->textWithTags, localDraft->previewCancelled);
msgCursor = msgDraft->cursor; localCursor = localDraft->cursor;
} }
if (auto editDraft = history->editDraft()) { if (auto editDraft = history->editDraft()) {
localEditDraft = Local::MessageDraft(editDraft->msgId, editDraft->textWithTags, editDraft->previewCancelled); storedEditDraft = Local::MessageDraft(editDraft->msgId, editDraft->textWithTags, editDraft->previewCancelled);
editCursor = editDraft->cursor; editCursor = editDraft->cursor;
} }
Local::writeDrafts(history->peer->id, localMsgDraft, localEditDraft); Local::writeDrafts(history->peer->id, storedLocalDraft, storedEditDraft);
Local::writeDraftCursors(history->peer->id, msgCursor, editCursor); Local::writeDraftCursors(history->peer->id, localCursor, editCursor);
} }
void HistoryWidget::cancelSendAction(History *history, SendActionType type) { void HistoryWidget::cancelSendAction(History *history, SendActionType type) {
@ -3342,7 +3371,7 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query) {
History *h = App::history(toPeerId); History *h = App::history(toPeerId);
TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() }; TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX }; MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false)); h->setLocalDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
if (h == _history) { if (h == _history) {
applyDraft(); applyDraft();
} else { } else {
@ -3652,7 +3681,7 @@ void HistoryWidget::applyDraft(bool parseLinks) {
_replyToId = 0; _replyToId = 0;
} else { } else {
_editMsgId = 0; _editMsgId = 0;
_replyToId = readyToForward() ? 0 : _history->msgDraft()->msgId; _replyToId = readyToForward() ? 0 : _history->localDraft()->msgId;
} }
if (parseLinks) { if (parseLinks) {
onPreviewParse(); onPreviewParse();
@ -3665,6 +3694,12 @@ void HistoryWidget::applyDraft(bool parseLinks) {
} }
} }
void HistoryWidget::applyCloudDraft(History *history) {
if (_history == history) {
applyDraft();
}
}
void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool reload) { void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool reload) {
MsgId wasMsgId = _showAtMsgId; MsgId wasMsgId = _showAtMsgId;
History *wasHistory = _history; History *wasHistory = _history;
@ -3708,7 +3743,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
if (startBot && _peer->isUser() && _peer->asUser()->botInfo) { if (startBot && _peer->isUser() && _peer->asUser()->botInfo) {
if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id; if (wasHistory) _peer->asUser()->botInfo->inlineReturnPeerId = wasHistory->peer->id;
onBotStart(); onBotStart();
_history->clearMsgDraft(); _history->clearLocalDraft();
applyDraft(); applyDraft();
} }
return; return;
@ -3726,24 +3761,15 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
clearAllLoadRequests(); clearAllLoadRequests();
if (_history) { if (_history) {
if (_editMsgId) { if (App::main()) App::main()->saveDraftToCloud();
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(_field, _editMsgId, _previewCancelled, _saveEditMsgRequestId));
} else {
if (_replyToId || !_field.isEmpty()) {
_history->setMsgDraft(std_::make_unique<HistoryDraft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearMsgDraft();
}
_history->clearEditDraft();
}
if (_migrated) { if (_migrated) {
_migrated->clearEditDraft(); // use migrated draft only once _migrated->clearLocalDraft(); // use migrated draft only once
_migrated->clearEditDraft(); _migrated->clearEditDraft();
} }
auto msgDraft = _history->msgDraft(); auto localDraft = _history->localDraft();
auto editDraft = _history->editDraft(); auto editDraft = _history->editDraft();
writeDrafts(&msgDraft, &editDraft); writeDrafts(&localDraft, &editDraft);
_history->showAtMsgId = _showAtMsgId; _history->showAtMsgId = _showAtMsgId;
@ -3848,7 +3874,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
if (_migrated) { if (_migrated) {
Local::readDraftsWithCursors(_migrated); Local::readDraftsWithCursors(_migrated);
_migrated->clearEditDraft(); _migrated->clearEditDraft();
_history->takeMsgDraft(_migrated); _history->takeLocalDraft(_migrated);
} }
applyDraft(false); applyDraft(false);
@ -4725,7 +4751,7 @@ void HistoryWidget::saveEditMsgDone(History *history, const MTPUpdates &updates,
cancelEdit(); cancelEdit();
} }
if (auto editDraft = history->editDraft()) { if (auto editDraft = history->editDraft()) {
if (editDraft->saveRequest == req) { if (editDraft->saveRequestId == req) {
history->clearEditDraft(); history->clearEditDraft();
writeDrafts(history); writeDrafts(history);
} }
@ -4738,8 +4764,8 @@ bool HistoryWidget::saveEditMsgFail(History *history, const RPCError &error, mtp
_saveEditMsgRequestId = 0; _saveEditMsgRequestId = 0;
} }
if (auto editDraft = history->editDraft()) { if (auto editDraft = history->editDraft()) {
if (editDraft->saveRequest == req) { if (editDraft->saveRequestId == req) {
editDraft->saveRequest = 0; editDraft->saveRequestId = 0;
} }
} }
@ -7204,10 +7230,10 @@ void HistoryWidget::onReplyToMessage() {
App::main()->cancelForwarding(); App::main()->cancelForwarding();
if (_editMsgId) { if (_editMsgId) {
if (auto msgDraft = _history->msgDraft()) { if (auto localDraft = _history->localDraft()) {
msgDraft->msgId = to->id; localDraft->msgId = to->id;
} else { } else {
_history->setMsgDraft(std_::make_unique<HistoryDraft>(TextWithTags(), to->id, MessageCursor(), false)); _history->setLocalDraft(std_::make_unique<HistoryDraft>(TextWithTags(), to->id, MessageCursor(), false));
} }
} else { } else {
_replyEditMsg = to; _replyEditMsg = to;
@ -7241,9 +7267,9 @@ void HistoryWidget::onEditMessage() {
delete box; delete box;
if (_replyToId || !_field.isEmpty()) { if (_replyToId || !_field.isEmpty()) {
_history->setMsgDraft(std_::make_unique<HistoryDraft>(_field, _replyToId, _previewCancelled)); _history->setLocalDraft(std_::make_unique<HistoryDraft>(_field, _replyToId, _previewCancelled));
} else { } else {
_history->clearMsgDraft(); _history->clearLocalDraft();
} }
auto original = to->originalText(); auto original = to->originalText();
@ -7251,7 +7277,7 @@ void HistoryWidget::onEditMessage() {
auto editTags = textTagsFromEntities(original.entities); auto editTags = textTagsFromEntities(original.entities);
TextWithTags editData = { editText, editTags }; TextWithTags editData = { editText, editTags };
MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX }; MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
_history->setEditDraft(std_::make_unique<HistoryEditDraft>(editData, to->id, cursor, false)); _history->setEditDraft(std_::make_unique<HistoryDraft>(editData, to->id, cursor, false));
applyDraft(false); applyDraft(false);
_previewData = nullptr; _previewData = nullptr;
@ -7368,12 +7394,12 @@ void HistoryWidget::cancelReply(bool lastKeyboardUsed) {
resizeEvent(0); resizeEvent(0);
update(); update();
} else if (auto msgDraft = (_history ? _history->msgDraft() : nullptr)) { } else if (auto localDraft = (_history ? _history->localDraft() : nullptr)) {
if (msgDraft->msgId) { if (localDraft->msgId) {
if (msgDraft->textWithTags.text.isEmpty()) { if (localDraft->textWithTags.text.isEmpty()) {
_history->clearMsgDraft(); _history->clearLocalDraft();
} else { } else {
msgDraft->msgId = 0; localDraft->msgId = 0;
} }
} }
} }

View File

@ -632,6 +632,9 @@ public:
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
void clearDelayedShowAt(); void clearDelayedShowAt();
void clearAllLoadRequests(); void clearAllLoadRequests();
void saveFieldToHistoryLocalDraft();
void applyCloudDraft(History *history);
void contactsReceived(); void contactsReceived();
void updateToEndVisibility(); void updateToEndVisibility();
@ -781,6 +784,7 @@ public slots:
void onDraftSaveDelayed(); void onDraftSaveDelayed();
void onDraftSave(bool delayed = false); void onDraftSave(bool delayed = false);
void onCloudDraftSave();
void updateStickers(); void updateStickers();
@ -959,7 +963,7 @@ private:
Q_DECLARE_FLAGS(TextUpdateEvents, TextUpdateEvent); Q_DECLARE_FLAGS(TextUpdateEvents, TextUpdateEvent);
Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(TextUpdateEvents); Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(TextUpdateEvents);
void writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **editDraft); void writeDrafts(HistoryDraft **localDraft, HistoryDraft **editDraft);
void writeDrafts(History *history); void writeDrafts(History *history);
void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory); void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory);
void clearFieldText(TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory) { void clearFieldText(TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory) {
@ -1088,7 +1092,7 @@ private:
uint64 _saveDraftStart = 0; uint64 _saveDraftStart = 0;
bool _saveDraftText = false; bool _saveDraftText = false;
QTimer _saveDraftTimer; QTimer _saveDraftTimer, _saveCloudDraftTimer;
PlainShadow _sideShadow, _topShadow; PlainShadow _sideShadow, _topShadow;
bool _inGrab = false; bool _inGrab = false;

View File

@ -2270,10 +2270,10 @@ namespace Local {
return _oldSettingsVersion; return _oldSettingsVersion;
} }
void writeDrafts(const PeerId &peer, const MessageDraft &msgDraft, const MessageDraft &editDraft) { void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft) {
if (!_working()) return; if (!_working()) return;
if (msgDraft.msgId <= 0 && msgDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) {
auto i = _draftsMap.find(peer); auto i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) { if (i != _draftsMap.cend()) {
clearKey(i.value()); clearKey(i.value());
@ -2291,17 +2291,17 @@ namespace Local {
_writeMap(WriteMapFast); _writeMap(WriteMapFast);
} }
auto msgTags = FlatTextarea::serializeTagsList(msgDraft.textWithTags.tags); auto msgTags = FlatTextarea::serializeTagsList(localDraft.textWithTags.tags);
auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags); auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags);
int size = sizeof(quint64); int size = sizeof(quint64);
size += Serialize::stringSize(msgDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32);
size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32); size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32);
EncryptedDescriptor data(size); EncryptedDescriptor data(size);
data.stream << quint64(peer); data.stream << quint64(peer);
data.stream << msgDraft.textWithTags.text << msgTags; data.stream << localDraft.textWithTags.text << msgTags;
data.stream << qint32(msgDraft.msgId) << qint32(msgDraft.previewCancelled ? 1 : 0); data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0);
data.stream << editDraft.textWithTags.text << editTags; data.stream << editDraft.textWithTags.text << editTags;
data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0); data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0);
@ -2322,7 +2322,7 @@ namespace Local {
} }
} }
void _readDraftCursors(const PeerId &peer, MessageCursor &msgCursor, MessageCursor &editCursor) { void _readDraftCursors(const PeerId &peer, MessageCursor &localCursor, MessageCursor &editCursor) {
DraftsMap::iterator j = _draftCursorsMap.find(peer); DraftsMap::iterator j = _draftCursorsMap.find(peer);
if (j == _draftCursorsMap.cend()) { if (j == _draftCursorsMap.cend()) {
return; return;
@ -2334,9 +2334,9 @@ namespace Local {
return; return;
} }
quint64 draftPeer; quint64 draftPeer;
qint32 msgPosition = 0, msgAnchor = 0, msgScroll = 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 >> msgPosition >> msgAnchor >> msgScroll; draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll;
if (!draft.stream.atEnd()) { if (!draft.stream.atEnd()) {
draft.stream >> editPosition >> editAnchor >> editScroll; draft.stream >> editPosition >> editAnchor >> editScroll;
} }
@ -2346,7 +2346,7 @@ namespace Local {
return; return;
} }
msgCursor = MessageCursor(msgPosition, msgAnchor, msgScroll); localCursor = MessageCursor(localPosition, localAnchor, localScroll);
editCursor = MessageCursor(editPosition, editAnchor, editScroll); editCursor = MessageCursor(editPosition, editAnchor, editScroll);
} }
@ -2404,15 +2404,17 @@ namespace Local {
MessageCursor msgCursor, editCursor; MessageCursor msgCursor, editCursor;
_readDraftCursors(peer, msgCursor, editCursor); _readDraftCursors(peer, msgCursor, editCursor);
if (msgData.text.isEmpty() && !msgReplyTo) { if (!h->localDraft()) {
h->clearMsgDraft(); if (msgData.text.isEmpty() && !msgReplyTo) {
} else { h->clearLocalDraft();
h->setMsgDraft(std_::make_unique<HistoryDraft>(msgData, msgReplyTo, msgCursor, msgPreviewCancelled)); } else {
h->setLocalDraft(std_::make_unique<HistoryDraft>(msgData, msgReplyTo, msgCursor, msgPreviewCancelled));
}
} }
if (!editMsgId) { if (!editMsgId) {
h->clearEditDraft(); h->clearEditDraft();
} else { } else {
h->setEditDraft(std_::make_unique<HistoryEditDraft>(editData, editMsgId, editCursor, editPreviewCancelled)); h->setEditDraft(std_::make_unique<HistoryDraft>(editData, editMsgId, editCursor, editPreviewCancelled));
} }
} }

View File

@ -116,9 +116,9 @@ namespace Local {
TextWithTags textWithTags; TextWithTags textWithTags;
bool previewCancelled; bool previewCancelled;
}; };
void writeDrafts(const PeerId &peer, const MessageDraft &msgDraft, const MessageDraft &editDraft); void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft);
void readDraftsWithCursors(History *h); void readDraftsWithCursors(History *h);
void writeDraftCursors(const PeerId &peer, const MessageCursor &msgCursor, const MessageCursor &editCursor); void writeDraftCursors(const PeerId &peer, const MessageCursor &localCursor, const MessageCursor &editCursor);
bool hasDraftCursors(const PeerId &peer); bool hasDraftCursors(const PeerId &peer);
bool hasDraft(const PeerId &peer); bool hasDraft(const PeerId &peer);

View File

@ -161,7 +161,7 @@ bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QStrin
History *h = App::history(peer); History *h = App::history(peer);
TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() }; TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() };
MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX }; MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false)); h->setLocalDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
h->clearEditDraft(); h->clearEditDraft();
bool opened = _history->peer() && (_history->peer()->id == peer); bool opened = _history->peer() && (_history->peer()->id == peer);
if (opened) { if (opened) {
@ -181,7 +181,7 @@ bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQ
History *h = App::history(peer); History *h = App::history(peer);
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->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false)); h->setLocalDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
h->clearEditDraft(); h->clearEditDraft();
bool opened = _history->peer() && (_history->peer()->id == peer); bool opened = _history->peer() && (_history->peer()->id == peer);
if (opened) { if (opened) {
@ -916,8 +916,9 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu
if (!v) return; if (!v) return;
if (v->isEmpty()) { if (v->isEmpty()) {
if (peer->isChat() && peer->asChat()->haveLeft()) { if (peer->isChat() && !peer->asChat()->haveLeft()) {
deleteConversation(peer, false); History *h = App::historyLoaded(peer->id);
if (h) Local::addSavedPeer(peer, h->lastMsgDate);
} else if (peer->isChannel()) { } else if (peer->isChannel()) {
if (peer->asChannel()->inviter > 0 && peer->asChannel()->amIn()) { if (peer->asChannel()->inviter > 0 && peer->asChannel()->amIn()) {
if (UserData *from = App::userLoaded(peer->asChannel()->inviter)) { if (UserData *from = App::userLoaded(peer->asChannel()->inviter)) {
@ -929,8 +930,7 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu
} }
} }
} else { } else {
History *h = App::historyLoaded(peer->id); deleteConversation(peer, false);
if (h) Local::addSavedPeer(peer, h->lastMsgDate);
} }
} else { } else {
History *h = App::history(peer->id); History *h = App::history(peer->id);
@ -1114,6 +1114,10 @@ void MainWidget::sendMessage(const MessageToSend &message) {
if (!sentEntities.c_vector().v.isEmpty()) { if (!sentEntities.c_vector().v.isEmpty()) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities; sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
} }
if (message.clearDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
history->clearCloudDraft();
}
lastMessage = history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(history->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread); lastMessage = history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(history->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread);
history->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_flags(sendFlags), history->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, history->sendRequestId); history->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_flags(sendFlags), history->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, history->sendRequestId);
} }
@ -3695,9 +3699,14 @@ void MainWidget::updateOnline(bool gotOtherOffline) {
_lastSetOnline = ms; _lastSetOnline = ms;
_onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline))); _onlineRequest = MTP::send(MTPaccount_UpdateStatus(MTP_bool(!isOnline)));
if (App::self()) App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1); if (App::self()) {
App::self()->onlineTill = unixtime() + (isOnline ? (Global::OnlineUpdatePeriod() / 1000) : -1);
}
if (!isOnline) { // Went offline, so we need to save message draft to the cloud.
saveDraftToCloud();
}
_lastSetOnline = getms(true); _lastSetOnline = ms;
updateOnlineDisplay(); updateOnlineDisplay();
} else if (isOnline) { } else if (isOnline) {
@ -3706,6 +3715,64 @@ void MainWidget::updateOnline(bool gotOtherOffline) {
_onlineTimer.start(updateIn); _onlineTimer.start(updateIn);
} }
void MainWidget::saveDraftToCloud() {
_history->saveFieldToHistoryLocalDraft();
auto peer = _history->peer();
if (auto history = App::historyLoaded(peer)) {
auto localDraft = history->localDraft();
auto cloudDraft = history->cloudDraft();
if (!historyDraftsAreEqual(localDraft, cloudDraft)) {
if (cloudDraft && cloudDraft->saveRequestId) {
MTP::cancel(cloudDraft->saveRequestId);
}
cloudDraft = history->createCloudDraft(localDraft);
MTPmessages_SaveDraft::Flags flags = 0;
auto &textWithTags = cloudDraft->textWithTags;
if (cloudDraft->previewCancelled) {
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
}
if (cloudDraft->msgId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id;
}
if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities;
}
auto entities = linksToMTP(entitiesFromTextTags(textWithTags.tags), true);
cloudDraft->saveRequestId = MTP::send(MTPmessages_SaveDraft(MTP_flags(flags), MTP_int(cloudDraft->msgId), peer->input, MTP_string(textWithTags.text), entities), rpcDone(&MainWidget::saveCloudDraftDone, peer), rpcFail(&MainWidget::saveCloudDraftFail, peer));
}
}
}
void MainWidget::applyCloudDraft(History *history) {
_history->applyCloudDraft(history);
}
void MainWidget::saveCloudDraftDone(PeerData *peer, const MTPBool &result, mtpRequestId requestId) {
if (auto history = App::historyLoaded(peer)) {
if (auto cloudDraft = history->cloudDraft()) {
if (cloudDraft->saveRequestId == requestId) {
cloudDraft->saveRequestId = 0;
history->updateChatListEntry();
}
}
}
}
bool MainWidget::saveCloudDraftFail(PeerData *peer, const RPCError &error, mtpRequestId requestId) {
if (MTP::isDefaultHandledError(error)) return false;
if (auto history = App::historyLoaded(peer)) {
if (auto cloudDraft = history->cloudDraft()) {
if (cloudDraft->saveRequestId == requestId) {
history->clearCloudDraft();
}
}
}
return true;
}
void MainWidget::checkIdleFinish() { void MainWidget::checkIdleFinish() {
if (this != App::main()) return; if (this != App::main()) return;
if (psIdleTime() < uint64(Global::OfflineIdleTimeout())) { if (psIdleTime() < uint64(Global::OfflineIdleTimeout())) {

View File

@ -232,6 +232,9 @@ public:
bool lastWasOnline() const; bool lastWasOnline() const;
uint64 lastSetOnline() const; uint64 lastSetOnline() const;
void saveDraftToCloud();
void applyCloudDraft(History *history);
int32 dlgsWidth() const; int32 dlgsWidth() const;
void forwardLayer(int32 forwardSelected = 0); // -1 - send paths void forwardLayer(int32 forwardSelected = 0); // -1 - send paths
@ -287,6 +290,7 @@ public:
MsgId replyTo = 0; MsgId replyTo = 0;
bool silent = false; bool silent = false;
WebPageId webPageId = 0; WebPageId webPageId = 0;
bool clearDraft = true;
}; };
void sendMessage(const MessageToSend &message); void sendMessage(const MessageToSend &message);
void saveRecentHashtags(const QString &text); void saveRecentHashtags(const QString &text);
@ -489,6 +493,9 @@ private:
void messagesAffected(PeerData *peer, const MTPmessages_AffectedMessages &result); void messagesAffected(PeerData *peer, const MTPmessages_AffectedMessages &result);
void overviewLoaded(History *history, const MTPmessages_Messages &result, mtpRequestId req); void overviewLoaded(History *history, const MTPmessages_Messages &result, mtpRequestId req);
void saveCloudDraftDone(PeerData *peer, const MTPBool &result, mtpRequestId requestId);
bool saveCloudDraftFail(PeerData *peer, const RPCError &error, mtpRequestId requestId);
bool _started = false; bool _started = false;
uint64 failedObjId = 0; uint64 failedObjId = 0;

View File

@ -89,6 +89,7 @@ void MacPrivate::notifyReplied(unsigned long long peer, int msgid, const char *s
message.textWithTags = { QString::fromUtf8(str), TextWithTags::Tags() }; message.textWithTags = { QString::fromUtf8(str), TextWithTags::Tags() };
message.replyTo = (msgid > 0 && !history->peer->isUser()) ? msgid : 0; message.replyTo = (msgid > 0 && !history->peer->isUser()) ? msgid : 0;
message.silent = false; message.silent = false;
message.clearDraft = false;
App::main()->sendMessage(message); App::main()->sendMessage(message);
} }

View File

@ -3068,6 +3068,7 @@ void Text::clear() {
delete *i; delete *i;
} }
clearFields(); clearFields();
_text.clear();
} }
void Text::clearFields() { void Text::clearFields() {