diff --git a/Telegram/SourceFiles/ayu/data/ayu_database.cpp b/Telegram/SourceFiles/ayu/data/ayu_database.cpp index 3631831ab7..a8bcf95141 100644 --- a/Telegram/SourceFiles/ayu/data/ayu_database.cpp +++ b/Telegram/SourceFiles/ayu/data/ayu_database.cpp @@ -25,6 +25,11 @@ auto storage = make_storage( column(&EditedMessage::userId), column(&EditedMessage::dialogId), column(&EditedMessage::messageId)), + make_index("idx_local_message_userId_dialogId_topicId_messageId", + column(&LocalMessage::userId), + column(&LocalMessage::dialogId), + column(&LocalMessage::topicId), + column(&LocalMessage::messageId)), make_table( "DeletedMessage", make_column("fakeId", &DeletedMessage::fakeId, primary_key().autoincrement()), @@ -97,6 +102,42 @@ auto storage = make_storage( make_column("documentAttributesSerialized", &EditedMessage::documentAttributesSerialized), make_column("mimeType", &EditedMessage::mimeType) ), + make_table( + "LocalMessage", + make_column("fakeId", &LocalMessage::fakeId, primary_key().autoincrement()), + make_column("userId", &LocalMessage::userId), + make_column("dialogId", &LocalMessage::dialogId), + make_column("groupedId", &LocalMessage::groupedId), + make_column("peerId", &LocalMessage::peerId), + make_column("fromId", &LocalMessage::fromId), + make_column("topicId", &LocalMessage::topicId), + make_column("messageId", &LocalMessage::messageId), + make_column("date", &LocalMessage::date), + make_column("flags", &LocalMessage::flags), + make_column("editDate", &LocalMessage::editDate), + make_column("views", &LocalMessage::views), + make_column("fwdFlags", &LocalMessage::fwdFlags), + make_column("fwdFromId", &LocalMessage::fwdFromId), + make_column("fwdName", &LocalMessage::fwdName), + make_column("fwdDate", &LocalMessage::fwdDate), + make_column("fwdPostAuthor", &LocalMessage::fwdPostAuthor), + make_column("replyFlags", &LocalMessage::replyFlags), + make_column("replyMessageId", &LocalMessage::replyMessageId), + make_column("replyPeerId", &LocalMessage::replyPeerId), + make_column("replyTopId", &LocalMessage::replyTopId), + make_column("replyForumTopic", &LocalMessage::replyForumTopic), + make_column("replySerialized", &LocalMessage::replySerialized), + make_column("entityCreateDate", &LocalMessage::entityCreateDate), + make_column("text", &LocalMessage::text), + make_column("textEntities", &LocalMessage::textEntities), + make_column("mediaPath", &LocalMessage::mediaPath), + make_column("hqThumbPath", &LocalMessage::hqThumbPath), + make_column("documentType", &LocalMessage::documentType), + make_column("documentSerialized", &LocalMessage::documentSerialized), + make_column("thumbsSerialized", &LocalMessage::thumbsSerialized), + make_column("documentAttributesSerialized", &LocalMessage::documentAttributesSerialized), + make_column("mimeType", &LocalMessage::mimeType) + ), make_table( "DeletedDialog", make_column("fakeId", &DeletedDialog::fakeId, primary_key().autoincrement()), @@ -160,6 +201,47 @@ void moveCurrentDatabase() { } } +void addLocalMessage(const LocalMessage &message) { + try { + storage.begin_transaction(); + storage.insert(message); + storage.commit(); + } catch (std::exception &ex) { + LOG(("Failed to save local message for some reason: %1").arg(ex.what())); + } +} + +std::vector getLocalMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit) { + return storage.get_all( + where( + column(&LocalMessage::userId) == userId and + column(&LocalMessage::dialogId) == dialogId and + (column(&LocalMessage::topicId) == topicId or topicId == 0) and + (column(&LocalMessage::messageId) > minId or minId == 0) and + (column(&LocalMessage::messageId) < maxId or maxId == 0) + ), + order_by(column(&LocalMessage::messageId)).desc(), + limit(totalLimit) + ); +} + +bool hasLocalMessages(ID userId, ID dialogId, ID topicId) { + try { + return !storage.select( + columns(column(&LocalMessage::dialogId)), + where( + column(&LocalMessage::userId) == userId and + column(&LocalMessage::dialogId) == dialogId and + (column(&LocalMessage::topicId) == topicId or topicId == 0) + ), + limit(1) + ).empty(); + } catch (std::exception &ex) { + LOG(("Failed to check if dialog has local message: %1").arg(ex.what())); + return false; + } +} + void initialize() { auto movePrevious = false; diff --git a/Telegram/SourceFiles/ayu/data/ayu_database.h b/Telegram/SourceFiles/ayu/data/ayu_database.h index 417113ba5a..1c84774d51 100644 --- a/Telegram/SourceFiles/ayu/data/ayu_database.h +++ b/Telegram/SourceFiles/ayu/data/ayu_database.h @@ -20,4 +20,8 @@ void addDeletedMessage(const DeletedMessage &message); std::vector getDeletedMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit); bool hasDeletedMessages(ID userId, ID dialogId, ID topicId); +void addLocalMessage(const LocalMessage &message); +std::vector getLocalMessages(ID userId, ID dialogId, ID topicId, ID minId, ID maxId, int totalLimit); +bool hasLocalMessages(ID userId, ID dialogId, ID topicId); + } diff --git a/Telegram/SourceFiles/ayu/data/entities.h b/Telegram/SourceFiles/ayu/data/entities.h index 52e470b698..7ad0288db8 100644 --- a/Telegram/SourceFiles/ayu/data/entities.h +++ b/Telegram/SourceFiles/ayu/data/entities.h @@ -58,6 +58,10 @@ class EditedMessage : public AyuMessageBase { }; +class LocalMessage : public AyuMessageBase +{ +}; + class DeletedDialog { public: diff --git a/Telegram/SourceFiles/ayu/data/messages_storage.cpp b/Telegram/SourceFiles/ayu/data/messages_storage.cpp index 22721c9be4..cf91b9f78d 100644 --- a/Telegram/SourceFiles/ayu/data/messages_storage.cpp +++ b/Telegram/SourceFiles/ayu/data/messages_storage.cpp @@ -139,4 +139,28 @@ bool hasDeletedMessages(not_null peer, ID topicId) { return AyuDatabase::hasDeletedMessages(userId, getDialogIdFromPeer(peer), topicId); } +void addLocalMessage(not_null item) { + LocalMessage message; + map(item, message); + + // Optionally, add checks similar to addEditedMessage or addDeletedMessage + // if (message.text.empty()) { + // return; + // } + + AyuDatabase::addLocalMessage(message); +} + +std::vector +getLocalMessages(not_null peer, ID topicId, ID minId, ID maxId, int totalLimit) { + const ID userId = peer->session().userId().bare & PeerId::kChatTypeMask; + return convertToBase( + AyuDatabase::getLocalMessages(userId, getDialogIdFromPeer(peer), topicId, minId, maxId, totalLimit)); +} + +bool hasLocalMessages(not_null peer, ID topicId) { + const ID userId = peer->session().userId().bare & PeerId::kChatTypeMask; + return AyuDatabase::hasLocalMessages(userId, getDialogIdFromPeer(peer), topicId); +} + } diff --git a/Telegram/SourceFiles/ayu/data/messages_storage.h b/Telegram/SourceFiles/ayu/data/messages_storage.h index 8780ae1d12..d1dcaf8175 100644 --- a/Telegram/SourceFiles/ayu/data/messages_storage.h +++ b/Telegram/SourceFiles/ayu/data/messages_storage.h @@ -20,4 +20,8 @@ void addDeletedMessage(not_null item); std::vector getDeletedMessages(not_null peer, ID topicId, ID minId, ID maxId, int totalLimit); bool hasDeletedMessages(not_null peer, ID topicId); +void addLocalMessage(not_null item); +std::vector getLocalMessages(not_null peer, ID topicId, ID minId, ID maxId, int totalLimit); +bool hasLocalMessages(not_null peer, ID topicId); + } diff --git a/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp b/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp index 8770f059af..90e214815f 100644 --- a/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp +++ b/Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp @@ -117,8 +117,11 @@ void GenerateItems( addPart(makeSimpleTextMessage(std::move(text))); }; - const auto text = QString::fromStdString(message.text); - addSimpleTextMessage(Ui::Text::WithEntities(text)); + QString textString = QString::fromStdString(message.text); + if (dynamic_cast(&message)) { + textString = QStringLiteral("[Local] ") + textString; + } + addSimpleTextMessage(Ui::Text::WithEntities(textString)); } } // namespace MessageHistory diff --git a/Telegram/SourceFiles/boxes/add_local_message_box.cpp b/Telegram/SourceFiles/boxes/add_local_message_box.cpp new file mode 100644 index 0000000000..5d146339a2 --- /dev/null +++ b/Telegram/SourceFiles/boxes/add_local_message_box.cpp @@ -0,0 +1,83 @@ +#include "boxes/add_local_message_box.h" + +#include "ui/widgets/labels.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/layout.h" +#include "ui/wrap/padding_wrap.h" +#include "lang/lang_keys.h" +#include "window/window_session_controller.h" +#include "main/main_session.h" +#include "data/data_user.h" +#include "styles/style_layers.h" +#include "styles/style_boxes.h" + +AddLocalMessageBox::AddLocalMessageBox(QWidget*, not_null controller) +: BoxContent(controller->uiShow()) +, _controller(controller) +, _senderField( + this, + st::defaultInputField, + tr::lng_local_message_sender_ph(), // Placeholder for sender name + _controller->session().user()->name()) // Default to current user's name +, _messageField( + this, + st::defaultInputFieldContext, // Use context for multiline + tr::lng_local_message_text_ph()) { // Placeholder for message text + setupControls(); +} + +void AddLocalMessageBox::setupControls() { + const auto content =verticalLayout(); + setTitle(tr::lng_box_add_local_message_title()); + + content->add(object_ptr( + content, + tr::lng_local_message_sender_label(), // "Sender:" + st::boxLabel)); + content->add(object_ptr>( + content, + base::duplicate(_senderField), + st::boxPadding)); + + content->add(object_ptr( + content, + tr::lng_local_message_text_label(), // "Message Text:" + st::boxLabel)); + _messageField->setInstantInserts(true); + _messageField->setSubmitSettings(Ui::InputField::SubmitSettings::Both); + _messageField->setMaxHeight(st::boxMaxListHeight / 2); // Allow ample space for text + content->add(object_ptr>( + content, + base::duplicate(_messageField), + st::boxPadding)); + + _submitButton = addButton(tr::lng_box_ok(), [=] { save(); }); + addButton(tr::lng_cancel(), [=] { closeBox(); }); +} + +void AddLocalMessageBox::prepare() { + setDimensions(st::boxWidth, layout()->heightForWidth(st::boxWidth)); + _senderField->setFocus(); +} + +void AddLocalMessageBox::setInnerFocus() { + _senderField->setFocus(); +} + +void AddLocalMessageBox::save() { + const auto senderName = _senderField->getLastText().trimmed(); + const auto messageText = _messageField->getLastText().trimmed(); + + if (messageText.isEmpty()) { + _messageField->showError(); + return; + } + + _saveLocalMessageRequests.fire({senderName, messageText}); + closeBox(); +} + +rpl::producer AddLocalMessageBox::saveLocalMessageRequests() const { + return _saveLocalMessageRequests.events(); +} diff --git a/Telegram/SourceFiles/boxes/add_local_message_box.h b/Telegram/SourceFiles/boxes/add_local_message_box.h new file mode 100644 index 0000000000..65bc307cea --- /dev/null +++ b/Telegram/SourceFiles/boxes/add_local_message_box.h @@ -0,0 +1,37 @@ +#pragma once + +#include "ui/layers/box_content.h" +#include "ui/widgets/input_fields.h" +#include "ui/widgets/buttons.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +class AddLocalMessageBox : public Ui::BoxContent { +public: + AddLocalMessageBox(QWidget*, not_null controller); + + struct LocalMessageData { + QString senderName; + QString messageText; + }; + + rpl::producer saveLocalMessageRequests() const; + +protected: + void prepare() override; + void setInnerFocus() override; + +private: + void setupControls(); + void save(); + + not_null _controller; + object_ptr _senderField; + object_ptr _messageField; + object_ptr _submitButton; + + rpl::event_stream _saveLocalMessageRequests; + +}; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b2113c07aa..d4687b15a7 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -179,6 +179,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" #include "styles/style_chat_helpers.h" #include "styles/style_info.h" +#include "ui/widgets/popup_menu.h" // Required for Ui::PopupMenu +#include "boxes/add_local_message_box.h" // Will be created +#include "ayu/data/messages_storage.h" // Required for AyuMessages::addLocalMessage #include #include @@ -542,6 +545,10 @@ HistoryWidget::HistoryWidget( supportInitAutocomplete(); } _field->rawTextEdit()->installEventFilter(this); + _field->setContextMenuPolicy(Qt::CustomContextMenu); + connect(_field, &Ui::InputField::customContextMenuRequested, this, [this](const QPoint &pos) { + showFieldContextMenu(pos); + }); _field->setMimeDataHook([=]( not_null data, Ui::InputField::MimeAction action) { @@ -9563,3 +9570,55 @@ HistoryWidget::~HistoryWidget() { } setTabbedPanel(nullptr); } + +void HistoryWidget::showFieldContextMenu(const QPoint &pos) { + auto menu = base::make_unique_q(this, st::defaultPopupMenu); + _field->fillContextMenu(menu.get()); + if (_history && _canSendMessages) { // Only show if we can potentially add a message + menu->addSeparator(); + menu->addAction(tr::lng_context_add_local_message(tr::now), [=] { // Assuming this lang key will be added + showAddLocalMessageBox(); + }); + } + menu->popup(_field->mapToGlobal(pos)); +} + +void HistoryWidget::showAddLocalMessageBox() { + if (!_history) return; + + auto box = Ui::Show(Box(this), Ui::LayerOption::KeepOther); + box->saveLocalMessageRequests( + ) | rpl::start_with_next([=](const AddLocalMessageBox::LocalMessageData &data) { + handleAddLocalMessage(data.senderName, data.messageText); + }, box->lifetime()); +} + +void HistoryWidget::handleAddLocalMessage(const QString &senderName, const QString &messageText) { + if (!_history || messageText.isEmpty()) { + return; + } + + PeerData *fromPeer = session().user(); + QString finalPostAuthor = QString(); + + if (!senderName.isEmpty() && senderName != fromPeer->name()) { + // This is a simplified approach. A real implementation might involve + // searching for the user or allowing creation of a placeholder. + // For now, if a name is provided that isn't the current user, + // we'll use the current user as the sender but set the postAuthor. + finalPostAuthor = senderName; + LOG(("Warning: Custom sender '%1' requested for local message. Defaulting to current user as sender, using name as postAuthor.").arg(senderName)); + } + + + HistoryItem *newItem = _history->makeMessage({ + .id = _history->nextNonHistoryEntryId(), + .flags = MessageFlag::FakeHistoryItem | MessageFlag::HasFromId | (finalPostAuthor.isEmpty() ? MessageFlag() : MessageFlag::HasPostAuthor), + .from = fromPeer->id, + .date = base::unixtime::now(), + .postAuthor = finalPostAuthor, + }, { messageText }, MTP_messageMediaEmpty()); + + AyuMessages::addLocalMessage(newItem); + // The UI should update automatically due to existing mechanisms for new messages. +}