Add local message editor and manager for custom messages

Co-authored-by: g6582566 <g6582566@morris.umn.edu>
This commit is contained in:
Cursor Agent 2025-07-23 23:08:34 +00:00
parent 683aef44f9
commit 00840d76e1
9 changed files with 1526 additions and 79 deletions

View file

@ -6909,3 +6909,64 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_AddLocalMessageTitle" = "Add Local Message"; "ayu_AddLocalMessageTitle" = "Add Local Message";
"ayu_AddLocalMessagePlaceholder" = "Message text..."; "ayu_AddLocalMessagePlaceholder" = "Message text...";
"ayu_AddLocalMessageFromPlaceholder" = "From user (optional)..."; "ayu_AddLocalMessageFromPlaceholder" = "From user (optional)...";
// Local Message Editor
"ayu_LocalMessageEditorTitle" = "Local Message Editor";
"ayu_LocalMessageText" = "Message Text";
"ayu_LocalMessageTextPlaceholder" = "Enter message text...";
"ayu_LocalMessageType" = "Message Type";
"ayu_LocalMessageIsService" = "Service message";
"ayu_LocalMessageServiceText" = "Service text";
"ayu_LocalMessageAuthor" = "Author";
"ayu_LocalMessageFrom" = "From user";
"ayu_LocalMessagePostAuthor" = "Post author (for channels)";
"ayu_LocalMessageViaBot" = "Via bot";
"ayu_LocalMessageTiming" = "Date & Time";
"ayu_LocalMessageDate" = "Date (DD.MM.YYYY)";
"ayu_LocalMessageTime" = "Time (HH:MM:SS)";
"ayu_LocalMessageWasEdited" = "Show as edited";
"ayu_LocalMessageEditDate" = "Edit date (DD.MM.YYYY)";
"ayu_LocalMessageEditTime" = "Edit time (HH:MM:SS)";
"ayu_LocalMessageFlags" = "Message Flags";
"ayu_LocalMessageSilent" = "Silent message";
"ayu_LocalMessagePinned" = "Pinned message";
"ayu_LocalMessageNoForwards" = "Restrict forwarding";
"ayu_LocalMessageInvertMedia" = "Invert media";
"ayu_LocalMessageStats" = "Statistics";
"ayu_LocalMessageHasViews" = "Show views";
"ayu_LocalMessageViews" = "Views count";
"ayu_LocalMessageHasShares" = "Show shares";
"ayu_LocalMessageShares" = "Shares count";
"ayu_LocalMessageReply" = "Reply";
"ayu_LocalMessageReplyTo" = "Reply to message ID";
"ayu_LocalMessageReplyQuote" = "Reply quote text";
"ayu_LocalMessageForward" = "Forward";
"ayu_LocalMessageIsForwarded" = "Forwarded message";
"ayu_LocalMessageForwardFrom" = "Forward from user";
"ayu_LocalMessageForwardFromName" = "Forward from name";
"ayu_LocalMessageForwardDate" = "Forward date (DD.MM.YYYY)";
"ayu_LocalMessageForwardTime" = "Forward time (HH:MM:SS)";
"ayu_LocalMessageForwardPostAuthor" = "Forward post author";
"ayu_LocalMessageMedia" = "Media";
"ayu_LocalMessageHasMedia" = "Has media";
"ayu_LocalMessageMediaPath" = "Media file path";
"ayu_LocalMessageMediaCaption" = "Media caption";
"ayu_LocalMessageAdvanced" = "Advanced";
"ayu_LocalMessageStarsPaid" = "Stars paid";
"ayu_LocalMessageEffectId" = "Effect ID";
"ayu_LocalMessageGroupedId" = "Grouped ID";
// Local Messages Manager
"ayu_LocalMessagesManager" = "Manage Local Messages";
"ayu_LocalMessagesManagerTitle" = "Local Messages Manager";
"ayu_LocalMessagesSearch" = "Search";
"ayu_LocalMessagesSearchPlaceholder" = "Search messages...";
"ayu_LocalMessagesStats" = "{count} local messages";
"ayu_LocalMessageCreateNew" = "Create New Message";
"ayu_LocalMessagesEmpty" = "No local messages in this chat";
"ayu_LocalMessagesNoResults" = "No messages found";
"ayu_LocalMessageEmptyText" = "(empty message)";
"ayu_LocalMessageUnknownSender" = "Unknown sender";
"ayu_LocalMessageEdit" = "Edit";
"ayu_LocalMessageDelete" = "Delete";
"ayu_LocalMessageDeleteConfirm" = "Delete local message: \"{message}\"?";

View file

