UI: big pairing QR code (#22384)
parent
b289ee6e53
commit
45409cb4fe
|
@ -427,6 +427,7 @@ selfdrive/assets/assets.qrc
|
||||||
selfdrive/assets/*.png
|
selfdrive/assets/*.png
|
||||||
selfdrive/assets/*.svg
|
selfdrive/assets/*.svg
|
||||||
selfdrive/assets/fonts/*.ttf
|
selfdrive/assets/fonts/*.ttf
|
||||||
|
selfdrive/assets/icons/*
|
||||||
selfdrive/assets/images/*
|
selfdrive/assets/images/*
|
||||||
selfdrive/assets/offroad/*
|
selfdrive/assets/offroad/*
|
||||||
selfdrive/assets/sounds/*
|
selfdrive/assets/sounds/*
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<file>img_circled_slash.svg</file>
|
<file>img_circled_slash.svg</file>
|
||||||
<file>img_eye_open.svg</file>
|
<file>img_eye_open.svg</file>
|
||||||
<file>img_eye_closed.svg</file>
|
<file>img_eye_closed.svg</file>
|
||||||
|
<file>icons/close.svg</file>
|
||||||
<file>offroad/icon_lock_closed.svg</file>
|
<file>offroad/icon_lock_closed.svg</file>
|
||||||
<file>offroad/icon_checkmark.svg</file>
|
<file>offroad/icon_checkmark.svg</file>
|
||||||
<file>offroad/icon_warning.png</file>
|
<file>offroad/icon_warning.png</file>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="svg4" width="63.238" height="63.238" fill="none" version="1.1" viewBox="0 0 63.238 63.238" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="path2" d="m 55.7181,63.2381 7.52,-7.52 L 39.1199,31.6 63.219,7.5009 55.7371,0.019 31.6381,24.1181 7.5199,0 0,7.52 24.1181,31.6381 0.019,55.7372 7.5009,63.219 31.6,39.12 Z" fill="#000"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 379 B |
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# sudo apt install scour
|
||||||
|
|
||||||
|
for svg in $(find icons/ -type f | grep svg$); do
|
||||||
|
# scour doesn't support overwriting input file
|
||||||
|
scour $svg --remove-metadata $svg.tmp
|
||||||
|
mv $svg.tmp $svg
|
||||||
|
done
|
|
@ -32,7 +32,6 @@ QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) {
|
||||||
QPushButton:pressed {
|
QPushButton:pressed {
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
}
|
}
|
||||||
|
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,21 +8,18 @@
|
||||||
#include <QStackedWidget>
|
#include <QStackedWidget>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include <QrCode.hpp>
|
#include <QrCode.hpp>
|
||||||
|
|
||||||
#include "selfdrive/ui/qt/request_repeater.h"
|
#include "selfdrive/ui/qt/request_repeater.h"
|
||||||
#include "selfdrive/ui/qt/util.h"
|
#include "selfdrive/ui/qt/util.h"
|
||||||
|
#include "selfdrive/ui/qt/qt_window.h"
|
||||||
|
|
||||||
using qrcodegen::QrCode;
|
using qrcodegen::QrCode;
|
||||||
|
|
||||||
PairingQRWidget::PairingQRWidget(QWidget* parent) : QWidget(parent) {
|
PairingQRWidget::PairingQRWidget(QWidget* parent) : QWidget(parent) {
|
||||||
qrCode = new QLabel;
|
|
||||||
qrCode->setScaledContents(true);
|
|
||||||
QVBoxLayout* main_layout = new QVBoxLayout(this);
|
|
||||||
main_layout->addWidget(qrCode, 0, Qt::AlignCenter);
|
|
||||||
|
|
||||||
QTimer* timer = new QTimer(this);
|
QTimer* timer = new QTimer(this);
|
||||||
timer->start(30 * 1000);
|
timer->start(5 * 60 * 1000);
|
||||||
connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh);
|
connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,35 +28,86 @@ void PairingQRWidget::showEvent(QShowEvent *event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PairingQRWidget::refresh() {
|
void PairingQRWidget::refresh() {
|
||||||
QString pairToken = CommaApi::create_jwt({{"pair", true}});
|
if (isVisible()) {
|
||||||
QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
|
QString pairToken = CommaApi::create_jwt({{"pair", true}});
|
||||||
this->updateQrCode(qrString);
|
QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
|
||||||
|
this->updateQrCode(qrString);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PairingQRWidget::updateQrCode(const QString &text) {
|
void PairingQRWidget::updateQrCode(const QString &text) {
|
||||||
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
|
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
|
||||||
qint32 sz = qr.getSize();
|
qint32 sz = qr.getSize();
|
||||||
// make the image larger so we can have a white border
|
QImage im(sz, sz, QImage::Format_RGB32);
|
||||||
QImage im(sz + 2, sz + 2, QImage::Format_RGB32);
|
|
||||||
QRgb black = qRgb(0, 0, 0);
|
QRgb black = qRgb(0, 0, 0);
|
||||||
QRgb white = qRgb(255, 255, 255);
|
QRgb white = qRgb(255, 255, 255);
|
||||||
|
|
||||||
for (int y = 0; y < sz + 2; y++) {
|
|
||||||
for (int x = 0; x < sz + 2; x++) {
|
|
||||||
im.setPixel(x, y, white);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int y = 0; y < sz; y++) {
|
for (int y = 0; y < sz; y++) {
|
||||||
for (int x = 0; x < sz; x++) {
|
for (int x = 0; x < sz; x++) {
|
||||||
im.setPixel(x + 1, y + 1, qr.getModule(x, y) ? black : white);
|
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Integer division to prevent anti-aliasing
|
// Integer division to prevent anti-aliasing
|
||||||
int approx500 = (500 / (sz + 2)) * (sz + 2);
|
int final_sz = ((width() / sz) - 1) * sz;
|
||||||
qrCode->setPixmap(QPixmap::fromImage(im.scaled(approx500, approx500, Qt::KeepAspectRatio, Qt::FastTransformation), Qt::MonoOnly));
|
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
|
||||||
qrCode->setFixedSize(approx500, approx500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PairingQRWidget::paintEvent(QPaintEvent *e) {
|
||||||
|
QPainter p(this);
|
||||||
|
p.fillRect(rect(), Qt::white);
|
||||||
|
|
||||||
|
QSize s = (size() - img.size()) / 2;
|
||||||
|
p.drawPixmap(s.width(), s.height(), img);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) {
|
||||||
|
QHBoxLayout *hlayout = new QHBoxLayout(this);
|
||||||
|
hlayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
hlayout->setSpacing(0);
|
||||||
|
|
||||||
|
setStyleSheet("PairingPopup { background-color: #E0E0E0; }");
|
||||||
|
|
||||||
|
// text
|
||||||
|
QVBoxLayout *vlayout = new QVBoxLayout();
|
||||||
|
vlayout->setContentsMargins(85, 70, 50, 70);
|
||||||
|
vlayout->setSpacing(50);
|
||||||
|
hlayout->addLayout(vlayout, 1);
|
||||||
|
{
|
||||||
|
QPushButton *close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
|
||||||
|
close->setIconSize(QSize(80, 80));
|
||||||
|
close->setStyleSheet("border: none;");
|
||||||
|
vlayout->addWidget(close, 0, Qt::AlignLeft);
|
||||||
|
QObject::connect(close, &QPushButton::clicked, this, &QDialog::reject);
|
||||||
|
|
||||||
|
vlayout->addSpacing(30);
|
||||||
|
|
||||||
|
QLabel *title = new QLabel("Pair your device to your comma account", this);
|
||||||
|
title->setStyleSheet("font-size: 75px; color: black;");
|
||||||
|
title->setWordWrap(true);
|
||||||
|
vlayout->addWidget(title);
|
||||||
|
|
||||||
|
QLabel *instructions = new QLabel(R"(
|
||||||
|
<ol type='1' style='margin-left: 15px;'>
|
||||||
|
<li style='margin-bottom: 50px;'>Go to https://connect.comma.ai on your phone</li>
|
||||||
|
<li style='margin-bottom: 50px;'>Click "add new device" and scan the QR code on the right</li>
|
||||||
|
<li style='margin-bottom: 50px;'>Bookmark connect.comma.ai to your home screen to use it like an app</li>
|
||||||
|
</ol>
|
||||||
|
)", this);
|
||||||
|
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
|
||||||
|
instructions->setWordWrap(true);
|
||||||
|
vlayout->addWidget(instructions);
|
||||||
|
|
||||||
|
vlayout->addStretch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// QR code
|
||||||
|
PairingQRWidget *qr = new PairingQRWidget(this);
|
||||||
|
hlayout->addWidget(qr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
|
PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
|
||||||
mainLayout = new QVBoxLayout(this);
|
mainLayout = new QVBoxLayout(this);
|
||||||
mainLayout->setMargin(0);
|
mainLayout->setMargin(0);
|
||||||
|
@ -101,7 +149,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
|
||||||
commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;");
|
commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;");
|
||||||
pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop);
|
pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop);
|
||||||
|
|
||||||
points = new QLabel("210");
|
points = new QLabel("0");
|
||||||
points->setStyleSheet("font-size: 91px; font-weight: bold;");
|
points->setStyleSheet("font-size: 91px; font-weight: bold;");
|
||||||
pointsLayout->addWidget(points, 0, Qt::AlignTop);
|
pointsLayout->addWidget(points, 0, Qt::AlignTop);
|
||||||
|
|
||||||
|
@ -192,9 +240,9 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
|
||||||
|
|
||||||
finishRegistationLayout->addStretch();
|
finishRegistationLayout->addStretch();
|
||||||
|
|
||||||
QPushButton* finishButton = new QPushButton("Pair device");
|
QPushButton* pair = new QPushButton("Pair device");
|
||||||
finishButton->setFixedHeight(220);
|
pair->setFixedHeight(220);
|
||||||
finishButton->setStyleSheet(R"(
|
pair->setStyleSheet(R"(
|
||||||
QPushButton {
|
QPushButton {
|
||||||
font-size: 55px;
|
font-size: 55px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -205,34 +253,18 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
|
||||||
background-color: #3049F4;
|
background-color: #3049F4;
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
finishRegistationLayout->addWidget(finishButton);
|
finishRegistationLayout->addWidget(pair);
|
||||||
QObject::connect(finishButton, &QPushButton::clicked, this, &SetupWidget::showQrCode);
|
|
||||||
|
popup = new PairingPopup(this);
|
||||||
|
QObject::connect(pair, &QPushButton::clicked, popup, &PairingPopup::exec);
|
||||||
|
|
||||||
mainLayout->addWidget(finishRegistration);
|
mainLayout->addWidget(finishRegistration);
|
||||||
|
|
||||||
// Pairing QR code layout
|
// build stacked layout
|
||||||
|
|
||||||
QWidget* q = new QWidget;
|
|
||||||
q->setObjectName("primeWidget");
|
|
||||||
QVBoxLayout* qrLayout = new QVBoxLayout(q);
|
|
||||||
qrLayout->setContentsMargins(90, 90, 90, 90);
|
|
||||||
|
|
||||||
QLabel* qrLabel = new QLabel("Scan the QR code to pair.");
|
|
||||||
qrLabel->setAlignment(Qt::AlignHCenter);
|
|
||||||
qrLabel->setStyleSheet("font-size: 47px; font-weight: light;");
|
|
||||||
qrLayout->addWidget(qrLabel);
|
|
||||||
qrLayout->addSpacing(50);
|
|
||||||
|
|
||||||
qrLayout->addWidget(new PairingQRWidget);
|
|
||||||
qrLayout->addStretch();
|
|
||||||
|
|
||||||
// setup widget
|
|
||||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||||
outer_layout->setContentsMargins(0, 0, 0, 0);
|
outer_layout->setContentsMargins(0, 0, 0, 0);
|
||||||
outer_layout->addWidget(mainLayout);
|
outer_layout->addWidget(mainLayout);
|
||||||
|
|
||||||
mainLayout->addWidget(q);
|
|
||||||
|
|
||||||
primeAd = new PrimeAdWidget;
|
primeAd = new PrimeAdWidget;
|
||||||
mainLayout->addWidget(primeAd);
|
mainLayout->addWidget(primeAd);
|
||||||
|
|
||||||
|
@ -259,27 +291,15 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
|
||||||
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/";
|
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/";
|
||||||
RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5);
|
RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5);
|
||||||
|
|
||||||
|
QObject::connect(repeater, &RequestRepeater::failedResponse, this, &SetupWidget::show);
|
||||||
QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &SetupWidget::replyFinished);
|
QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &SetupWidget::replyFinished);
|
||||||
QObject::connect(repeater, &RequestRepeater::failedResponse, this, &SetupWidget::parseError);
|
|
||||||
}
|
}
|
||||||
hide(); // Only show when first request comes back
|
hide(); // Only show when first request comes back
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupWidget::parseError(const QString &response) {
|
|
||||||
show();
|
|
||||||
if (mainLayout->currentIndex() == 1) {
|
|
||||||
showQr = false;
|
|
||||||
mainLayout->setCurrentIndex(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetupWidget::showQrCode() {
|
|
||||||
showQr = true;
|
|
||||||
mainLayout->setCurrentIndex(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetupWidget::replyFinished(const QString &response) {
|
void SetupWidget::replyFinished(const QString &response) {
|
||||||
show();
|
show();
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||||
if (doc.isNull()) {
|
if (doc.isNull()) {
|
||||||
qDebug() << "JSON Parse failed on getting pairing and prime status";
|
qDebug() << "JSON Parse failed on getting pairing and prime status";
|
||||||
|
@ -288,12 +308,13 @@ void SetupWidget::replyFinished(const QString &response) {
|
||||||
|
|
||||||
QJsonObject json = doc.object();
|
QJsonObject json = doc.object();
|
||||||
if (!json["is_paired"].toBool()) {
|
if (!json["is_paired"].toBool()) {
|
||||||
mainLayout->setCurrentIndex(showQr);
|
mainLayout->setCurrentIndex(0);
|
||||||
} else if (!json["prime"].toBool()) {
|
|
||||||
showQr = false;
|
|
||||||
mainLayout->setCurrentWidget(primeAd);
|
|
||||||
} else {
|
} else {
|
||||||
showQr = false;
|
popup->reject();
|
||||||
mainLayout->setCurrentWidget(primeUser);
|
if (!json["prime"].toBool()) {
|
||||||
|
mainLayout->setCurrentWidget(primeAd);
|
||||||
|
} else {
|
||||||
|
mainLayout->setCurrentWidget(primeUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,18 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/widgets/input.h"
|
||||||
|
|
||||||
|
// pairing QR code
|
||||||
class PairingQRWidget : public QWidget {
|
class PairingQRWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PairingQRWidget(QWidget* parent = 0);
|
explicit PairingQRWidget(QWidget* parent = 0);
|
||||||
|
void paintEvent(QPaintEvent*) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLabel* qrCode;
|
QPixmap img;
|
||||||
void updateQrCode(const QString &text);
|
void updateQrCode(const QString &text);
|
||||||
void showEvent(QShowEvent *event) override;
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
@ -20,6 +24,15 @@ private slots:
|
||||||
void refresh();
|
void refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// pairing popup widget
|
||||||
|
class PairingPopup : public QDialogBase {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PairingPopup(QWidget* parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
// widget for paired users with prime
|
||||||
class PrimeUserWidget : public QWidget {
|
class PrimeUserWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -33,12 +46,15 @@ private slots:
|
||||||
void replyFinished(const QString &response);
|
void replyFinished(const QString &response);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// widget for paired users without prime
|
||||||
class PrimeAdWidget : public QFrame {
|
class PrimeAdWidget : public QFrame {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit PrimeAdWidget(QWidget* parent = 0);
|
explicit PrimeAdWidget(QWidget* parent = 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// container widget
|
||||||
class SetupWidget : public QFrame {
|
class SetupWidget : public QFrame {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -46,13 +62,11 @@ public:
|
||||||
explicit SetupWidget(QWidget* parent = 0);
|
explicit SetupWidget(QWidget* parent = 0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStackedWidget* mainLayout;
|
PairingPopup *popup;
|
||||||
|
QStackedWidget *mainLayout;
|
||||||
PrimeAdWidget *primeAd;
|
PrimeAdWidget *primeAd;
|
||||||
PrimeUserWidget *primeUser;
|
PrimeUserWidget *primeUser;
|
||||||
bool showQr = false;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void parseError(const QString &response);
|
|
||||||
void replyFinished(const QString &response);
|
void replyFinished(const QString &response);
|
||||||
void showQrCode();
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue