|  |  |  | #include "selfdrive/ui/qt/widgets/prime.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | #include <QJsonDocument>
 | 
					
						
							|  |  |  | #include <QJsonObject>
 | 
					
						
							|  |  |  | #include <QLabel>
 | 
					
						
							|  |  |  | #include <QPushButton>
 | 
					
						
							|  |  |  | #include <QStackedWidget>
 | 
					
						
							|  |  |  | #include <QTimer>
 | 
					
						
							|  |  |  | #include <QVBoxLayout>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QrCode.hpp>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "selfdrive/ui/qt/request_repeater.h"
 | 
					
						
							|  |  |  | #include "selfdrive/ui/qt/util.h"
 | 
					
						
							|  |  |  | #include "selfdrive/ui/qt/qt_window.h"
 | 
					
						
							|  |  |  | #include "selfdrive/ui/qt/widgets/wifi.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | using qrcodegen::QrCode;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PairingQRWidget::PairingQRWidget(QWidget* parent) : QWidget(parent) {
 | 
					
						
							|  |  |  |   timer = new QTimer(this);
 | 
					
						
							|  |  |  |   connect(timer, &QTimer::timeout, this, &PairingQRWidget::refresh);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PairingQRWidget::showEvent(QShowEvent *event) {
 | 
					
						
							|  |  |  |   refresh();
 | 
					
						
							|  |  |  |   timer->start(5 * 60 * 1000);
 | 
					
						
							|  |  |  |   device()->setOffroadBrightness(100);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PairingQRWidget::hideEvent(QHideEvent *event) {
 | 
					
						
							|  |  |  |   timer->stop();
 | 
					
						
							|  |  |  |   device()->setOffroadBrightness(BACKLIGHT_OFFROAD);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PairingQRWidget::refresh() {
 | 
					
						
							|  |  |  |   QString pairToken = CommaApi::create_jwt({{"pair", true}});
 | 
					
						
							|  |  |  |   QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
 | 
					
						
							|  |  |  |   this->updateQrCode(qrString);
 | 
					
						
							|  |  |  |   update();
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PairingQRWidget::updateQrCode(const QString &text) {
 | 
					
						
							|  |  |  |   QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
 | 
					
						
							|  |  |  |   qint32 sz = qr.getSize();
 | 
					
						
							|  |  |  |   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; y++) {
 | 
					
						
							|  |  |  |     for (int x = 0; x < sz; x++) {
 | 
					
						
							|  |  |  |       im.setPixel(x, y, qr.getModule(x, y) ? black : white);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Integer division to prevent anti-aliasing
 | 
					
						
							|  |  |  |   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) : DialogBase(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(tr("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(QString(R"(
 | 
					
						
							|  |  |  |       <ol type='1' style='margin-left: 15px;'>
 | 
					
						
							|  |  |  |         <li style='margin-bottom: 50px;'>%1</li>
 | 
					
						
							|  |  |  |         <li style='margin-bottom: 50px;'>%2</li>
 | 
					
						
							|  |  |  |         <li style='margin-bottom: 50px;'>%3</li>
 | 
					
						
							|  |  |  |       </ol>
 | 
					
						
							|  |  |  |     )").arg(tr("Go to https://connect.comma.ai on your phone"))
 | 
					
						
							|  |  |  |     .arg(tr("Click \"add new device\" and scan the QR code on the right"))
 | 
					
						
							|  |  |  |     .arg(tr("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) : QFrame(parent) {
 | 
					
						
							|  |  |  |   setObjectName("primeWidget");
 | 
					
						
							|  |  |  |   QVBoxLayout *mainLayout = new QVBoxLayout(this);
 | 
					
						
							|  |  |  |   mainLayout->setContentsMargins(56, 40, 56, 40);
 | 
					
						
							|  |  |  |   mainLayout->setSpacing(20);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel *subscribed = new QLabel(tr("✓ SUBSCRIBED"));
 | 
					
						
							|  |  |  |   subscribed->setStyleSheet("font-size: 41px; font-weight: bold; color: #86FF4E;");
 | 
					
						
							|  |  |  |   mainLayout->addWidget(subscribed);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel *commaPrime = new QLabel(tr("comma prime"));
 | 
					
						
							|  |  |  |   commaPrime->setStyleSheet("font-size: 75px; font-weight: bold;");
 | 
					
						
							|  |  |  |   mainLayout->addWidget(commaPrime);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PrimeAdWidget::PrimeAdWidget(QWidget* parent) : QFrame(parent) {
 | 
					
						
							|  |  |  |   QVBoxLayout *main_layout = new QVBoxLayout(this);
 | 
					
						
							|  |  |  |   main_layout->setContentsMargins(80, 90, 80, 60);
 | 
					
						
							|  |  |  |   main_layout->setSpacing(0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel *upgrade = new QLabel(tr("Upgrade Now"));
 | 
					
						
							|  |  |  |   upgrade->setStyleSheet("font-size: 75px; font-weight: bold;");
 | 
					
						
							|  |  |  |   main_layout->addWidget(upgrade, 0, Qt::AlignTop);
 | 
					
						
							|  |  |  |   main_layout->addSpacing(50);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel *description = new QLabel(tr("Become a comma prime member at connect.comma.ai"));
 | 
					
						
							|  |  |  |   description->setStyleSheet("font-size: 56px; font-weight: light; color: white;");
 | 
					
						
							|  |  |  |   description->setWordWrap(true);
 | 
					
						
							|  |  |  |   main_layout->addWidget(description, 0, Qt::AlignTop);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   main_layout->addStretch();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel *features = new QLabel(tr("PRIME FEATURES:"));
 | 
					
						
							|  |  |  |   features->setStyleSheet("font-size: 41px; font-weight: bold; color: #E5E5E5;");
 | 
					
						
							|  |  |  |   main_layout->addWidget(features, 0, Qt::AlignBottom);
 | 
					
						
							|  |  |  |   main_layout->addSpacing(30);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QVector<QString> bullets = {tr("Remote access"), tr("24/7 LTE connectivity"), tr("1 year of drive storage"), tr("Turn-by-turn navigation")};
 | 
					
						
							|  |  |  |   for (auto &b : bullets) {
 | 
					
						
							|  |  |  |     const QString check = "<b><font color='#465BEA'>✓</font></b> ";
 | 
					
						
							|  |  |  |     QLabel *l = new QLabel(check + b);
 | 
					
						
							|  |  |  |     l->setAlignment(Qt::AlignLeft);
 | 
					
						
							|  |  |  |     l->setStyleSheet("font-size: 50px; margin-bottom: 15px;");
 | 
					
						
							|  |  |  |     main_layout->addWidget(l, 0, Qt::AlignBottom);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setStyleSheet(R"(
 | 
					
						
							|  |  |  |     PrimeAdWidget {
 | 
					
						
							|  |  |  |       border-radius: 10px;
 | 
					
						
							|  |  |  |       background-color: #333333;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   )");
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
 | 
					
						
							|  |  |  |   mainLayout = new QStackedWidget;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Unpaired, registration prompt layout
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QFrame* finishRegistration = new QFrame;
 | 
					
						
							|  |  |  |   finishRegistration->setObjectName("primeWidget");
 | 
					
						
							|  |  |  |   QVBoxLayout* finishRegistationLayout = new QVBoxLayout(finishRegistration);
 | 
					
						
							|  |  |  |   finishRegistationLayout->setSpacing(38);
 | 
					
						
							|  |  |  |   finishRegistationLayout->setContentsMargins(64, 48, 64, 48);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel* registrationTitle = new QLabel(tr("Finish Setup"));
 | 
					
						
							|  |  |  |   registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold;");
 | 
					
						
							|  |  |  |   finishRegistationLayout->addWidget(registrationTitle);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
 | 
					
						
							|  |  |  |   registrationDescription->setWordWrap(true);
 | 
					
						
							|  |  |  |   registrationDescription->setStyleSheet("font-size: 50px; font-weight: light;");
 | 
					
						
							|  |  |  |   finishRegistationLayout->addWidget(registrationDescription);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   finishRegistationLayout->addStretch();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QPushButton* pair = new QPushButton(tr("Pair device"));
 | 
					
						
							|  |  |  |   pair->setStyleSheet(R"(
 | 
					
						
							|  |  |  |     QPushButton {
 | 
					
						
							|  |  |  |       font-size: 55px;
 | 
					
						
							|  |  |  |       font-weight: 500;
 | 
					
						
							|  |  |  |       border-radius: 10px;
 | 
					
						
							|  |  |  |       background-color: #465BEA;
 | 
					
						
							|  |  |  |       padding: 64px;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     QPushButton:pressed {
 | 
					
						
							|  |  |  |       background-color: #3049F4;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   )");
 | 
					
						
							|  |  |  |   finishRegistationLayout->addWidget(pair);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   popup = new PairingPopup(this);
 | 
					
						
							|  |  |  |   QObject::connect(pair, &QPushButton::clicked, popup, &PairingPopup::exec);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   mainLayout->addWidget(finishRegistration);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // build stacked layout
 | 
					
						
							|  |  |  |   QVBoxLayout *outer_layout = new QVBoxLayout(this);
 | 
					
						
							|  |  |  |   outer_layout->setContentsMargins(0, 0, 0, 0);
 | 
					
						
							|  |  |  |   outer_layout->addWidget(mainLayout);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QWidget *content = new QWidget;
 | 
					
						
							|  |  |  |   QVBoxLayout *content_layout = new QVBoxLayout(content);
 | 
					
						
							|  |  |  |   content_layout->setContentsMargins(0, 0, 0, 0);
 | 
					
						
							|  |  |  |   content_layout->setSpacing(30);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   primeUser = new PrimeUserWidget;
 | 
					
						
							|  |  |  |   content_layout->addWidget(primeUser);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   WiFiPromptWidget *wifi_prompt = new WiFiPromptWidget;
 | 
					
						
							|  |  |  |   QObject::connect(wifi_prompt, &WiFiPromptWidget::openSettings, this, &SetupWidget::openSettings);
 | 
					
						
							|  |  |  |   content_layout->addWidget(wifi_prompt);
 | 
					
						
							|  |  |  |   content_layout->addStretch();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   mainLayout->addWidget(content);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   primeUser->setVisible(uiState()->hasPrime());
 | 
					
						
							|  |  |  |   mainLayout->setCurrentIndex(1);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setStyleSheet(R"(
 | 
					
						
							|  |  |  |     #primeWidget {
 | 
					
						
							|  |  |  |       border-radius: 10px;
 | 
					
						
							|  |  |  |       background-color: #333333;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   )");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Retain size while hidden
 | 
					
						
							|  |  |  |   QSizePolicy sp_retain = sizePolicy();
 | 
					
						
							|  |  |  |   sp_retain.setRetainSizeWhenHidden(true);
 | 
					
						
							|  |  |  |   setSizePolicy(sp_retain);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // set up API requests
 | 
					
						
							|  |  |  |   if (auto dongleId = getDongleId()) {
 | 
					
						
							|  |  |  |     QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/";
 | 
					
						
							|  |  |  |     RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QObject::connect(repeater, &RequestRepeater::requestDone, this, &SetupWidget::replyFinished);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SetupWidget::replyFinished(const QString &response, bool success) {
 | 
					
						
							|  |  |  |   if (!success) return;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
 | 
					
						
							|  |  |  |   if (doc.isNull()) {
 | 
					
						
							|  |  |  |     qDebug() << "JSON Parse failed on getting pairing and prime status";
 | 
					
						
							|  |  |  |     return;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QJsonObject json = doc.object();
 | 
					
						
							|  |  |  |   bool is_paired = json["is_paired"].toBool();
 | 
					
						
							|  |  |  |   PrimeType prime_type = static_cast<PrimeType>(json["prime_type"].toInt());
 | 
					
						
							|  |  |  |   uiState()->setPrimeType(is_paired ? prime_type : PrimeType::UNPAIRED);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!is_paired) {
 | 
					
						
							|  |  |  |     mainLayout->setCurrentIndex(0);
 | 
					
						
							|  |  |  |   } else {
 | 
					
						
							|  |  |  |     popup->reject();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     primeUser->setVisible(uiState()->hasPrime());
 | 
					
						
							|  |  |  |     mainLayout->setCurrentIndex(1);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 |