mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-09-06 20:13:12 +02:00
Add local message editor and manager for custom messages
Co-authored-by: g6582566 <g6582566@morris.umn.edu>
This commit is contained in:
parent
683aef44f9
commit
00840d76e1
9 changed files with 1526 additions and 79 deletions
|
@ -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}\"?";
|
||||||
|
|
803
Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.cpp
Normal file
803
Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.cpp
Normal 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
|
80
Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.h
Normal file
80
Telegram/SourceFiles/ayu/ui/boxes/local_message_editor.h
Normal 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
|
439
Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.cpp
Normal file
439
Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.cpp
Normal 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
|
65
Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.h
Normal file
65
Telegram/SourceFiles/ayu/ui/boxes/local_messages_manager.h
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue