#include "selfdrive/ui/qt/widgets/prime.h" #include #include #include #include #include #include #include #include #include #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) : 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(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"(
  1. %1
  2. %2
  3. %3
)").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 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 = " "; 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()->primeType()); 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(); int prime_type = json["prime_type"].toInt(); uiState()->setPrimeType(prime_type); if (!json["is_paired"].toBool()) { mainLayout->setCurrentIndex(0); } else { popup->reject(); primeUser->setVisible(prime_type); mainLayout->setCurrentIndex(1); } }