dm: маршрутизация через общую группу (group_id), JSON-эскейпинг; звонки: иконки/UI в desktop и android
This commit is contained in:
1 parent
b483335c06
commit
e4557b0473
14 files changed
+925
-139
No files matched your search
@@ -11,6 +11,8 @@
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
#include <QCloseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
@@ -19,29 +21,146 @@ extern "C" {
|
||||
#include "../../lib/debug_config.h"
|
||||
}
|
||||
|
||||
/* ── рисование иконок звонка (QPainter, без внешних ассетов) ── */
|
||||
|
||||
enum class Glyph { Phone, PhoneDown, Mic, MicOff };
|
||||
|
||||
static QColor colorForName(const QString& name) {
|
||||
static const QColor palette[] = {
|
||||
QColor("#5B9BD5"), QColor("#70AD47"), QColor("#ED7D31"),
|
||||
QColor("#9B59B6"), QColor("#F4B400"), QColor("#E67E22"),
|
||||
QColor("#16A085"), QColor("#D35400"),
|
||||
};
|
||||
quint32 h = 0;
|
||||
for (const QChar c : name) h = h * 31u + c.unicode();
|
||||
return palette[h % 8];
|
||||
}
|
||||
|
||||
static QPixmap makeAvatar(const QString& name, int size) {
|
||||
QPixmap pm(size, size);
|
||||
pm.fill(Qt::transparent);
|
||||
QPainter p(&pm);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setBrush(colorForName(name));
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawEllipse(0, 0, size, size);
|
||||
QString initial = name.trimmed().isEmpty() ? QStringLiteral("?") : name.trimmed().left(1).toUpper();
|
||||
p.setPen(Qt::white);
|
||||
QFont f = p.font();
|
||||
f.setBold(true);
|
||||
f.setPixelSize(size / 2);
|
||||
p.setFont(f);
|
||||
p.drawText(QRect(0, 0, size, size), Qt::AlignCenter, initial);
|
||||
p.end();
|
||||
return pm;
|
||||
}
|
||||
|
||||
static void drawPhoneGlyph(QPainter* p, const QRectF& r, bool down) {
|
||||
const qreal s = r.width();
|
||||
p->save();
|
||||
p->translate(r.center());
|
||||
p->rotate(down ? 135.0 : -45.0);
|
||||
const qreal w = s * 0.36, h = s * 0.64;
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(-w / 2, -h / 2, w, h), w * 0.5, w * 0.5);
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(Qt::white);
|
||||
p->drawPath(path);
|
||||
p->restore();
|
||||
}
|
||||
|
||||
static void drawMicGlyph(QPainter* p, const QRectF& r, bool off) {
|
||||
const qreal s = r.width();
|
||||
const QPointF c = r.center();
|
||||
p->save();
|
||||
QPen pen(Qt::white, s * 0.09, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
p->setPen(pen);
|
||||
p->setBrush(Qt::NoBrush);
|
||||
const qreal cw = s * 0.30;
|
||||
p->drawRoundedRect(QRectF(c.x() - cw / 2, r.top() + s * 0.10, cw, s * 0.40), cw / 2, cw / 2);
|
||||
p->drawArc(QRectF(c.x() - s * 0.20, r.top() + s * 0.42, s * 0.40, s * 0.26), 180 * 16, 180 * 16);
|
||||
p->drawLine(QPointF(c.x(), r.top() + s * 0.60), QPointF(c.x(), r.top() + s * 0.68));
|
||||
p->drawLine(QPointF(c.x() - s * 0.17, r.top() + s * 0.68), QPointF(c.x() + s * 0.17, r.top() + s * 0.68));
|
||||
if (off) {
|
||||
p->setPen(QPen(Qt::white, s * 0.09, Qt::SolidLine, Qt::RoundCap));
|
||||
p->drawLine(QPointF(c.x() - s * 0.26, c.y() - s * 0.26), QPointF(c.x() + s * 0.26, c.y() + s * 0.26));
|
||||
}
|
||||
p->restore();
|
||||
}
|
||||
|
||||
static QIcon makeRoundIcon(const QColor& bg, Glyph glyph, int size) {
|
||||
QPixmap pm(size, size);
|
||||
pm.fill(Qt::transparent);
|
||||
QPainter p(&pm);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(bg);
|
||||
p.drawEllipse(0, 0, size, size);
|
||||
const QRectF glyphRect(size * 0.24, size * 0.24, size * 0.52, size * 0.52);
|
||||
switch (glyph) {
|
||||
case Glyph::Phone: drawPhoneGlyph(&p, glyphRect, false); break;
|
||||
case Glyph::PhoneDown: drawPhoneGlyph(&p, glyphRect, true); break;
|
||||
case Glyph::Mic: drawMicGlyph(&p, glyphRect, false); break;
|
||||
case Glyph::MicOff: drawMicGlyph(&p, glyphRect, true); break;
|
||||
}
|
||||
p.end();
|
||||
return QIcon(pm);
|
||||
}
|
||||
|
||||
static QPushButton* makeRoundBtn(const QIcon& icon, const QString& tooltip, QWidget* parent) {
|
||||
auto* btn = new QPushButton(parent);
|
||||
btn->setIcon(icon);
|
||||
btn->setIconSize(QSize(52, 52));
|
||||
btn->setFixedSize(64, 64);
|
||||
btn->setCursor(Qt::PointingHandCursor);
|
||||
btn->setToolTip(tooltip);
|
||||
btn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 32px; background: transparent; }"
|
||||
"QPushButton:pressed { background: rgba(255,255,255,0.15); }");
|
||||
return btn;
|
||||
}
|
||||
|
||||
static QWidget* wrapWithCaption(QWidget* btn, const QString& caption, QLabel** captionOut = nullptr) {
|
||||
auto* w = new QWidget(btn->parentWidget());
|
||||
auto* l = new QVBoxLayout(w);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(6);
|
||||
l->addWidget(btn, 0, Qt::AlignHCenter);
|
||||
auto* cap = new QLabel(caption, w);
|
||||
cap->setAlignment(Qt::AlignCenter);
|
||||
cap->setStyleSheet("color: palette(text); font-size: 11px; background: transparent;");
|
||||
l->addWidget(cap, 0, Qt::AlignHCenter);
|
||||
if (captionOut) *captionOut = cap;
|
||||
return w;
|
||||
}
|
||||
|
||||
CallWindow::CallWindow(QWidget* parent)
|
||||
: QWidget(parent, Qt::Window) {
|
||||
setWindowTitle(QStringLiteral("Звонок"));
|
||||
setFixedSize(320, 300);
|
||||
setFixedSize(340, 400);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
m_avatarLabel = new QLabel(this);
|
||||
m_avatarLabel->setFixedSize(80, 80);
|
||||
m_avatarLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
m_peerLabel = new QLabel(this);
|
||||
m_peerLabel->setAlignment(Qt::AlignCenter);
|
||||
m_peerLabel->setStyleSheet("font-size: 17px; font-weight: bold; color: palette(text);");
|
||||
|
||||
m_statusLabel = new QLabel(this);
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_statusLabel->setStyleSheet("color: gray; font-size: 13px;");
|
||||
|
||||
m_durationLabel = new QLabel(QStringLiteral("00:00"), this);
|
||||
m_durationLabel->setAlignment(Qt::AlignCenter);
|
||||
m_durationLabel->setStyleSheet("font-size: 15px; color: palette(text); font-family: monospace;");
|
||||
|
||||
m_acceptBtn = new QPushButton(QStringLiteral("Принять"), this);
|
||||
m_declineBtn = new QPushButton(QStringLiteral("Отклонить"), this);
|
||||
m_hangupBtn = new QPushButton(QStringLiteral("Завершить"), this);
|
||||
|
||||
m_muteBtn = new QPushButton(QStringLiteral("Mute"), this);
|
||||
m_acceptBtn = makeRoundBtn(makeRoundIcon(QColor("#27ae60"), Glyph::Phone, 96), QStringLiteral("Принять"), this);
|
||||
m_declineBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QStringLiteral("Отклонить"), this);
|
||||
m_hangupBtn = makeRoundBtn(makeRoundIcon(QColor("#e74c3c"), Glyph::PhoneDown, 96), QStringLiteral("Завершить"), this);
|
||||
m_muteBtn = makeRoundBtn(makeRoundIcon(QColor("#7f8c8d"), Glyph::Mic, 96), QStringLiteral("Mute"), this);
|
||||
m_muteBtn->setCheckable(true);
|
||||
m_muteBtn->setStyleSheet(
|
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 4px 12px; font-size: 12px; background: palette(button); }"
|
||||
"QPushButton:checked { background: #c0392b; color: white; }");
|
||||
|
||||
m_deviceCombo = new QComboBox(this);
|
||||
m_deviceCombo->setStyleSheet(
|
||||
@@ -49,15 +168,29 @@ CallWindow::CallWindow(QWidget* parent)
|
||||
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
|
||||
|
||||
QVBoxLayout* lay = new QVBoxLayout(this);
|
||||
lay->setContentsMargins(16, 20, 16, 16);
|
||||
lay->setSpacing(8);
|
||||
lay->addWidget(m_avatarLabel, 0, Qt::AlignHCenter);
|
||||
lay->addWidget(m_peerLabel);
|
||||
lay->addWidget(m_statusLabel);
|
||||
lay->addWidget(m_durationLabel);
|
||||
lay->addStretch(1);
|
||||
|
||||
m_acceptCell = wrapWithCaption(m_acceptBtn, QStringLiteral("Принять"));
|
||||
m_declineCell = wrapWithCaption(m_declineBtn, QStringLiteral("Отклонить"));
|
||||
m_hangupCell = wrapWithCaption(m_hangupBtn, QStringLiteral("Завершить"));
|
||||
m_muteCell = wrapWithCaption(m_muteBtn, QStringLiteral("Mute"), &m_muteCaption);
|
||||
|
||||
QHBoxLayout* btn = new QHBoxLayout();
|
||||
btn->addWidget(m_acceptBtn);
|
||||
btn->addWidget(m_declineBtn);
|
||||
btn->addWidget(m_hangupBtn);
|
||||
btn->setSpacing(10);
|
||||
btn->addStretch(1);
|
||||
btn->addWidget(m_acceptCell);
|
||||
btn->addWidget(m_declineCell);
|
||||
btn->addWidget(m_hangupCell);
|
||||
btn->addWidget(m_muteCell);
|
||||
btn->addStretch(1);
|
||||
lay->addLayout(btn);
|
||||
lay->addWidget(m_muteBtn);
|
||||
lay->addStretch(1);
|
||||
lay->addWidget(m_deviceCombo);
|
||||
|
||||
connect(m_acceptBtn, &QPushButton::clicked, this, &CallWindow::onAccept);
|
||||
@@ -77,6 +210,7 @@ CallWindow::~CallWindow() = default;
|
||||
|
||||
void CallWindow::setPeerName(const QString& name) {
|
||||
m_peerLabel->setText(name);
|
||||
m_avatarLabel->setPixmap(makeAvatar(name, 80));
|
||||
}
|
||||
|
||||
void CallWindow::setState(State s) {
|
||||
@@ -125,10 +259,10 @@ void CallWindow::populateDeviceCombo() {
|
||||
}
|
||||
|
||||
void CallWindow::updateButtons() {
|
||||
m_acceptBtn->setVisible(m_state == Incoming);
|
||||
m_declineBtn->setVisible(m_state == Incoming);
|
||||
m_hangupBtn->setVisible(m_state == Outgoing || m_state == Active);
|
||||
m_muteBtn->setVisible(m_state == Active);
|
||||
m_acceptCell->setVisible(m_state == Incoming);
|
||||
m_declineCell->setVisible(m_state == Incoming);
|
||||
m_hangupCell->setVisible(m_state == Outgoing || m_state == Active);
|
||||
m_muteCell->setVisible(m_state == Active);
|
||||
m_deviceCombo->setVisible(m_state == Active);
|
||||
}
|
||||
|
||||
@@ -155,7 +289,10 @@ void CallWindow::onHangup() {
|
||||
|
||||
void CallWindow::onMuteToggled(bool checked) {
|
||||
CallAudioEngine::instance()->setMuted(checked);
|
||||
m_muteBtn->setText(checked ? QStringLiteral("Вкл. микрофон") : QStringLiteral("Mute"));
|
||||
m_muteBtn->setIcon(makeRoundIcon(checked ? QColor("#e74c3c") : QColor("#7f8c8d"),
|
||||
checked ? Glyph::MicOff : Glyph::Mic, 96));
|
||||
if (m_muteCaption)
|
||||
m_muteCaption->setText(checked ? QStringLiteral("Вкл. микрофон") : QStringLiteral("Mute"));
|
||||
}
|
||||
|
||||
void CallWindow::onDeviceChanged(int index) {
|
||||
|
||||
Reference in new issue
Block a user