diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index fbb2eb025c..fe7439b755 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6909,3 +6909,64 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "ayu_AddLocalMessageTitle" = "Add Local Message"; "ayu_AddLocalMessagePlaceholder" = "Message text..."; "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}\"?"; diff --git a/Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.cpp b/Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.cpp new file mode 100644 index 0000000000..9c0c33db6b --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.cpp @@ -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 + +namespace AyuUi { + +namespace { + +constexpr auto kMaxMessageLength = 4096; +constexpr auto kMaxCaptionLength = 1024; +constexpr auto kMaxPostAuthorLength = 255; + +class LocalMessageEditor { +public: + LocalMessageEditor( + not_null box, + not_null controller, + not_null 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 _box; + const not_null _controller; + const not_null _history; + LocalMessageData _data; + + // UI Elements + not_null _scroll; + not_null _content; + + // Basic fields + Ui::InputField* _textField = nullptr; + Ui::InputField* _fromField = nullptr; + Ui::UserpicButton* _fromUserpic = nullptr; + + // Message type + Ui::Checkbox* _isServiceMessage = nullptr; + Ui::SlideWrap* _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* _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* _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 box, + not_null controller, + not_null history, + const LocalMessageData &initialData) +: _box(box) +, _controller(controller) +, _history(history) +, _data(initialData) +, _scroll(_box->addRow(object_ptr(_box))) +, _content(_scroll->setOwnedWidget(object_ptr(_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( + _content, + tr::ayu_LocalMessageText(), + st::boxLabel)); + + _textField = _content->add(object_ptr( + _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(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupMessageTypeSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageType(), + st::boxLabel)); + + _isServiceMessage = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageIsService(), + _data.isService, + st::defaultCheckbox)); + + _serviceTextWrap = _content->add(object_ptr>( + _content, + object_ptr( + _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(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupAuthorSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageAuthor(), + st::boxLabel)); + + // From user field with userpic + const auto fromContainer = _content->add(object_ptr(_content)); + fromContainer->resize(fromContainer->width(), st::defaultInputField.height); + + _fromUserpic = Ui::CreateChild( + fromContainer, + st::defaultUserpicButton); + _fromUserpic->move(0, 0); + + _fromField = Ui::CreateChild( + 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( + _content, + st::defaultInputField, + tr::ayu_LocalMessagePostAuthor(), + _data.postAuthor)); + _postAuthorField->setMaxLength(kMaxPostAuthorLength); + + // Via bot field + _viaBotField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessageViaBot(), + QString())); + + updateFromUser(); + + _fromField->changes() | rpl::start_with_next([=] { + updateFromUser(); + }, _fromField->lifetime()); + + _content->add(object_ptr(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupTimingSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageTiming(), + st::boxLabel)); + + // Date and time fields + const auto dateTimeContainer = _content->add(object_ptr(_content)); + dateTimeContainer->resize(dateTimeContainer->width(), st::defaultInputField.height); + + _dateField = Ui::CreateChild( + dateTimeContainer, + st::defaultInputField, + tr::ayu_LocalMessageDate(), + QString()); + _dateField->setMask("99.99.9999"); + _dateField->move(0, 0); + + _timeField = Ui::CreateChild( + 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( + _content, + tr::ayu_LocalMessageWasEdited(), + _data.wasEdited, + st::defaultCheckbox)); + + const auto editDateContainer = _content->add(object_ptr(_content)); + editDateContainer->resize(editDateContainer->width(), st::defaultInputField.height); + + _editDateField = Ui::CreateChild( + editDateContainer, + st::defaultInputField, + tr::ayu_LocalMessageEditDate(), + QString()); + _editDateField->setMask("99.99.9999"); + _editDateField->move(0, 0); + + _editTimeField = Ui::CreateChild( + 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(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupFlagsSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageFlags(), + st::boxLabel)); + + _silentCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageSilent(), + _data.silent, + st::defaultCheckbox)); + + _pinnedCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessagePinned(), + _data.pinned, + st::defaultCheckbox)); + + _noForwardsCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageNoForwards(), + _data.noForwards, + st::defaultCheckbox)); + + _invertMediaCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageInvertMedia(), + _data.invertMedia, + st::defaultCheckbox)); + + _content->add(object_ptr(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupStatsSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageStats(), + st::boxLabel)); + + // Views + _hasViewsCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageHasViews(), + _data.hasViews, + st::defaultCheckbox)); + + _viewsField = _content->add(object_ptr( + _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( + _content, + tr::ayu_LocalMessageHasShares(), + _data.hasShares, + st::defaultCheckbox)); + + _sharesField = _content->add(object_ptr( + _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(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupReplySection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageReply(), + st::boxLabel)); + + _replyToField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessageReplyTo(), + QString())); + + _replyQuoteField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessageReplyQuote(), + QString())); + + _content->add(object_ptr(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupForwardSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageForward(), + st::boxLabel)); + + _isForwardedCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageIsForwarded(), + _data.isForwarded, + st::defaultCheckbox)); + + const auto forwardContent = _content->add(object_ptr(_content)); + _forwardWrap = _content->add(object_ptr>( + _content, + object_ptr(_content))); + + auto forwardFields = _forwardWrap->entity(); + + _forwardFromField = forwardFields->add(object_ptr( + forwardFields, + st::defaultInputField, + tr::ayu_LocalMessageForwardFrom(), + QString())); + + _forwardFromNameField = forwardFields->add(object_ptr( + forwardFields, + st::defaultInputField, + tr::ayu_LocalMessageForwardFromName(), + _data.forwardFromName)); + + // Forward date and time + const auto forwardDateContainer = forwardFields->add(object_ptr(forwardFields)); + forwardDateContainer->resize(forwardDateContainer->width(), st::defaultInputField.height); + + _forwardDateField = Ui::CreateChild( + forwardDateContainer, + st::defaultInputField, + tr::ayu_LocalMessageForwardDate(), + QString()); + _forwardDateField->setMask("99.99.9999"); + _forwardDateField->move(0, 0); + + _forwardTimeField = Ui::CreateChild( + 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( + 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(_content, st::boxMediumSkip)); +} + +void LocalMessageEditor::setupMediaSection() { + _content->add(object_ptr( + _content, + tr::ayu_LocalMessageMedia(), + st::boxLabel)); + + _hasMediaCheck = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageHasMedia(), + _data.hasMedia, + st::defaultCheckbox)); + + const auto mediaContent = _content->add(object_ptr(_content)); + _mediaWrap = _content->add(object_ptr>( + _content, + object_ptr(_content))); + + auto mediaFields = _mediaWrap->entity(); + + _mediaPathField = mediaFields->add(object_ptr( + mediaFields, + st::defaultInputField, + tr::ayu_LocalMessageMediaPath(), + _data.mediaPath)); + + _mediaCaptionField = mediaFields->add(object_ptr( + 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( + _content, + tr::ayu_LocalMessageAdvanced(), + st::boxLabel)); + + _starsPaidField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessageStarsPaid(), + QString::number(_data.starsPaid))); + _starsPaidField->setMask("999999999"); + + _effectIdField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessageEffectId(), + QString::number(_data.effectId))); + _effectIdField->setMask("999999999999999999"); + + _groupedIdField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessageGroupedId(), + QString::number(_data.groupedId))); + _groupedIdField->setMask("999999999999999999"); + + _content->add(object_ptr(_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 box, + not_null controller, + not_null history, + const LocalMessageData &initialData) { + const auto editor = box->lifetime().make_state( + box, + controller, + history, + initialData); +} + +} // namespace AyuUi \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.h b/Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.h new file mode 100644 index 0000000000..fa20401984 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.h @@ -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 box, + not_null controller, + not_null history, + const LocalMessageData &initialData = {}); + +} // namespace AyuUi \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.cpp b/Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.cpp new file mode 100644 index 0000000000..a795b6f63d --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.cpp @@ -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 + +namespace AyuUi { + +namespace { + +class LocalMessageRow : public Ui::RpWidget { +public: + LocalMessageRow( + QWidget *parent, + not_null item, + not_null controller); + + [[nodiscard]] not_null 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 _item; + const not_null _controller; + + Ui::UserpicButton* _userpic = nullptr; + QString _dateText; + QString _previewText; + + rpl::event_stream<> _editRequests; + rpl::event_stream<> _deleteRequests; +}; + +LocalMessageRow::LocalMessageRow( + QWidget *parent, + not_null item, + not_null controller) +: RpWidget(parent) +, _item(item) +, _controller(controller) { + setupContent(); +} + +void LocalMessageRow::setupContent() { + const auto height = st::defaultPeerListItem.height; + resize(width(), height); + + _userpic = Ui::CreateChild( + 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( + 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 box, + not_null controller, + not_null history) +: _box(box) +, _controller(controller) +, _history(history) +, _scroll(_box->addRow(object_ptr(_box))) +, _content(_scroll->setOwnedWidget(object_ptr(_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( + _content, + tr::ayu_LocalMessagesSearch(), + st::boxLabel)); + + _searchField = _content->add(object_ptr( + _content, + st::defaultInputField, + tr::ayu_LocalMessagesSearchPlaceholder(), + QString())); + + _searchField->changes() | rpl::start_with_next([=] { + refreshMessagesList(); + }, _searchField->lifetime()); + + // Stats + const auto statsLabel = _content->add(object_ptr( + _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(_content)); + + // Create new message button + const auto createButton = _content->add(object_ptr( + _content, + tr::ayu_LocalMessageCreateNew(), + st::defaultActiveButton)); + + createButton->setClickedCallback([=] { + createNewMessage(); + }); + + _content->add(object_ptr(_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( + _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 a, + not_null b) { + return a->date() > b->date(); + }); + + // Add message rows + for (const auto &item : _localMessages) { + addMessageRow(item); + } + + if (_localMessages.empty()) { + _content->add(object_ptr( + _content, + searchText.isEmpty() + ? tr::ayu_LocalMessagesEmpty() + : tr::ayu_LocalMessagesNoResults(), + st::boxDividerLabel)); + } +} + +void LocalMessagesManager::addMessageRow(not_null item) { + const auto row = _content->add(object_ptr( + _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 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 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 box, + not_null controller, + not_null history) { + const auto manager = box->lifetime().make_state( + box, + controller, + history); +} + +} // namespace AyuUi \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.h b/Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.h new file mode 100644 index 0000000000..81be271c72 --- /dev/null +++ b/Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.h @@ -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 box, + not_null controller, + not_null history); + +private: + void setupContent(); + void setupHeader(); + void setupMessagesList(); + void setupButtons(); + + void refreshMessagesList(); + void addMessageRow(not_null item); + void editMessage(not_null item); + void deleteMessage(not_null item); + void createNewMessage(); + + const not_null _box; + const not_null _controller; + const not_null _history; + + not_null _scroll; + not_null _content; + Ui::InputField* _searchField = nullptr; + + std::vector> _localMessages; +}; + +void LocalMessagesManagerBox( + not_null box, + not_null controller, + not_null history); + +} // namespace AyuUi \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp index f01177a326..acb9192dbf 100644 --- a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp +++ b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp @@ -24,6 +24,8 @@ #include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/fields/input_field.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 "ayu/ui/message_history/history_section.h" @@ -565,87 +567,76 @@ void AddLocalMessageAction(not_null menu, HistoryItem *item) { menu->addAction( tr::ayu_AddLocalMessage(tr::now), [=]() { - const auto showBox = [=](const QString &text, PeerId fromId) { - if (text.isEmpty()) { - return; - } - - // Создаем локальное сообщение - 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 box) { - box->setTitle(tr::ayu_AddLocalMessageTitle()); - - // Поле для ввода текста сообщения - const auto textField = box->addRow(object_ptr( - box, - st::defaultInputField, - tr::ayu_AddLocalMessagePlaceholder(), - QString())); - textField->setMaxLength(4096); - - // Поле для выбора отправителя - const auto fromField = box->addRow(object_ptr( - 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(); - }); - })); + controller->show(Box( + LocalMessageEditorBox, + controller, + history, + LocalMessageData{})); }, &st::menuIconEdit); } +void AddLocalMessagesManagerAction(not_null 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 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 diff --git a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h index 16750858b7..670f26eb30 100644 --- a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h +++ b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.h @@ -36,5 +36,11 @@ void AddMessageDetailsAction(not_null menu, HistoryItem *item); void AddReadUntilAction(not_null menu, HistoryItem *item); void AddBurnAction(not_null menu, HistoryItem *item); void AddLocalMessageAction(not_null menu, HistoryItem *item); +void AddLocalMessagesManagerAction(not_null menu, HistoryItem *item); + +void AddLocalMessagesManagerToChatMenu(PeerData *peerData, + Data::Thread *thread, + not_null sessionController, + const Window::PeerMenuCallback &addCallback); } diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index d541d553a0..60b2afe0d6 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1032,6 +1032,7 @@ void AddMessageActions( AyuUi::AddUserMessagesAction(menu, request.item); AyuUi::AddMessageDetailsAction(menu, request.item); AyuUi::AddLocalMessageAction(menu, request.item); + AyuUi::AddLocalMessagesManagerAction(menu, request.item); } AddPostLinkAction(menu, request); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index da58c91829..2a47688058 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -1447,6 +1447,7 @@ void Filler::fillHistoryActions() { addTranslate(); addReport(); AyuUi::AddDeletedMessagesActions(_peer, _thread, _controller, _addAction); + AyuUi::AddLocalMessagesManagerToChatMenu(_peer, _thread, _controller, _addAction); addClearHistory(); addDeleteChat(); addLeaveChat();