From 45409cb4fe485dc12168dc9740eb561ffedebe4c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 30 Sep 2021 14:00:52 -0700 Subject: [PATCH] UI: big pairing QR code (#22384) --- release/files_common | 1 + selfdrive/assets/assets.qrc | 1 + selfdrive/assets/icons/close.svg | 4 + selfdrive/assets/strip-svg-metadata.sh | 9 ++ selfdrive/ui/qt/widgets/input.cc | 1 - selfdrive/ui/qt/widgets/prime.cc | 153 ++++++++++++++----------- selfdrive/ui/qt/widgets/prime.h | 24 +++- 7 files changed, 121 insertions(+), 72 deletions(-) create mode 100644 selfdrive/assets/icons/close.svg create mode 100755 selfdrive/assets/strip-svg-metadata.sh diff --git a/release/files_common b/release/files_common index c31ce0ec6b..fa5d1067df 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 4d3ac97bce..39be41aa65 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 0000000000..b1e6d3b867 --- /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 0000000000..a8b35eadde --- /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 f23d9b8b80..1122faf4c7 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 e3828f3d85..2be91b5d16 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"( +
    +
  1. Go to https://connect.comma.ai on your phone
  2. +
  3. Click "add new device" and scan the QR code on the right
  4. +
  5. Bookmark connect.comma.ai to your home screen to use it like an app
  6. +
+ )", 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 27b2b6e546..6044f06104 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(); };