@ -0,0 +1,803 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu/ui/boxes/local_message_editor.h"
#include "ayu/data/messages_storage.h"
#include "ayu/ui/context_menu/context_menu.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "history/history.h"
#include "lang_auto.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/masked_input_field.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/controls/userpic_button.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include <QtCore/QDateTime>
namespace AyuUi {
namespace {
constexpr auto kMaxMessageLength = 4096;
constexpr auto kMaxCaptionLength = 1024;
constexpr auto kMaxPostAuthorLength = 255;
class LocalMessageEditor {
public:
LocalMessageEditor(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history,
const LocalMessageData &initialData);
private:
void setupContent();
void setupBasicFields();
void setupAdvancedFields();
void setupMessageTypeSection();
void setupAuthorSection();
void setupTimingSection();
void setupFlagsSection();
void setupStatsSection();
void setupReplySection();
void setupForwardSection();
void setupMediaSection();
void setupButtons();
void updateFromUser();
void updateDateTime();
void updatePreview();
void saveMessage();
[[nodiscard]] MessageFlags buildFlags() const;
[[nodiscard]] HistoryItemCommonFields buildFields() const;
const not_null<Ui::GenericBox*> _box;
const not_null<Window::SessionController*> _controller;
const not_null<History*> _history;
LocalMessageData _data;
// UI Elements
not_null<Ui::ScrollArea*> _scroll;
not_null<Ui::VerticalLayout*> _content;
// Basic fields
Ui::InputField* _textField = nullptr;
Ui::InputField* _fromField = nullptr;
Ui::UserpicButton* _fromUserpic = nullptr;
// Message type
Ui::Checkbox* _isServiceMessage = nullptr;
Ui::SlideWrap<Ui::InputField>* _serviceTextWrap = nullptr;
// Author section
Ui::InputField* _postAuthorField = nullptr;
Ui::InputField* _viaBotField = nullptr;
// Timing section
Ui::MaskedInputField* _dateField = nullptr;
Ui::MaskedInputField* _timeField = nullptr;
Ui::Checkbox* _wasEditedCheck = nullptr;
Ui::MaskedInputField* _editDateField = nullptr;
Ui::MaskedInputField* _editTimeField = nullptr;
// Flags section
Ui::Checkbox* _silentCheck = nullptr;
Ui::Checkbox* _pinnedCheck = nullptr;
Ui::Checkbox* _noForwardsCheck = nullptr;
Ui::Checkbox* _invertMediaCheck = nullptr;
// Stats section
Ui::Checkbox* _hasViewsCheck = nullptr;
Ui::MaskedInputField* _viewsField = nullptr;
Ui::Checkbox* _hasSharesCheck = nullptr;
Ui::MaskedInputField* _sharesField = nullptr;
// Reply section
Ui::InputField* _replyToField = nullptr;
Ui::InputField* _replyQuoteField = nullptr;
// Forward section
Ui::Checkbox* _isForwardedCheck = nullptr;
Ui::SlideWrap<Ui::VerticalLayout>* _forwardWrap = nullptr;
Ui::InputField* _forwardFromField = nullptr;
Ui::InputField* _forwardFromNameField = nullptr;
Ui::MaskedInputField* _forwardDateField = nullptr;
Ui::MaskedInputField* _forwardTimeField = nullptr;
Ui::InputField* _forwardPostAuthorField = nullptr;
// Media section
Ui::Checkbox* _hasMediaCheck = nullptr;
Ui::SlideWrap<Ui::VerticalLayout>* _mediaWrap = nullptr;
Ui::InputField* _mediaPathField = nullptr;
Ui::InputField* _mediaCaptionField = nullptr;
// Business section
Ui::MaskedInputField* _starsPaidField = nullptr;
Ui::MaskedInputField* _effectIdField = nullptr;
Ui::MaskedInputField* _groupedIdField = nullptr;
};
LocalMessageEditor::LocalMessageEditor(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history,
const LocalMessageData &initialData)
: _box(box)
, _controller(controller)
, _history(history)
, _data(initialData)
, _scroll(_box->addRow(object_ptr<Ui::ScrollArea>(_box)))
, _content(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(_scroll))) {
// Initialize default values
if (_data.date == 0) {
_data.date = base::unixtime::now();
}
if (_data.fromId == 0) {
_data.fromId = _history->peer->id;
}
setupContent();
}
void LocalMessageEditor::setupContent() {
_box->setTitle(tr::ayu_LocalMessageEditorTitle());
_box->setWidth(st::boxWideWidth);
setupBasicFields();
setupMessageTypeSection();
setupAuthorSection();
setupTimingSection();
setupFlagsSection();
setupStatsSection();
setupReplySection();
setupForwardSection();
setupMediaSection();
setupButtons();
_scroll->setMaxHeight(st::boxMaxListHeight);
updatePreview();
}
void LocalMessageEditor::setupBasicFields() {
// Text field
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageText(),
st::boxLabel));
_textField = _content->add(object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageTextPlaceholder(),
_data.text));
_textField->setMaxLength(kMaxMessageLength);
_textField->heightValue() | rpl::start_with_next([=](int height) {
if (height > st::defaultInputField.heightMin * 3) {
_textField->setMaxHeight(st::defaultInputField.heightMin * 3);
}
}, _textField->lifetime());
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupMessageTypeSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageType(),
st::boxLabel));
_isServiceMessage = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageIsService(),
_data.isService,
st::defaultCheckbox));
_serviceTextWrap = _content->add(object_ptr<Ui::SlideWrap<Ui::InputField>>(
_content,
object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageServiceText(),
_data.serviceText)));
_serviceTextWrap->toggle(_data.isService, anim::type::instant);
_isServiceMessage->checkedChanges() | rpl::start_with_next([=](bool checked) {
_data.isService = checked;
_serviceTextWrap->toggle(checked, anim::type::normal);
}, _isServiceMessage->lifetime());
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupAuthorSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageAuthor(),
st::boxLabel));
// From user field with userpic
const auto fromContainer = _content->add(object_ptr<Ui::RpWidget>(_content));
fromContainer->resize(fromContainer->width(), st::defaultInputField.height);
_fromUserpic = Ui::CreateChild<Ui::UserpicButton>(
fromContainer,
st::defaultUserpicButton);
_fromUserpic->move(0, 0);
_fromField = Ui::CreateChild<Ui::InputField>(
fromContainer,
st::defaultInputField,
tr::ayu_LocalMessageFrom(),
QString());
_fromField->move(st::defaultUserpicButton.size.width() + st::boxMediumSkip, 0);
fromContainer->widthValue() | rpl::start_with_next([=](int width) {
_fromField->resize(
width - st::defaultUserpicButton.size.width() - st::boxMediumSkip,
st::defaultInputField.height);
}, fromContainer->lifetime());
// Post author field (for channels)
_postAuthorField = _content->add(object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessagePostAuthor(),
_data.postAuthor));
_postAuthorField->setMaxLength(kMaxPostAuthorLength);
// Via bot field
_viaBotField = _content->add(object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageViaBot(),
QString()));
updateFromUser();
_fromField->changes() | rpl::start_with_next([=] {
updateFromUser();
}, _fromField->lifetime());
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupTimingSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageTiming(),
st::boxLabel));
// Date and time fields
const auto dateTimeContainer = _content->add(object_ptr<Ui::RpWidget>(_content));
dateTimeContainer->resize(dateTimeContainer->width(), st::defaultInputField.height);
_dateField = Ui::CreateChild<Ui::MaskedInputField>(
dateTimeContainer,
st::defaultInputField,
tr::ayu_LocalMessageDate(),
QString());
_dateField->setMask("99.99.9999");
_dateField->move(0, 0);
_timeField = Ui::CreateChild<Ui::MaskedInputField>(
dateTimeContainer,
st::defaultInputField,
tr::ayu_LocalMessageTime(),
QString());
_timeField->setMask("99:99:99");
dateTimeContainer->widthValue() | rpl::start_with_next([=](int width) {
const auto fieldWidth = (width - st::boxMediumSkip) / 2;
_dateField->resize(fieldWidth, st::defaultInputField.height);
_timeField->resize(fieldWidth, st::defaultInputField.height);
_timeField->move(fieldWidth + st::boxMediumSkip, 0);
}, dateTimeContainer->lifetime());
// Edit date section
_wasEditedCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageWasEdited(),
_data.wasEdited,
st::defaultCheckbox));
const auto editDateContainer = _content->add(object_ptr<Ui::RpWidget>(_content));
editDateContainer->resize(editDateContainer->width(), st::defaultInputField.height);
_editDateField = Ui::CreateChild<Ui::MaskedInputField>(
editDateContainer,
st::defaultInputField,
tr::ayu_LocalMessageEditDate(),
QString());
_editDateField->setMask("99.99.9999");
_editDateField->move(0, 0);
_editTimeField = Ui::CreateChild<Ui::MaskedInputField>(
editDateContainer,
st::defaultInputField,
tr::ayu_LocalMessageEditTime(),
QString());
_editTimeField->setMask("99:99:99");
editDateContainer->widthValue() | rpl::start_with_next([=](int width) {
const auto fieldWidth = (width - st::boxMediumSkip) / 2;
_editDateField->resize(fieldWidth, st::defaultInputField.height);
_editTimeField->resize(fieldWidth, st::defaultInputField.height);
_editTimeField->move(fieldWidth + st::boxMediumSkip, 0);
}, editDateContainer->lifetime());
editDateContainer->setVisible(_data.wasEdited);
_wasEditedCheck->checkedChanges() | rpl::start_with_next([=](bool checked) {
_data.wasEdited = checked;
editDateContainer->setVisible(checked);
}, _wasEditedCheck->lifetime());
updateDateTime();
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupFlagsSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageFlags(),
st::boxLabel));
_silentCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageSilent(),
_data.silent,
st::defaultCheckbox));
_pinnedCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessagePinned(),
_data.pinned,
st::defaultCheckbox));
_noForwardsCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageNoForwards(),
_data.noForwards,
st::defaultCheckbox));
_invertMediaCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageInvertMedia(),
_data.invertMedia,
st::defaultCheckbox));
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupStatsSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageStats(),
st::boxLabel));
// Views
_hasViewsCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageHasViews(),
_data.hasViews,
st::defaultCheckbox));
_viewsField = _content->add(object_ptr<Ui::MaskedInputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageViews(),
QString::number(_data.views)));
_viewsField->setMask("999999999");
_viewsField->setVisible(_data.hasViews);
_hasViewsCheck->checkedChanges() | rpl::start_with_next([=](bool checked) {
_data.hasViews = checked;
_viewsField->setVisible(checked);
}, _hasViewsCheck->lifetime());
// Shares
_hasSharesCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageHasShares(),
_data.hasShares,
st::defaultCheckbox));
_sharesField = _content->add(object_ptr<Ui::MaskedInputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageShares(),
QString::number(_data.shares)));
_sharesField->setMask("999999999");
_sharesField->setVisible(_data.hasShares);
_hasSharesCheck->checkedChanges() | rpl::start_with_next([=](bool checked) {
_data.hasShares = checked;
_sharesField->setVisible(checked);
}, _hasSharesCheck->lifetime());
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupReplySection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageReply(),
st::boxLabel));
_replyToField = _content->add(object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageReplyTo(),
QString()));
_replyQuoteField = _content->add(object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageReplyQuote(),
QString()));
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupForwardSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageForward(),
st::boxLabel));
_isForwardedCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageIsForwarded(),
_data.isForwarded,
st::defaultCheckbox));
const auto forwardContent = _content->add(object_ptr<Ui::VerticalLayout>(_content));
_forwardWrap = _content->add(object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_content,
object_ptr<Ui::VerticalLayout>(_content)));
auto forwardFields = _forwardWrap->entity();
_forwardFromField = forwardFields->add(object_ptr<Ui::InputField>(
forwardFields,
st::defaultInputField,
tr::ayu_LocalMessageForwardFrom(),
QString()));
_forwardFromNameField = forwardFields->add(object_ptr<Ui::InputField>(
forwardFields,
st::defaultInputField,
tr::ayu_LocalMessageForwardFromName(),
_data.forwardFromName));
// Forward date and time
const auto forwardDateContainer = forwardFields->add(object_ptr<Ui::RpWidget>(forwardFields));
forwardDateContainer->resize(forwardDateContainer->width(), st::defaultInputField.height);
_forwardDateField = Ui::CreateChild<Ui::MaskedInputField>(
forwardDateContainer,
st::defaultInputField,
tr::ayu_LocalMessageForwardDate(),
QString());
_forwardDateField->setMask("99.99.9999");
_forwardDateField->move(0, 0);
_forwardTimeField = Ui::CreateChild<Ui::MaskedInputField>(
forwardDateContainer,
st::defaultInputField,
tr::ayu_LocalMessageForwardTime(),
QString());
_forwardTimeField->setMask("99:99:99");
forwardDateContainer->widthValue() | rpl::start_with_next([=](int width) {
const auto fieldWidth = (width - st::boxMediumSkip) / 2;
_forwardDateField->resize(fieldWidth, st::defaultInputField.height);
_forwardTimeField->resize(fieldWidth, st::defaultInputField.height);
_forwardTimeField->move(fieldWidth + st::boxMediumSkip, 0);
}, forwardDateContainer->lifetime());
_forwardPostAuthorField = forwardFields->add(object_ptr<Ui::InputField>(
forwardFields,
st::defaultInputField,
tr::ayu_LocalMessageForwardPostAuthor(),
_data.forwardPostAuthor));
_forwardWrap->toggle(_data.isForwarded, anim::type::instant);
_isForwardedCheck->checkedChanges() | rpl::start_with_next([=](bool checked) {
_data.isForwarded = checked;
_forwardWrap->toggle(checked, anim::type::normal);
}, _isForwardedCheck->lifetime());
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupMediaSection() {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageMedia(),
st::boxLabel));
_hasMediaCheck = _content->add(object_ptr<Ui::Checkbox>(
_content,
tr::ayu_LocalMessageHasMedia(),
_data.hasMedia,
st::defaultCheckbox));
const auto mediaContent = _content->add(object_ptr<Ui::VerticalLayout>(_content));
_mediaWrap = _content->add(object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_content,
object_ptr<Ui::VerticalLayout>(_content)));
auto mediaFields = _mediaWrap->entity();
_mediaPathField = mediaFields->add(object_ptr<Ui::InputField>(
mediaFields,
st::defaultInputField,
tr::ayu_LocalMessageMediaPath(),
_data.mediaPath));
_mediaCaptionField = mediaFields->add(object_ptr<Ui::InputField>(
mediaFields,
st::defaultInputField,
tr::ayu_LocalMessageMediaCaption(),
_data.mediaCaption));
_mediaCaptionField->setMaxLength(kMaxCaptionLength);
_mediaWrap->toggle(_data.hasMedia, anim::type::instant);
_hasMediaCheck->checkedChanges() | rpl::start_with_next([=](bool checked) {
_data.hasMedia = checked;
_mediaWrap->toggle(checked, anim::type::normal);
}, _hasMediaCheck->lifetime());
// Advanced fields
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessageAdvanced(),
st::boxLabel));
_starsPaidField = _content->add(object_ptr<Ui::MaskedInputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageStarsPaid(),
QString::number(_data.starsPaid)));
_starsPaidField->setMask("999999999");
_effectIdField = _content->add(object_ptr<Ui::MaskedInputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageEffectId(),
QString::number(_data.effectId)));
_effectIdField->setMask("999999999999999999");
_groupedIdField = _content->add(object_ptr<Ui::MaskedInputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessageGroupedId(),
QString::number(_data.groupedId)));
_groupedIdField->setMask("999999999999999999");
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
}
void LocalMessageEditor::setupButtons() {
_box->addButton(tr::lng_box_ok(), [=] {
saveMessage();
_box->closeBox();
});
_box->addButton(tr::lng_cancel(), [=] {
_box->closeBox();
});
_box->setFocusCallback([=] {
_textField->setFocusFast();
});
}
void LocalMessageEditor::updateFromUser() {
const auto text = _fromField->getLastText().trimmed();
if (text.isEmpty()) {
_data.fromId = _history->peer->id;
if (const auto user = _history->peer->asUser()) {
_fromUserpic->showUser(user);
} else {
_fromUserpic->showPeer(_history->peer);
}
return;
}
// Try to find user by username or name
const auto &owner = _controller->session().data();
if (const auto user = owner.userByUsername(text)) {
_data.fromId = user->id;
_fromUserpic->showUser(user);
} else {
// Search by name in chat participants
if (const auto chat = _history->peer->asChat()) {
for (const auto &participant : chat->participants) {
if (const auto user = owner.user(participant.userId())) {
if (user->name().toLower().contains(text.toLower())) {
_data.fromId = user->id;
_fromUserpic->showUser(user);
return;
}
}
}
}
// If not found, keep current peer
_fromUserpic->showPeer(_history->peer);
}
}
void LocalMessageEditor::updateDateTime() {
const auto dateTime = QDateTime::fromSecsSinceEpoch(_data.date);
_dateField->setText(dateTime.date().toString("dd.MM.yyyy"));
_timeField->setText(dateTime.time().toString("hh:mm:ss"));
if (_data.wasEdited && _data.editDate > 0) {
const auto editDateTime = QDateTime::fromSecsSinceEpoch(_data.editDate);
_editDateField->setText(editDateTime.date().toString("dd.MM.yyyy"));
_editTimeField->setText(editDateTime.time().toString("hh:mm:ss"));
}
}
void LocalMessageEditor::updatePreview() {
// TODO: Add preview functionality
}
void LocalMessageEditor::saveMessage() {
// Collect data from fields
_data.text = _textField->getLastText();
_data.postAuthor = _postAuthorField->getLastText();
_data.serviceText = _serviceTextWrap->entity()->getLastText();
// Parse date and time
const auto dateText = _dateField->getLastText();
const auto timeText = _timeField->getLastText();
if (!dateText.isEmpty() && !timeText.isEmpty()) {
const auto dateTime = QDateTime::fromString(
dateText + " " + timeText,
"dd.MM.yyyy hh:mm:ss");
if (dateTime.isValid()) {
_data.date = dateTime.toSecsSinceEpoch();
}
}
// Parse edit date if applicable
if (_data.wasEdited) {
const auto editDateText = _editDateField->getLastText();
const auto editTimeText = _editTimeField->getLastText();
if (!editDateText.isEmpty() && !editTimeText.isEmpty()) {
const auto editDateTime = QDateTime::fromString(
editDateText + " " + editTimeText,
"dd.MM.yyyy hh:mm:ss");
if (editDateTime.isValid()) {
_data.editDate = editDateTime.toSecsSinceEpoch();
}
}
}
// Collect flags
_data.silent = _silentCheck->checked();
_data.pinned = _pinnedCheck->checked();
_data.noForwards = _noForwardsCheck->checked();
_data.invertMedia = _invertMediaCheck->checked();
// Collect stats
if (_data.hasViews) {
_data.views = _viewsField->getLastText().toInt();
}
if (_data.hasShares) {
_data.shares = _sharesField->getLastText().toInt();
}
// Collect advanced fields
_data.starsPaid = _starsPaidField->getLastText().toInt();
_data.effectId = _effectIdField->getLastText().toULongLong();
_data.groupedId = _groupedIdField->getLastText().toULongLong();
// Create the message
const auto localId = _controller->session().data().nextLocalMessageId();
const auto fields = buildFields();
HistoryItem* localItem = nullptr;
if (_data.isService) {
// Create service message
// TODO: Implement service message creation
} else {
// Create regular message
localItem = _history->addNewLocalMessage(
HistoryItemCommonFields{
.id = localId,
.flags = buildFlags(),
.from = _data.fromId,
.date = _data.date,
.postAuthor = _data.postAuthor,
.groupedId = _data.groupedId,
.effectId = _data.effectId,
},
TextWithEntities{ _data.text },
MTP_messageMediaEmpty());
}
if (localItem) {
// Add to local messages database
AyuMessages::addLocalMessage(localItem);
}
}
MessageFlags LocalMessageEditor::buildFlags() const {
MessageFlags flags = MessageFlag::HasFromId | MessageFlag::Local;
if (_data.silent) flags |= MessageFlag::Silent;
if (_data.pinned) flags |= MessageFlag::Pinned;
if (_data.noForwards) flags |= MessageFlag::NoForwards;
if (_data.invertMedia) flags |= MessageFlag::InvertMedia;
if (_data.hasViews) flags |= MessageFlag::HasViews;
if (!_data.postAuthor.isEmpty()) flags |= MessageFlag::HasPostAuthor;
if (_data.wasEdited) flags |= MessageFlag::HideEdited; // Will show as edited
if (_data.isForwarded) {
// TODO: Add forward flags
}
return flags;
}
HistoryItemCommonFields LocalMessageEditor::buildFields() const {
return HistoryItemCommonFields{
.id = 0, // Will be set by caller
.flags = buildFlags(),
.from = _data.fromId,
.date = _data.date,
.starsPaid = _data.starsPaid,
.viaBotId = UserId(_data.viaBotId),
.postAuthor = _data.postAuthor,
.groupedId = _data.groupedId,
.effectId = _data.effectId,
};
}
} // namespace
void LocalMessageEditorBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history,
const LocalMessageData &initialData) {
const auto editor = box->lifetime().make_state<LocalMessageEditor>(
box,
controller,
history,
initialData);
}
} // namespace AyuUi

View file

@ -0,0 +1,80 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "ui/layers/generic_box.h"
#include "history/history_item.h"
#include "data/data_msg_id.h"
namespace Ui {
class InputField;
class MaskedInputField;
class Checkbox;
class Dropdown;
class DateTimeEdit;
class UserpicButton;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
class History;
class HistoryItem;
class PeerData;
namespace AyuUi {
struct LocalMessageData {
QString text;
PeerId fromId = 0;
QString postAuthor;
TimeId date = 0;
bool silent = false;
bool pinned = false;
bool hasViews = false;
int views = 0;
bool hasShares = false;
int shares = 0;
bool noForwards = false;
bool invertMedia = false;
FullReplyTo replyTo;
uint64 groupedId = 0;
UserId viaBotId = 0;
EffectId effectId = 0;
bool isService = false;
QString serviceText;
// Media parameters
bool hasMedia = false;
QString mediaPath;
QString mediaCaption;
// Forward parameters
bool isForwarded = false;
PeerId forwardFromId = 0;
QString forwardFromName;
TimeId forwardDate = 0;
QString forwardPostAuthor;
bool forwardSavedFromPeer = false;
// Edit parameters
bool wasEdited = false;
TimeId editDate = 0;
// Business parameters
BusinessShortcutId shortcutId = 0;
int starsPaid = 0;
};
void LocalMessageEditorBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history,
const LocalMessageData &initialData = {});
} // namespace AyuUi

View file

@ -0,0 +1,439 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#include "ayu/ui/boxes/local_messages_manager.h"
#include "ayu/ui/boxes/local_message_editor.h"
#include "ayu/data/messages_storage.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang_auto.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/controls/userpic_button.h"
#include "ui/text/format_values.h"
#include "ui/boxes/confirm_box.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
#include <QtCore/QDateTime>
namespace AyuUi {
namespace {
class LocalMessageRow : public Ui::RpWidget {
public:
LocalMessageRow(
QWidget *parent,
not_null<HistoryItem*> item,
not_null<Window::SessionController*> controller);
[[nodiscard]] not_null<HistoryItem*> item() const {
return _item;
}
rpl::producer<> editRequests() const {
return _editRequests.events();
}
rpl::producer<> deleteRequests() const {
return _deleteRequests.events();
}
protected:
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
private:
void setupContent();
void showContextMenu(QPoint position);
const not_null<HistoryItem*> _item;
const not_null<Window::SessionController*> _controller;
Ui::UserpicButton* _userpic = nullptr;
QString _dateText;
QString _previewText;
rpl::event_stream<> _editRequests;
rpl::event_stream<> _deleteRequests;
};
LocalMessageRow::LocalMessageRow(
QWidget *parent,
not_null<HistoryItem*> item,
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _item(item)
, _controller(controller) {
setupContent();
}
void LocalMessageRow::setupContent() {
const auto height = st::defaultPeerListItem.height;
resize(width(), height);
_userpic = Ui::CreateChild<Ui::UserpicButton>(
this,
st::defaultUserpicButton);
_userpic->move(st::defaultPeerListItem.photoPosition);
if (const auto from = _item->from()) {
if (const auto user = _controller->session().data().user(from)) {
_userpic->showUser(user);
} else {
_userpic->showPeer(_item->history()->peer);
}
} else {
_userpic->showPeer(_item->history()->peer);
}
// Format date
const auto dateTime = QDateTime::fromSecsSinceEpoch(_item->date());
_dateText = dateTime.toString("dd.MM.yyyy hh:mm");
// Format preview text
const auto text = _item->originalText().text;
_previewText = text.left(100);
if (text.length() > 100) {
_previewText += "...";
}
if (_previewText.isEmpty()) {
_previewText = tr::ayu_LocalMessageEmptyText(tr::now);
}
}
void LocalMessageRow::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
p.fillRect(e->rect(), st::windowBg);
const auto left = st::defaultPeerListItem.namePosition.x();
const auto top = st::defaultPeerListItem.namePosition.y();
const auto available = width() - left - st::boxLayerScroll.width;
// Draw sender name
p.setPen(st::contactsNameFg);
p.setFont(st::defaultPeerListItem.nameStyle.font);
QString fromName;
if (const auto from = _item->from()) {
if (const auto user = _controller->session().data().user(from)) {
fromName = user->name();
} else {
fromName = tr::ayu_LocalMessageUnknownSender(tr::now);
}
} else {
fromName = _item->history()->peer->name();
}
const auto nameWidth = available / 2;
p.drawText(
QRect(left, top, nameWidth, st::defaultPeerListItem.nameStyle.font->height),
fromName,
QTextOption(Qt::AlignLeft | Qt::AlignVCenter));
// Draw date
p.setPen(st::contactsStatusFg);
p.setFont(st::defaultPeerListItem.statusFont);
const auto dateWidth = available / 4;
p.drawText(
QRect(left + nameWidth, top, dateWidth, st::defaultPeerListItem.statusFont->height),
_dateText,
QTextOption(Qt::AlignRight | Qt::AlignVCenter));
// Draw preview text
const auto previewTop = top + st::defaultPeerListItem.nameStyle.font->height + st::lineWidth;
p.drawText(
QRect(left, previewTop, available, st::defaultPeerListItem.statusFont->height),
_previewText,
QTextOption(Qt::AlignLeft | Qt::AlignVCenter));
// Draw flags indicators
const auto flagsTop = previewTop + st::defaultPeerListItem.statusFont->height + st::lineWidth;
QString flagsText;
if (_item->out()) flagsText += "📤 ";
if (_item->isSilent()) flagsText += "🔇 ";
if (_item->isPinned()) flagsText += "📌 ";
if (_item->hasViews()) flagsText += QString("👁 %1 ").arg(_item->viewsCount());
if (!flagsText.isEmpty()) {
p.setPen(st::contactsStatusFgOnline);
p.setFont(st::normalFont);
p.drawText(
QRect(left, flagsTop, available, st::normalFont->height),
flagsText.trimmed(),
QTextOption(Qt::AlignLeft | Qt::AlignVCenter));
}
}
void LocalMessageRow::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton) {
_editRequests.fire({});
}
RpWidget::mousePressEvent(e);
}
void LocalMessageRow::contextMenuEvent(QContextMenuEvent *e) {
showContextMenu(e->globalPos());
}
void LocalMessageRow::showContextMenu(QPoint position) {
auto menu = base::make_unique_q<Ui::PopupMenu>(
this,
st::popupMenuWithIcons);
menu->addAction(
tr::ayu_LocalMessageEdit(tr::now),
[=] { _editRequests.fire({}); },
&st::menuIconEdit);
menu->addAction(
tr::ayu_LocalMessageDelete(tr::now),
[=] { _deleteRequests.fire({}); },
&st::menuIconDelete);
menu->popup(position);
}
} // namespace
LocalMessagesManager::LocalMessagesManager(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history)
: _box(box)
, _controller(controller)
, _history(history)
, _scroll(_box->addRow(object_ptr<Ui::ScrollArea>(_box)))
, _content(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(_scroll))) {
setupContent();
}
void LocalMessagesManager::setupContent() {
_box->setTitle(tr::ayu_LocalMessagesManagerTitle());
_box->setWidth(st::boxWideWidth);
setupHeader();
setupMessagesList();
setupButtons();
_scroll->setMaxHeight(st::boxMaxListHeight);
refreshMessagesList();
}
void LocalMessagesManager::setupHeader() {
// Search field
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessagesSearch(),
st::boxLabel));
_searchField = _content->add(object_ptr<Ui::InputField>(
_content,
st::defaultInputField,
tr::ayu_LocalMessagesSearchPlaceholder(),
QString()));
_searchField->changes() | rpl::start_with_next([=] {
refreshMessagesList();
}, _searchField->lifetime());
// Stats
const auto statsLabel = _content->add(object_ptr<Ui::FlatLabel>(
_content,
QString(),
st::boxDividerLabel));
const auto updateStats = [=] {
const auto total = _localMessages.size();
statsLabel->setText(tr::ayu_LocalMessagesStats(tr::now, lt_count, total));
};
// Add separator
_content->add(object_ptr<Ui::BoxContentDivider>(_content));
// Create new message button
const auto createButton = _content->add(object_ptr<Ui::RoundButton>(
_content,
tr::ayu_LocalMessageCreateNew(),
st::defaultActiveButton));
createButton->setClickedCallback([=] {
createNewMessage();
});
_content->add(object_ptr<Ui::FixedHeightWidget>(_content, st::boxMediumSkip));
// Update stats initially
rpl::combine(
rpl::single(rpl::empty_value()),
_searchField->changes()
) | rpl::start_with_next([=] {
updateStats();
}, _content->lifetime());
}
void LocalMessagesManager::setupMessagesList() {
// Messages will be added dynamically in refreshMessagesList()
}
void LocalMessagesManager::setupButtons() {
_box->addButton(tr::lng_close(), [=] {
_box->closeBox();
});
}
void LocalMessagesManager::refreshMessagesList() {
// Clear existing rows
while (_content->count() > 5) { // Keep header elements
delete _content->widgetAt(_content->count() - 1);
}
_localMessages.clear();
// Get local messages for this peer
if (!AyuMessages::hasLocalMessages(_history->peer, 0)) {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
tr::ayu_LocalMessagesEmpty(),
st::boxDividerLabel));
return;
}
const auto messages = AyuMessages::getLocalMessages(_history->peer, 0, 0, 0, 1000);
const auto searchText = _searchField->getLastText().toLower();
// Convert to HistoryItem and filter
for (const auto &messageData : messages) {
// Find the actual HistoryItem by ID
const auto item = _history->owner().message(_history->peer->id, messageData.messageId);
if (!item || !item->isLocal()) {
continue;
}
// Apply search filter
if (!searchText.isEmpty()) {
const auto text = item->originalText().text.toLower();
const auto fromName = item->from()
? _controller->session().data().user(item->from())->name().toLower()
: _history->peer->name().toLower();
if (!text.contains(searchText) && !fromName.contains(searchText)) {
continue;
}
}
_localMessages.push_back(item);
}
// Sort by date (newest first)
std::sort(_localMessages.begin(), _localMessages.end(), [](
not_null<HistoryItem*> a,
not_null<HistoryItem*> b) {
return a->date() > b->date();
});
// Add message rows
for (const auto &item : _localMessages) {
addMessageRow(item);
}
if (_localMessages.empty()) {
_content->add(object_ptr<Ui::FlatLabel>(
_content,
searchText.isEmpty()
? tr::ayu_LocalMessagesEmpty()
: tr::ayu_LocalMessagesNoResults(),
st::boxDividerLabel));
}
}
void LocalMessagesManager::addMessageRow(not_null<HistoryItem*> item) {
const auto row = _content->add(object_ptr<LocalMessageRow>(
_content,
item,
_controller));
row->editRequests() | rpl::start_with_next([=] {
editMessage(item);
}, row->lifetime());
row->deleteRequests() | rpl::start_with_next([=] {
deleteMessage(item);
}, row->lifetime());
}
void LocalMessagesManager::editMessage(not_null<HistoryItem*> item) {
// Convert HistoryItem to LocalMessageData
LocalMessageData data;
data.text = item->originalText().text;
data.fromId = item->from();
data.postAuthor = item->postAuthor();
data.date = item->date();
data.silent = item->isSilent();
data.pinned = item->isPinned();
data.hasViews = item->hasViews();
data.views = item->viewsCount();
data.noForwards = item->forbidsForward();
// ... populate other fields as needed
_controller->show(Box(
LocalMessageEditorBox,
_controller,
_history,
data));
}
void LocalMessagesManager::deleteMessage(not_null<HistoryItem*> item) {
const auto text = tr::ayu_LocalMessageDeleteConfirm(
tr::now,
lt_message,
item->originalText().text.left(50));
_controller->show(Ui::MakeConfirmBox({
.text = text,
.confirmed = [=] {
// Remove from history
_history->destroyMessage(item);
// Refresh the list
refreshMessagesList();
},
.confirmText = tr::lng_box_delete(),
}));
}
void LocalMessagesManager::createNewMessage() {
_controller->show(Box(
LocalMessageEditorBox,
_controller,
_history,
LocalMessageData{}));
}
void LocalMessagesManagerBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history) {
const auto manager = box->lifetime().make_state<LocalMessagesManager>(
box,
controller,
history);
}
} // namespace AyuUi

