mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-07-24 06:22:59 +02:00
Make and display replies to tasks.
This commit is contained in:
parent
23f5102f1b
commit
b5c9b6f552
11 changed files with 169 additions and 21 deletions
|
@ -4260,6 +4260,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_context_to_msg" = "Go To Message";
|
||||
"lng_context_reply_msg" = "Reply";
|
||||
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||
"lng_context_reply_to_task" = "Reply to Task";
|
||||
"lng_context_edit_msg" = "Edit";
|
||||
"lng_context_add_factcheck" = "Add Fact Check";
|
||||
"lng_context_edit_factcheck" = "Edit Fact Check";
|
||||
|
@ -4450,6 +4451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
||||
"lng_preview_reply_to" = "Reply to {name}";
|
||||
"lng_preview_reply_to_quote" = "Reply to quote from {name}";
|
||||
"lng_preview_reply_to_task" = "Reply to task from {title}";
|
||||
|
||||
"lng_suggest_bar_title" = "Suggest a Post Below";
|
||||
"lng_suggest_bar_text" = "Click to offer a price for publishing.";
|
||||
|
|
|
@ -17,6 +17,7 @@ constexpr auto kSendReactionEmojiProperty = 0x04;
|
|||
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
||||
constexpr auto kDocumentFilenameTooltipProperty = 0x06;
|
||||
constexpr auto kPhoneNumberLinkProperty = 0x07;
|
||||
constexpr auto kTodoListItemIdProperty = 0x08;
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
|
|
|
@ -2342,6 +2342,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
const auto linkUserpicPeerId = (link && _dragStateUserpic)
|
||||
? link->property(kPeerLinkPeerIdProperty).toULongLong()
|
||||
: 0;
|
||||
const auto todoListTaskId = link
|
||||
? link->property(kTodoListItemIdProperty).toInt()
|
||||
: 0;
|
||||
const auto session = &this->session();
|
||||
_whoReactedMenuLifetime.destroy();
|
||||
if (!clickedReaction.empty()
|
||||
|
@ -2702,6 +2705,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
const auto selected = selectedQuote(item);
|
||||
auto text = (selected
|
||||
? tr::lng_context_quote_and_reply
|
||||
: todoListTaskId
|
||||
? tr::lng_context_reply_to_task
|
||||
: tr::lng_context_reply_msg)(
|
||||
tr::now,
|
||||
Ui::Text::FixAmpersandInAction);
|
||||
|
@ -2714,6 +2719,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
.messageId = itemId,
|
||||
.quote = quote,
|
||||
.quoteOffset = quoteOffset,
|
||||
.todoItemId = todoListTaskId,
|
||||
});
|
||||
if (!quote.empty()) {
|
||||
_widget->clearSelected();
|
||||
|
|
|
@ -4213,6 +4213,7 @@ void HistoryItem::createComponentsHelper(HistoryItemCommonFields &&fields) {
|
|||
: replyTo.monoforumPeerId
|
||||
? replyTo.monoforumPeerId
|
||||
: PeerId();
|
||||
config.reply.todoItemId = replyTo.todoItemId;
|
||||
const auto replyToTop = replyTo.topicRootId
|
||||
? replyTo.topicRootId
|
||||
: LookupReplyToTop(_history, to);
|
||||
|
|
|
@ -68,6 +68,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_changes.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_todo_list.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_photo.h"
|
||||
|
@ -8548,7 +8549,7 @@ void HistoryWidget::clearFieldText(
|
|||
void HistoryWidget::replyToMessage(FullReplyTo id) {
|
||||
if (const auto item = session().data().message(id.messageId)) {
|
||||
if (CanSendReply(item) && !base::IsCtrlPressed()) {
|
||||
replyToMessage(item, id.quote, id.quoteOffset);
|
||||
replyToMessage(item, id);
|
||||
} else if (item->allowsForward()) {
|
||||
const auto show = controller()->uiShow();
|
||||
HistoryView::Controls::ShowReplyToChatBox(show, id);
|
||||
|
@ -8561,16 +8562,12 @@ void HistoryWidget::replyToMessage(FullReplyTo id) {
|
|||
|
||||
void HistoryWidget::replyToMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithEntities quote,
|
||||
int quoteOffset) {
|
||||
FullReplyTo fields) {
|
||||
if (isJoinChannel()) {
|
||||
return;
|
||||
}
|
||||
_processingReplyTo = {
|
||||
.messageId = item->fullId(),
|
||||
.quote = quote,
|
||||
.quoteOffset = quoteOffset,
|
||||
};
|
||||
fields.messageId = item->fullId();
|
||||
_processingReplyTo = fields;
|
||||
_processingReplyItem = item;
|
||||
processReply();
|
||||
}
|
||||
|
@ -9231,11 +9228,24 @@ void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
|
|||
.session = &session(),
|
||||
.repaint = [=] { updateField(); },
|
||||
});
|
||||
const auto text = [&] {
|
||||
const auto media = _replyTo.todoItemId ? item->media() : nullptr;
|
||||
if (const auto todolist = media ? media->todolist() : nullptr) {
|
||||
const auto i = ranges::find(
|
||||
todolist->items,
|
||||
_replyTo.todoItemId,
|
||||
&TodoListItem::id);
|
||||
if (i != end(todolist->items)) {
|
||||
return i->text;
|
||||
}
|
||||
}
|
||||
return (_editMsgId || _replyTo.quote.empty())
|
||||
? item->inReplyText()
|
||||
: _replyTo.quote;
|
||||
}();
|
||||
_replyEditMsgText.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
((_editMsgId || _replyTo.quote.empty())
|
||||
? item->inReplyText()
|
||||
: _replyTo.quote),
|
||||
text,
|
||||
Ui::DialogTextOptions(),
|
||||
context);
|
||||
if (fieldOrDisabledShown() || isRecording()) {
|
||||
|
@ -9321,10 +9331,9 @@ void HistoryWidget::updateReplyToName() {
|
|||
.customEmojiLoopLimit = 1,
|
||||
});
|
||||
const auto to = _replyEditMsg ? _replyEditMsg : _kbReplyTo;
|
||||
const auto replyToQuote = _replyTo && !_replyTo.quote.empty();
|
||||
_replyToName.setMarkedText(
|
||||
st::fwdTextStyle,
|
||||
HistoryView::Reply::ComposePreviewName(_history, to, replyToQuote),
|
||||
HistoryView::Reply::ComposePreviewName(_history, to, _replyTo),
|
||||
Ui::NameTextOptions(),
|
||||
context);
|
||||
}
|
||||
|
|
|
@ -205,8 +205,7 @@ public:
|
|||
void replyToMessage(FullReplyTo id);
|
||||
void replyToMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithEntities quote = {},
|
||||
int quoteOffset = 0);
|
||||
FullReplyTo fields = {});
|
||||
void editMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextSelection &selection);
|
||||
|
|
|
@ -492,10 +492,9 @@ void FieldHeader::setShownMessage(HistoryItem *item) {
|
|||
.customEmojiLoopLimit = 1,
|
||||
});
|
||||
const auto replyTo = _replyTo.current();
|
||||
const auto quote = replyTo && !replyTo.quote.empty();
|
||||
_shownMessageName.setMarkedText(
|
||||
st::fwdTextStyle,
|
||||
HistoryView::Reply::ComposePreviewName(_history, item, quote),
|
||||
HistoryView::Reply::ComposePreviewName(_history, item, replyTo),
|
||||
Ui::NameTextOptions(),
|
||||
context);
|
||||
} else {
|
||||
|
|
|
@ -639,9 +639,14 @@ bool AddReplyToMessageAction(
|
|||
return false;
|
||||
}
|
||||
|
||||
const auto todoListTaskId = request.link
|
||||
? request.link->property(kTodoListItemIdProperty).toInt()
|
||||
: 0;
|
||||
const auto "e = request.quote;
|
||||
auto text = (quote.text.empty()
|
||||
? tr::lng_context_reply_msg
|
||||
: todoListTaskId
|
||||
? tr::lng_context_reply_to_task
|
||||
: tr::lng_context_quote_and_reply)(
|
||||
tr::now,
|
||||
Ui::Text::FixAmpersandInAction);
|
||||
|
@ -650,6 +655,7 @@ bool AddReplyToMessageAction(
|
|||
.messageId = itemId,
|
||||
.quote = quote.text,
|
||||
.quoteOffset = quote.offset,
|
||||
.todoItemId = todoListTaskId,
|
||||
}, base::IsCtrlPressed());
|
||||
}, &st::menuIconReply);
|
||||
return true;
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_story.h"
|
||||
#include "data/data_todo_list.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/view/history_view_item_preview.h"
|
||||
#include "history/history.h"
|
||||
|
@ -38,6 +39,85 @@ namespace {
|
|||
|
||||
constexpr auto kNonExpandedLinesLimit = 5;
|
||||
|
||||
[[nodiscard]] QImage MakeTaskImage() {
|
||||
const auto diameter = st::normalFont->ascent;
|
||||
const auto line = st::historyPollRadio.thickness;
|
||||
const auto size = 2 * line + diameter;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(Qt::transparent);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
p.setOpacity(st::historyPollRadioOpacity);
|
||||
|
||||
const auto rect = QRectF(line, line, diameter, diameter).marginsRemoved(
|
||||
QMarginsF(line / 2., line / 2., line / 2., line / 2.));
|
||||
auto pen = QPen(QColor(255, 255, 255));
|
||||
pen.setWidth(line);
|
||||
p.setPen(pen);
|
||||
p.drawEllipse(rect);
|
||||
|
||||
p.end();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage MakeTaskDoneImage() {
|
||||
const auto white = QColor(255, 255, 255);
|
||||
const auto black = QColor(0, 0, 0);
|
||||
|
||||
const auto diameter = st::normalFont->ascent;
|
||||
const auto line = st::historyPollRadio.thickness;
|
||||
const auto size = 2 * line + diameter;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
auto result = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
result.fill(black);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
const auto rect = QRectF(line, line, diameter, diameter).marginsRemoved(
|
||||
QMarginsF(line / 2., line / 2., line / 2., line / 2.));
|
||||
auto pen = QPen(white);
|
||||
pen.setWidth(line);
|
||||
p.setPen(pen);
|
||||
p.setBrush(white);
|
||||
p.drawEllipse(rect);
|
||||
const auto &icon = st::historyPollInChoiceRight;
|
||||
icon.paint(
|
||||
p,
|
||||
line + (diameter - icon.width()) / 2,
|
||||
line + (diameter - icon.height()) / 2,
|
||||
size,
|
||||
black);
|
||||
p.end();
|
||||
|
||||
return style::colorizeImage(result, white);
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities TaskDoneIcon(
|
||||
not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
MakeTaskDoneImage(),
|
||||
QMargins(0, st::lineWidth, st::lineWidth, 0)));
|
||||
}
|
||||
|
||||
[[nodiscard]] TextWithEntities TaskIcon(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
MakeTaskImage(),
|
||||
QMargins(0, st::lineWidth, st::lineWidth, 0)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ValidateBackgroundEmoji(
|
||||
|
@ -193,6 +273,22 @@ void Reply::update(
|
|||
const auto item = view->data();
|
||||
const auto &fields = data->fields();
|
||||
const auto message = data->resolvedMessage.get();
|
||||
const auto messageMedia = (message && fields.todoItemId)
|
||||
? message->media()
|
||||
: nullptr;
|
||||
const auto messageTodoList = messageMedia
|
||||
? messageMedia->todolist()
|
||||
: nullptr;
|
||||
const auto taskIndex = messageTodoList
|
||||
? int(ranges::find(
|
||||
messageTodoList->items,
|
||||
fields.todoItemId,
|
||||
&TodoListItem::id) - begin(messageTodoList->items))
|
||||
: -1;
|
||||
const auto task = (taskIndex >= 0
|
||||
&& taskIndex < messageTodoList->items.size())
|
||||
? &messageTodoList->items[taskIndex]
|
||||
: nullptr;
|
||||
const auto story = data->resolvedStory.get();
|
||||
const auto externalMedia = fields.externalMedia.get();
|
||||
if (!_externalSender) {
|
||||
|
@ -210,7 +306,6 @@ void Reply::update(
|
|||
_hiddenSenderColorIndexPlusOne = (!_colorPeer && message)
|
||||
? (message->originalHiddenSenderInfo()->colorIndex + 1)
|
||||
: 0;
|
||||
|
||||
const auto hasPreview = (story && story->hasReplyPreview())
|
||||
|| (message
|
||||
&& message->media()
|
||||
|
@ -225,8 +320,13 @@ void Reply::update(
|
|||
&& !fields.quote.empty();
|
||||
_hasQuoteIcon = hasQuoteIcon ? 1 : 0;
|
||||
|
||||
const auto session = &view->history()->session();
|
||||
const auto text = (!_displaying && data->unavailable())
|
||||
? TextWithEntities()
|
||||
: task
|
||||
? Ui::Text::Colorized(task->completionDate
|
||||
? TaskDoneIcon(session)
|
||||
: TaskIcon(session)).append(task->text)
|
||||
: (message && (fields.quote.empty() || !fields.manualQuote))
|
||||
? message->inReplyText()
|
||||
: !fields.quote.empty()
|
||||
|
@ -867,18 +967,28 @@ TextWithEntities Reply::ForwardEmoji(not_null<Data::Session*> owner) {
|
|||
TextWithEntities Reply::ComposePreviewName(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> to,
|
||||
bool quote) {
|
||||
const FullReplyTo &replyTo) {
|
||||
const auto sender = [&] {
|
||||
if (const auto from = to->displayFrom()) {
|
||||
return not_null(from);
|
||||
}
|
||||
return to->author();
|
||||
}();
|
||||
if (const auto media = replyTo.todoItemId ? to->media() : nullptr) {
|
||||
if (const auto todolist = media->todolist()) {
|
||||
return tr::lng_preview_reply_to_task(
|
||||
tr::now,
|
||||
lt_title,
|
||||
todolist->title,
|
||||
Ui::Text::WithEntities);
|
||||
}
|
||||
}
|
||||
const auto toPeer = to->history()->peer;
|
||||
const auto displayAsExternal = (to->history() != history);
|
||||
const auto groupNameAdded = displayAsExternal
|
||||
&& (toPeer != sender)
|
||||
&& (toPeer->isChat() || toPeer->isMegagroup());
|
||||
const auto quote = replyTo && !replyTo.quote.empty();
|
||||
const auto shorten = groupNameAdded || quote;
|
||||
|
||||
auto nameFull = TextWithEntities();
|
||||
|
|
|
@ -110,7 +110,7 @@ public:
|
|||
[[nodiscard]] static TextWithEntities ComposePreviewName(
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> to,
|
||||
bool quote);
|
||||
const FullReplyTo &replyTo);
|
||||
|
||||
private:
|
||||
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
|
||||
|
|
|
@ -334,9 +334,11 @@ void TodoList::updateTasks(bool skipAnimations) {
|
|||
ClickHandlerPtr TodoList::createTaskClickHandler(
|
||||
const Task &task) {
|
||||
const auto id = task.id;
|
||||
return std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||
auto result = std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
|
||||
toggleCompletion(id);
|
||||
}));
|
||||
result->setProperty(kTodoListItemIdProperty, id);
|
||||
return result;
|
||||
}
|
||||
|
||||
void TodoList::startToggleAnimation(Task &task) {
|
||||
|
@ -375,11 +377,24 @@ void TodoList::toggleCompletion(int id) {
|
|||
if (i == end(_tasks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto selected = (i->completionDate != 0);
|
||||
i->completionDate = selected ? TimeId() : base::unixtime::now();
|
||||
if (!selected) {
|
||||
i->setCompletedBy(_parent->history()->session().user());
|
||||
}
|
||||
|
||||
const auto parentMedia = _parent->data()->media();
|
||||
const auto baseList = parentMedia ? parentMedia->todolist() : nullptr;
|
||||
if (baseList) {
|
||||
const auto j = ranges::find(baseList->items, id, &TodoListItem::id);
|
||||
if (j != end(baseList->items)) {
|
||||
j->completionDate = i->completionDate;
|
||||
j->completedBy = i->completedBy;
|
||||
}
|
||||
history()->owner().updateDependentMessages(_parent->data());
|
||||
}
|
||||
|
||||
startToggleAnimation(*i);
|
||||
repaint();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue