diff --git a/release/files_common b/release/files_common
index c31ce0ec6..fa5d1067d 100644
--- a/release/files_common
+++ b/release/files_common
@@ -427,6 +427,7 @@ selfdrive/assets/assets.qrc
selfdrive/assets/*.png
selfdrive/assets/*.svg
selfdrive/assets/fonts/*.ttf
+selfdrive/assets/icons/*
selfdrive/assets/images/*
selfdrive/assets/offroad/*
selfdrive/assets/sounds/*
diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc
index 4d3ac97bc..39be41aa6 100644
--- a/selfdrive/assets/assets.qrc
+++ b/selfdrive/assets/assets.qrc
@@ -5,6 +5,7 @@
img_circled_slash.svg
img_eye_open.svg
img_eye_closed.svg
+ icons/close.svg
offroad/icon_lock_closed.svg
offroad/icon_checkmark.svg
offroad/icon_warning.png
diff --git a/selfdrive/assets/icons/close.svg b/selfdrive/assets/icons/close.svg
new file mode 100644
index 000000000..b1e6d3b86
--- /dev/null
+++ b/selfdrive/assets/icons/close.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/selfdrive/assets/strip-svg-metadata.sh b/selfdrive/assets/strip-svg-metadata.sh
new file mode 100755
index 000000000..a8b35eadd
--- /dev/null
+++ b/selfdrive/assets/strip-svg-metadata.sh
@@ -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
diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc
index f23d9b8b8..1122faf4c 100644
--- a/selfdrive/ui/qt/widgets/input.cc
+++ b/selfdrive/ui/qt/widgets/input.cc
@@ -32,7 +32,6 @@ QDialogBase::QDialogBase(QWidget *parent) : QDialog(parent) {
QPushButton:pressed {
background-color: #444444;
}
-
)");
}
diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc
index e3828f3d8..2be91b5d1 100644
--- a/selfdrive/ui/qt/widgets/prime.cc
+++ b/selfdrive/ui/qt/widgets/prime.cc
@@ -8,21 +8,18 @@
#include
#include
#include
+
#include
#include "selfdrive/ui/qt/request_repeater.h"
#include "selfdrive/ui/qt/util.h"
+#include "selfdrive/ui/qt/qt_window.h"
using qrcodegen::QrCode;
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);
- timer->start(30 * 1000);
+ timer->start(5 * 60 * 1000);
connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh);
}
@@ -31,35 +28,86 @@ void PairingQRWidget::showEvent(QShowEvent *event) {
}
void PairingQRWidget::refresh() {
- QString pairToken = CommaApi::create_jwt({{"pair", true}});
- QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
- this->updateQrCode(qrString);
+ if (isVisible()) {
+ QString pairToken = CommaApi::create_jwt({{"pair", true}});
+ QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
+ this->updateQrCode(qrString);
+ }
}
void PairingQRWidget::updateQrCode(const QString &text) {
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
qint32 sz = qr.getSize();
- // make the image larger so we can have a white border
- QImage im(sz + 2, sz + 2, QImage::Format_RGB32);
+ QImage im(sz, sz, QImage::Format_RGB32);
+
QRgb black = qRgb(0, 0, 0);
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 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
- int approx500 = (500 / (sz + 2)) * (sz + 2);
- qrCode->setPixmap(QPixmap::fromImage(im.scaled(approx500, approx500, Qt::KeepAspectRatio, Qt::FastTransformation), Qt::MonoOnly));
- qrCode->setFixedSize(approx500, approx500);
+ int final_sz = ((width() / sz) - 1) * sz;
+ img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
+}
+
+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"(
+
+ - Go to https://connect.comma.ai on your phone
+ - Click "add new device" and scan the QR code on the right
+ - Bookmark connect.comma.ai to your home screen to use it like an app
+
+ )", 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) {
mainLayout = new QVBoxLayout(this);
mainLayout->setMargin(0);
@@ -101,7 +149,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) {
commaPoints->setStyleSheet("font-size: 41px; font-family: Inter SemiBold;");
pointsLayout->addWidget(commaPoints, 0, Qt::AlignTop);
- points = new QLabel("210");
+ points = new QLabel("0");
points->setStyleSheet("font-size: 91px; font-weight: bold;");
pointsLayout->addWidget(points, 0, Qt::AlignTop);
@@ -192,9 +240,9 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
finishRegistationLayout->addStretch();
- QPushButton* finishButton = new QPushButton("Pair device");
- finishButton->setFixedHeight(220);
- finishButton->setStyleSheet(R"(
+ QPushButton* pair = new QPushButton("Pair device");
+ pair->setFixedHeight(220);
+ pair->setStyleSheet(R"(
QPushButton {
font-size: 55px;
font-weight: 400;
@@ -205,34 +253,18 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
background-color: #3049F4;
}
)");
- finishRegistationLayout->addWidget(finishButton);
- QObject::connect(finishButton, &QPushButton::clicked, this, &SetupWidget::showQrCode);
-
- mainLayout->addWidget(finishRegistration);
-
- // Pairing QR code layout
-
- QWidget* q = new QWidget;
- q->setObjectName("primeWidget");
- QVBoxLayout* qrLayout = new QVBoxLayout(q);
- qrLayout->setContentsMargins(90, 90, 90, 90);
+ finishRegistationLayout->addWidget(pair);
- 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);
+ popup = new PairingPopup(this);
+ QObject::connect(pair, &QPushButton::clicked, popup, &PairingPopup::exec);
- qrLayout->addWidget(new PairingQRWidget);
- qrLayout->addStretch();
+ mainLayout->addWidget(finishRegistration);
- // setup widget
+ // build stacked layout
QVBoxLayout *outer_layout = new QVBoxLayout(this);
outer_layout->setContentsMargins(0, 0, 0, 0);
outer_layout->addWidget(mainLayout);
- mainLayout->addWidget(q);
-
primeAd = new PrimeAdWidget;
mainLayout->addWidget(primeAd);
@@ -259,27 +291,15 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/";
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::failedResponse, this, &SetupWidget::parseError);
}
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) {
show();
+
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
if (doc.isNull()) {
qDebug() << "JSON Parse failed on getting pairing and prime status";
@@ -288,12 +308,13 @@ void SetupWidget::replyFinished(const QString &response) {
QJsonObject json = doc.object();
if (!json["is_paired"].toBool()) {
- mainLayout->setCurrentIndex(showQr);
- } else if (!json["prime"].toBool()) {
- showQr = false;
- mainLayout->setCurrentWidget(primeAd);
+ mainLayout->setCurrentIndex(0);
} else {
- showQr = false;
- mainLayout->setCurrentWidget(primeUser);
+ popup->reject();
+ if (!json["prime"].toBool()) {
+ mainLayout->setCurrentWidget(primeAd);
+ } else {
+ mainLayout->setCurrentWidget(primeUser);
+ }
}
}
diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h
index 27b2b6e54..6044f0610 100644
--- a/selfdrive/ui/qt/widgets/prime.h
+++ b/selfdrive/ui/qt/widgets/prime.h
@@ -5,14 +5,18 @@
#include
#include
+#include "selfdrive/ui/qt/widgets/input.h"
+
+// pairing QR code
class PairingQRWidget : public QWidget {
Q_OBJECT
public:
explicit PairingQRWidget(QWidget* parent = 0);
+ void paintEvent(QPaintEvent*) override;
private:
- QLabel* qrCode;
+ QPixmap img;
void updateQrCode(const QString &text);
void showEvent(QShowEvent *event) override;
@@ -20,6 +24,15 @@ private slots:
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 {
Q_OBJECT
public:
@@ -33,12 +46,15 @@ private slots:
void replyFinished(const QString &response);
};
+
+// widget for paired users without prime
class PrimeAdWidget : public QFrame {
Q_OBJECT
public:
explicit PrimeAdWidget(QWidget* parent = 0);
};
+// container widget
class SetupWidget : public QFrame {
Q_OBJECT
@@ -46,13 +62,11 @@ public:
explicit SetupWidget(QWidget* parent = 0);
private:
- QStackedWidget* mainLayout;
+ PairingPopup *popup;
+ QStackedWidget *mainLayout;
PrimeAdWidget *primeAd;
PrimeUserWidget *primeUser;
- bool showQr = false;
private slots:
- void parseError(const QString &response);
void replyFinished(const QString &response);
- void showQrCode();
};