View file

@ -0,0 +1,65 @@
// This is the source code of AyuGram for Desktop.
//
// We do not and cannot prevent the use of our code,
// but be respectful and credit the original author.
//
// Copyright @Radolyn, 2025
#pragma once
#include "ui/layers/generic_box.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/vertical_layout.h"
namespace Ui {
class RoundButton;
class InputField;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
class History;
class HistoryItem;
class PeerData;
namespace AyuUi {
struct LocalMessageData;
class LocalMessagesManager {
public:
LocalMessagesManager(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history);
private:
void setupContent();
void setupHeader();
void setupMessagesList();
void setupButtons();
void refreshMessagesList();
void addMessageRow(not_null<HistoryItem*> item);
void editMessage(not_null<HistoryItem*> item);
void deleteMessage(not_null<HistoryItem*> item);
void createNewMessage();
const not_null<Ui::GenericBox*> _box;
const not_null<Window::SessionController*> _controller;
const not_null<History*> _history;
not_null<Ui::ScrollArea*> _scroll;
not_null<Ui::VerticalLayout*> _content;
Ui::InputField* _searchField = nullptr;
std::vector<not_null<HistoryItem*>> _localMessages;
};
void LocalMessagesManagerBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<History*> history);
} // namespace AyuUi

View file

@ -24,6 +24,8 @@
#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/boxes/generic_box.h" #include "ui/boxes/generic_box.h"
#include "ayu/ui/boxes/local_message_editor.h"
#include "ayu/ui/boxes/local_messages_manager.h"
#include "window/window_peer_menu.h" #include "window/window_peer_menu.h"
#include "ayu/ui/message_history/history_section.h" #include "ayu/ui/message_history/history_section.h"
@ -565,87 +567,76 @@ void AddLocalMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
menu->addAction( menu->addAction(
tr::ayu_AddLocalMessage(tr::now), tr::ayu_AddLocalMessage(tr::now),
[=]() { [=]() {
const auto showBox = [=](const QString &text, PeerId fromId) { controller->show(Box(
if (text.isEmpty()) { LocalMessageEditorBox,
return; controller,
} history,
LocalMessageData{}));
// Создаем локальное сообщение
const auto localId = history->session().data().nextLocalMessageId();
const auto localItem = history->addNewLocalMessage({
.id = localId,
.flags = MessageFlag::HasFromId,
.from = fromId,
.date = base::unixtime::now(),
}, TextWithEntities{ text }, MTP_messageMediaEmpty());
// Добавляем в базу данных локальных сообщений
AyuMessages::addLocalMessage(localItem);
};
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::ayu_AddLocalMessageTitle());
// Поле для ввода текста сообщения
const auto textField = box->addRow(object_ptr<Ui::InputField>(
box,
st::defaultInputField,
tr::ayu_AddLocalMessagePlaceholder(),
QString()));
textField->setMaxLength(4096);
// Поле для выбора отправителя
const auto fromField = box->addRow(object_ptr<Ui::InputField>(
box,
st::defaultInputField,
tr::ayu_AddLocalMessageFromPlaceholder(),
QString()));
box->setFocusCallback([=] {
textField->setFocusFast();
});
box->addButton(tr::lng_box_ok(), [=] {
const auto text = textField->getLastText().trimmed();
const auto fromText = fromField->getLastText().trimmed();
PeerId fromId = history->peer->id;
if (!fromText.isEmpty()) {
// Пытаемся найти пользователя по имени или username
const auto session = &history->session();
const auto &owner = session->data();
// Сначала пытаемся найти по username
if (const auto user = owner.userByUsername(fromText)) {
fromId = user->id;
} else {
// Затем ищем по имени среди участников чата
if (const auto chat = history->peer->asChat()) {
for (const auto &participant : chat->participants) {
if (const auto user = owner.user(participant.userId())) {
if (user->name().toLower().contains(fromText.toLower())) {
fromId = user->id;
break;
}
}
}
} else if (const auto channel = history->peer->asChannel()) {
// Для каналов используем текущего пользователя, если не найден
// В реальном приложении можно было бы реализовать поиск по участникам
}
}
}
showBox(text, fromId);
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}));
}, },
&st::menuIconEdit); &st::menuIconEdit);
} }
void AddLocalMessagesManagerAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
if (!item) {
return;
}
const auto& settings = AyuSettings::getInstance();
if (!needToShowItem(settings.showAddLocalMessageInContextMenu)) {
return;
}
const auto history = item->history();
const auto controller = history->session().tryResolveWindow();
if (!controller) {
return;
}
// Only show if there are local messages in this chat
if (!AyuMessages::hasLocalMessages(history->peer, 0)) {
return;
}
menu->addAction(
tr::ayu_LocalMessagesManager(tr::now),
[=]() {
controller->show(Box(
LocalMessagesManagerBox,
controller,
history));
},
&st::menuIconInfo);
}
void AddLocalMessagesManagerToChatMenu(PeerData *peerData,
Data::Thread *thread,
not_null<Window::SessionController*> sessionController,
const Window::PeerMenuCallback &addCallback) {
if (!peerData) {
return;
}
const auto& settings = AyuSettings::getInstance();
if (!needToShowItem(settings.showAddLocalMessageInContextMenu)) {
return;
}
const auto history = sessionController->session().data().history(peerData);
// Only show if there are local messages in this chat
if (!AyuMessages::hasLocalMessages(peerData, 0)) {
return;
}
addCallback(
tr::ayu_LocalMessagesManager(tr::now),
[=]() {
sessionController->show(Box(
LocalMessagesManagerBox,
sessionController,
history));
},
&st::menuIconInfo);
}
} // namespace AyuUi } // namespace AyuUi

View file

@ -36,5 +36,11 @@ void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item); void AddReadUntilAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
void AddBurnAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item); void AddBurnAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
void AddLocalMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item); void AddLocalMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
void AddLocalMessagesManagerAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
void AddLocalMessagesManagerToChatMenu(PeerData *peerData,
Data::Thread *thread,
not_null<Window::SessionController*> sessionController,
const Window::PeerMenuCallback &addCallback);
} }

View file

@ -1032,6 +1032,7 @@ void AddMessageActions(
AyuUi::AddUserMessagesAction(menu, request.item); AyuUi::AddUserMessagesAction(menu, request.item);
AyuUi::AddMessageDetailsAction(menu, request.item); AyuUi::AddMessageDetailsAction(menu, request.item);
AyuUi::AddLocalMessageAction(menu, request.item); AyuUi::AddLocalMessageAction(menu, request.item);
AyuUi::AddLocalMessagesManagerAction(menu, request.item);
} }
AddPostLinkAction(menu, request); AddPostLinkAction(menu, request);

View file

@ -1447,6 +1447,7 @@ void Filler::fillHistoryActions() {
addTranslate(); addTranslate();
addReport(); addReport();
AyuUi::AddDeletedMessagesActions(_peer, _thread, _controller, _addAction); AyuUi::AddDeletedMessagesActions(_peer, _thread, _controller, _addAction);
AyuUi::AddLocalMessagesManagerToChatMenu(_peer, _thread, _controller, _addAction);
addClearHistory(); addClearHistory();
addDeleteChat(); addDeleteChat();
addLeaveChat(); addLeaveChat();