setup electron app with boilerplate
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Created by arjun-flowylabs on 1/13/2026.
|
||||
//
|
||||
|
||||
#include "VerticalLabel.h"
|
||||
#include <QPainter>
|
||||
|
||||
VerticalLabel::VerticalLabel(const QString &text, QWidget *parent) : QLabel(text, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QSize VerticalLabel::sizeHint() const
|
||||
{
|
||||
QSize s = QLabel::sizeHint();
|
||||
return QSize(s.height(), s.width());
|
||||
}
|
||||
|
||||
QSize VerticalLabel::minimumSizeHint() const
|
||||
{
|
||||
QSize s = QLabel::minimumSizeHint();
|
||||
return QSize(s.height(), s.width());
|
||||
}
|
||||
|
||||
void VerticalLabel::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setPen(palette().color(QPalette::WindowText));
|
||||
painter.setFont(font());
|
||||
painter.translate(0, height());
|
||||
painter.rotate(-90);
|
||||
painter.drawText(QRect(0, 0, height(), width()), Qt::AlignCenter, text());
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by arjun-flowylabs on 1/13/2026.
|
||||
//
|
||||
|
||||
#ifndef LLINK_VERTICALLABEL_H
|
||||
#define LLINK_VERTICALLABEL_H
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class VerticalLabel : public QLabel
|
||||
{
|
||||
public:
|
||||
explicit VerticalLabel(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
};
|
||||
|
||||
#endif // LLINK_VERTICALLABEL_H
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "mediaparticlewidget.h"
|
||||
#include "../models.h"
|
||||
#include "../networkmanager.h"
|
||||
#include <QAudioOutput>
|
||||
#include <QLabel>
|
||||
#include <QMediaPlayer>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVideoWidget>
|
||||
|
||||
MediaParticleWidget::MediaParticleWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_nam = new QNetworkAccessManager(this);
|
||||
// To prevent our network request from fetching bytes in memory
|
||||
m_nam->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(2);
|
||||
|
||||
m_videoWidget = new QVideoWidget();
|
||||
m_videoWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
layout->addWidget(m_videoWidget);
|
||||
|
||||
m_durationLabel = new QLabel();
|
||||
m_durationLabel->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(m_durationLabel);
|
||||
|
||||
m_player = new QMediaPlayer(this);
|
||||
m_audioOutput = new QAudioOutput(this);
|
||||
m_player->setAudioOutput(m_audioOutput);
|
||||
m_player->setVideoOutput(m_videoWidget);
|
||||
m_player->setLoops(QMediaPlayer::Infinite);
|
||||
|
||||
connect(m_player, &QMediaPlayer::durationChanged, this, [this](qint64 duration) {
|
||||
if (duration > 0)
|
||||
{
|
||||
int secs = static_cast<int>(duration / 1000);
|
||||
int mins = secs / 60;
|
||||
secs = secs % 60;
|
||||
m_durationLabel->setText(QString("%1:%2").arg(mins).arg(secs, 2, 10, QChar('0')));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MediaParticleWidget::setParticle(const Particle *particle)
|
||||
{
|
||||
if (!particle) return;
|
||||
clear();
|
||||
fetchAndPlay(particle->id);
|
||||
}
|
||||
|
||||
void MediaParticleWidget::clear()
|
||||
{
|
||||
m_player->stop();
|
||||
m_player->setSource(QUrl());
|
||||
m_durationLabel->setText("");
|
||||
}
|
||||
|
||||
void MediaParticleWidget::fetchAndPlay(const QString &particleId)
|
||||
{
|
||||
QNetworkRequest request = NetworkManager::instance().buildRequest("/particles/" + particleId + "/download");
|
||||
QNetworkReply *reply = m_nam->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qWarning() << "MediaParticleWidget: download request failed:" << reply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
// m_nam uses ManualRedirectPolicy, so the reply stops at the 3xx
|
||||
// without downloading the media. Extract the pre-signed URL from Location.
|
||||
QUrl mediaUrl = reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||
if (!mediaUrl.isValid())
|
||||
{
|
||||
qWarning() << "MediaParticleWidget: no Location header in download response";
|
||||
return;
|
||||
}
|
||||
if (mediaUrl.isRelative())
|
||||
{
|
||||
mediaUrl = reply->url().resolved(mediaUrl);
|
||||
}
|
||||
|
||||
m_player->setSource(mediaUrl);
|
||||
m_player->play();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef MEDIAPARTICLEWIDGET_H
|
||||
#define MEDIAPARTICLEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QVideoWidget;
|
||||
class QMediaPlayer;
|
||||
class QAudioOutput;
|
||||
class QLabel;
|
||||
struct Particle;
|
||||
|
||||
class MediaParticleWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MediaParticleWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setParticle(const Particle *particle);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *m_nam;
|
||||
QVideoWidget *m_videoWidget;
|
||||
QMediaPlayer *m_player;
|
||||
QAudioOutput *m_audioOutput;
|
||||
QLabel *m_durationLabel;
|
||||
|
||||
void fetchAndPlay(const QString &particleId);
|
||||
};
|
||||
|
||||
#endif // MEDIAPARTICLEWIDGET_H
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "networkdetailwidget.h"
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
NetworkDetailWidget::NetworkDetailWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(16, 16, 16, 16);
|
||||
|
||||
// Network name
|
||||
m_nameLabel = new QLabel(this);
|
||||
QFont nameFont;
|
||||
nameFont.setPointSize(24);
|
||||
m_nameLabel->setFont(nameFont);
|
||||
layout->addWidget(m_nameLabel);
|
||||
|
||||
// Admin label
|
||||
m_adminLabel = new QLabel(this);
|
||||
layout->addWidget(m_adminLabel);
|
||||
|
||||
// Stream capacity progress bar
|
||||
m_streamBar = new QProgressBar(this);
|
||||
m_streamBar->setMinimum(0);
|
||||
m_streamBar->setTextVisible(false);
|
||||
layout->addWidget(m_streamBar);
|
||||
|
||||
m_streamBarLabel = new QLabel(this);
|
||||
layout->addWidget(m_streamBarLabel);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
// Members header
|
||||
auto *membersHeader = new QLabel("Members", this);
|
||||
QFont headerFont;
|
||||
headerFont.setBold(true);
|
||||
membersHeader->setFont(headerFont);
|
||||
layout->addWidget(membersHeader);
|
||||
|
||||
// Members list
|
||||
m_membersList = new QListWidget(this);
|
||||
layout->addWidget(m_membersList, 1);
|
||||
|
||||
// Button row
|
||||
auto *buttonRow = new QHBoxLayout;
|
||||
m_addMemberBtn = new QPushButton("Add Member", this);
|
||||
buttonRow->addWidget(m_addMemberBtn);
|
||||
buttonRow->addStretch();
|
||||
layout->addLayout(buttonRow);
|
||||
|
||||
connect(m_addMemberBtn, &QPushButton::clicked, this, [this]() {
|
||||
bool ok;
|
||||
QString email = QInputDialog::getText(this, "Add Member",
|
||||
"Email address:", QLineEdit::Normal,
|
||||
QString(), &ok);
|
||||
if (ok && !email.isEmpty())
|
||||
{
|
||||
emit addMemberRequested(m_networkId, email);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkDetailWidget::setNetwork(const Network &network, const QString ¤tUserEmail)
|
||||
{
|
||||
m_networkId = network.id;
|
||||
|
||||
m_nameLabel->setText(network.name);
|
||||
m_adminLabel->setText("Admin: " + network.admin.emailPrefix);
|
||||
|
||||
m_streamBar->setMaximum(network.openStreamCapacity > 0 ? network.openStreamCapacity : 1);
|
||||
m_streamBar->setValue(network.openStreamCount);
|
||||
m_streamBarLabel->setText(QString("%1 / %2 open streams")
|
||||
.arg(network.openStreamCount)
|
||||
.arg(network.openStreamCapacity));
|
||||
|
||||
m_membersList->clear();
|
||||
for (const auto &member : network.members)
|
||||
{
|
||||
m_membersList->addItem(member.email);
|
||||
}
|
||||
|
||||
bool isAdmin = (!currentUserEmail.isEmpty() &&
|
||||
currentUserEmail == network.admin.email);
|
||||
m_addMemberBtn->setVisible(isAdmin);
|
||||
}
|
||||
|
||||
void NetworkDetailWidget::clear()
|
||||
{
|
||||
m_networkId.clear();
|
||||
m_nameLabel->setText("");
|
||||
m_adminLabel->setText("");
|
||||
m_streamBar->setValue(0);
|
||||
m_streamBarLabel->setText("");
|
||||
m_membersList->clear();
|
||||
m_addMemberBtn->hide();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef NETWORKDETAILWIDGET_H
|
||||
#define NETWORKDETAILWIDGET_H
|
||||
|
||||
#include "../models.h"
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QProgressBar;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class NetworkDetailWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NetworkDetailWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setNetwork(const Network &network, const QString ¤tUserEmail);
|
||||
void clear();
|
||||
|
||||
signals:
|
||||
void addMemberRequested(const QString &networkId, const QString &email);
|
||||
|
||||
private:
|
||||
QString m_networkId;
|
||||
|
||||
QLabel *m_nameLabel;
|
||||
QLabel *m_adminLabel;
|
||||
QProgressBar *m_streamBar;
|
||||
QLabel *m_streamBarLabel;
|
||||
QListWidget *m_membersList;
|
||||
QPushButton *m_addMemberBtn;
|
||||
};
|
||||
|
||||
#endif // NETWORKDETAILWIDGET_H
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "textparticlewidget.h"
|
||||
#include "../models.h"
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TextParticleWidget::TextParticleWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
m_label = new QLabel();
|
||||
QFont font;
|
||||
font.setPointSize(36);
|
||||
m_label->setFont(font);
|
||||
m_label->setAlignment(Qt::AlignCenter);
|
||||
m_label->setWordWrap(true);
|
||||
m_label->setScaledContents(true);
|
||||
layout->addWidget(m_label);
|
||||
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
void TextParticleWidget::setParticle(const Particle *particle)
|
||||
{
|
||||
if (!particle) return;
|
||||
m_label->setText(particle->data.value("content").toString());
|
||||
}
|
||||
|
||||
void TextParticleWidget::clear()
|
||||
{
|
||||
m_label->setText("");
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef TEXTPARTICLEWIDGET_H
|
||||
#define TEXTPARTICLEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QLabel;
|
||||
struct Particle;
|
||||
|
||||
class TextParticleWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TextParticleWidget(QWidget *parent = nullptr);
|
||||
|
||||
void setParticle(const Particle *particle);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
QLabel *m_label;
|
||||
};
|
||||
|
||||
#endif // TEXTPARTICLEWIDGET_H
|
||||
Reference in New Issue
Block a user