// 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