feat: capture and render media particles

This commit is contained in:
talksik
2026-02-14 10:31:53 -08:00
parent 8c1c5aebf7
commit deb8f19c4c
16 changed files with 1149 additions and 33 deletions
+102 -29
View File
@@ -31,12 +31,21 @@
#include <QTimer>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "media/mediahandler.h"
#include "dialogs/mediapreviewdialog.h"
#include "mediaparticlegenerator.h"
#include "widgets/textparticlewidget.h"
#include "widgets/mediaparticlewidget.h"
#include <QStackedWidget>
#include <QUrl>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), m_trayIcon(nullptr), m_authManager(new AuthManager(this)), m_settingsDialog(nullptr),
m_authDialog(nullptr), m_store(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr),
m_selectedParticleRow(-1), m_isDragging(false), m_textInputDialog(nullptr), m_createStreamDialog(nullptr),
m_networkDetailWidget(nullptr)
: QMainWindow(parent), m_trayIcon(nullptr), m_networkDetailWidget(nullptr),
m_authManager(new AuthManager(this)), m_authDialog(nullptr), m_settingsDialog(nullptr),
m_textInputDialog(nullptr), m_createStreamDialog(nullptr),
m_store(nullptr), m_networkStreamModel(nullptr), m_particleListModel(nullptr),
m_selectedParticleRow(-1), m_isDragging(false), m_media{new MediaHandler{this}},
m_spaceHeld(false)
{
m_authDialog = new AuthDialog(this);
m_textInputDialog = new TextInputDialog(this);
@@ -142,6 +151,42 @@ MainWindow::MainWindow(QWidget *parent)
m_authManager->tryRestoreSession();
connect(m_media, &MediaHandler::errorOccurred, this, [this](const QString &message) {
m_statusBar->showMessage(message);
});
m_mediaPreviewDialog = new MediaPreviewDialog(this);
m_mediaParticleGenerator = new MediaParticleGenerator(m_store, this);
connect(m_media, &MediaHandler::recordingComplete, this,
[this](const QUrl &url, qint64 durationMs, const QString &mimeType) {
m_media->stop();
m_recordedFileUrl = url;
m_recordedDurationMs = durationMs;
m_recordedMimeType = mimeType;
m_mediaPreviewDialog->showPreview(url);
});
connect(m_mediaPreviewDialog, &MediaPreviewDialog::spaceReleased, this, [this]() {
m_spaceHeld = false;
m_media->recordStop();
});
connect(m_mediaPreviewDialog, &QDialog::accepted, this, [this]() {
m_mediaPreviewDialog->reset();
if (m_selectedStreamId.isEmpty() || m_selectedNetworkId.isEmpty()) return;
m_mediaParticleGenerator->generate(
m_recordedFileUrl, m_recordedMimeType,
m_selectedNetworkId, m_recordedDurationMs, m_selectedStreamId);
});
connect(m_mediaPreviewDialog, &QDialog::rejected, this, [this]() {
m_mediaPreviewDialog->reset();
});
connect(m_mediaParticleGenerator, &MediaParticleGenerator::errorOccurred, this,
[this](const QString &msg) { m_statusBar->showMessage(msg); });
setupTrayIcon();
}
@@ -207,39 +252,28 @@ void MainWindow::buildUi()
contentLayout->setSpacing(2);
contentLayout->setContentsMargins(0, 0, 0, 2);
// Content container with spacers and labels
auto *contentContainer = new QWidget();
contentContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto *contentContainerLayout = new QVBoxLayout(contentContainer);
contentContainerLayout->setSpacing(2);
contentContainerLayout->setContentsMargins(0, 0, 0, 0);
// Stacked widget for particle type-specific content
m_contentStack = new QStackedWidget();
m_contentStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
contentContainerLayout->addStretch();
m_textParticleWidget = new TextParticleWidget();
m_mediaParticleWidget = new MediaParticleWidget();
m_particleContentLabel = new QLabel();
QFont contentFont;
contentFont.setPointSize(36);
m_particleContentLabel->setFont(contentFont);
m_particleContentLabel->setMaximumSize(500, 300);
m_particleContentLabel->setAlignment(Qt::AlignCenter);
m_particleContentLabel->setWordWrap(true);
m_particleContentLabel->setScaledContents(true);
contentContainerLayout->addWidget(m_particleContentLabel);
m_contentStack->addWidget(m_textParticleWidget);
m_contentStack->addWidget(m_mediaParticleWidget);
contentContainerLayout->addStretch();
contentLayout->addWidget(m_contentStack);
m_creatorLabel = new QLabel();
QFont creatorFont;
creatorFont.setPointSize(15);
m_creatorLabel->setFont(creatorFont);
m_creatorLabel->setAlignment(Qt::AlignCenter);
contentContainerLayout->addWidget(m_creatorLabel);
contentLayout->addWidget(m_creatorLabel);
m_ackLabel = new QLabel();
m_ackLabel->setAlignment(Qt::AlignCenter);
contentContainerLayout->addWidget(m_ackLabel);
contentLayout->addWidget(contentContainer);
contentLayout->addWidget(m_ackLabel);
// Content toolbar (empty, matches widget_4 from .ui)
auto *contentToolbar = new QWidget();
@@ -366,6 +400,10 @@ void MainWindow::setupTrayIcon()
MainWindow::~MainWindow()
{
if (m_spaceHeld)
{
m_media->stop();
}
}
void MainWindow::on_exitButton_clicked()
@@ -639,7 +677,8 @@ void MainWindow::onParticleSelectionChanged(const QItemSelection &selected, cons
if (selected.indexes().isEmpty())
{
m_particleContentLabel->setText("");
m_textParticleWidget->clear();
m_mediaParticleWidget->clear();
m_creatorLabel->setText("");
m_ackLabel->setText("");
m_selectedParticleId.clear();
@@ -658,11 +697,15 @@ void MainWindow::onParticleSelectionChanged(const QItemSelection &selected, cons
if (particle->type == "text")
{
m_particleContentLabel->setText(particle->data.value("content").toString());
m_mediaParticleWidget->clear();
m_textParticleWidget->setParticle(particle);
m_contentStack->setCurrentWidget(m_textParticleWidget);
}
else
else if (particle->type == "media")
{
m_particleContentLabel->setText("");
m_textParticleWidget->clear();
m_mediaParticleWidget->setParticle(particle);
m_contentStack->setCurrentWidget(m_mediaParticleWidget);
}
// Show ack info
@@ -808,6 +851,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
if (event->type() == QEvent::KeyPress)
{
auto *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space && !keyEvent->isAutoRepeat() && !m_spaceHeld)
{
if (!m_selectedNetworkId.isEmpty() && !m_selectedStreamId.isEmpty())
{
m_spaceHeld = true;
m_mediaPreviewDialog->showRecording();
m_media->startRecording(m_mediaPreviewDialog->videoSink());
return true;
}
}
if (keyEvent->key() == Qt::Key_T && keyEvent->modifiers() == Qt::NoModifier && !m_selectedStreamId.isEmpty())
{
showTextInputDialog();
@@ -819,6 +872,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event)
return true;
}
}
if (event->type() == QEvent::KeyRelease)
{
auto *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space && !keyEvent->isAutoRepeat() && m_spaceHeld)
{
m_spaceHeld = false;
m_media->recordStop();
return true;
}
}
return QMainWindow::eventFilter(obj, event);
}
@@ -840,6 +903,16 @@ void MainWindow::onTextMessageSubmitted(const QString &text)
});
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
QMainWindow::keyPressEvent(event);
}
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
QMainWindow::keyReleaseEvent(event);
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)