diff --git a/common/params.cc b/common/params.cc index f07911cea..cd4c40603 100644 --- a/common/params.cc +++ b/common/params.cc @@ -201,6 +201,7 @@ std::unordered_map keys = { {"Version", PERSISTENT}, // --- sunnypilot params --- // + {"ApiCache_DriveStats", PERSISTENT}, {"EnableGithubRunner", PERSISTENT | BACKUP}, // MADS params diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 6452e15ae..c29ef4f2f 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -19,6 +19,7 @@ #define LayoutWidget LayoutWidgetSP #define Sidebar SidebarSP #define ElidedLabel ElidedLabelSP +#define SetupWidget SetupWidgetSP #else #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" diff --git a/selfdrive/ui/qt/offroad/offroad_home.cc b/selfdrive/ui/qt/offroad/offroad_home.cc index 06385027a..6fcb6a71a 100644 --- a/selfdrive/ui/qt/offroad/offroad_home.cc +++ b/selfdrive/ui/qt/offroad/offroad_home.cc @@ -45,10 +45,11 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { QWidget *home_widget = new QWidget(this); { - QHBoxLayout *home_layout = new QHBoxLayout(home_widget); + home_layout = new QHBoxLayout(home_widget); home_layout->setContentsMargins(0, 0, 0, 0); home_layout->setSpacing(30); +#ifndef SUNNYPILOT // left: PrimeAdWidget QStackedWidget *left_widget = new QStackedWidget(this); QVBoxLayout *left_prime_layout = new QVBoxLayout(); @@ -69,6 +70,7 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { }); home_layout->addWidget(left_widget, 1); +#endif // right: ExperimentalModeButton, SetupWidget QWidget* right_widget = new QWidget(this); diff --git a/selfdrive/ui/qt/offroad/offroad_home.h b/selfdrive/ui/qt/offroad/offroad_home.h index 6acf461ba..40b63ce8d 100644 --- a/selfdrive/ui/qt/offroad/offroad_home.h +++ b/selfdrive/ui/qt/offroad/offroad_home.h @@ -15,14 +15,17 @@ #include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" #include "selfdrive/ui/sunnypilot/qt/onroad/onroad_home.h" #include "selfdrive/ui/sunnypilot/qt/sidebar.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/prime.h" #define OnroadWindow OnroadWindowSP #define LayoutWidget LayoutWidgetSP #define Sidebar SidebarSP #define ElidedLabel ElidedLabelSP +#define SetupWidget SetupWidgetSP #else #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/onroad/onroad_home.h" #include "selfdrive/ui/qt/sidebar.h" +#include "selfdrive/ui/qt/widgets/prime.h" #endif class OffroadHome : public QFrame { @@ -34,6 +37,9 @@ public: signals: void openSettings(int index = 0, const QString ¶m = ""); +protected: + QHBoxLayout *home_layout; + private: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 62f5c0ab5..85ccfa64b 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -221,7 +221,7 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { outer_layout->addWidget(mainLayout); QWidget *content = new QWidget; - QVBoxLayout *content_layout = new QVBoxLayout(content); + content_layout = new QVBoxLayout(content); content_layout->setContentsMargins(0, 0, 0, 0); content_layout->setSpacing(30); diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h index d1ba334e8..7c27b6f7d 100644 --- a/selfdrive/ui/qt/widgets/prime.h +++ b/selfdrive/ui/qt/widgets/prime.h @@ -63,6 +63,9 @@ public: signals: void openSettings(int index = 0, const QString ¶m = ""); +protected: + QVBoxLayout *content_layout; + private: PairingPopup *popup; QStackedWidget *mainLayout; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 987b0d2e9..121f621b4 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -1,6 +1,8 @@ widgets_src = [ "sunnypilot/qt/widgets/toggle.cc", "sunnypilot/qt/widgets/controls.cc", + "sunnypilot/qt/widgets/drive_stats.cc", + "sunnypilot/qt/widgets/prime.cc", "sunnypilot/qt/widgets/scrollview.cc", ] diff --git a/selfdrive/ui/sunnypilot/qt/home.cc b/selfdrive/ui/sunnypilot/qt/home.cc index 2fd4dcbbe..92b6e724f 100644 --- a/selfdrive/ui/sunnypilot/qt/home.cc +++ b/selfdrive/ui/sunnypilot/qt/home.cc @@ -7,6 +7,8 @@ #include "selfdrive/ui/sunnypilot/qt/home.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" + HomeWindowSP::HomeWindowSP(QWidget *parent) : HomeWindow(parent) { } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc b/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc index 66e116bb3..05b1d8084 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/offroad_home.cc @@ -7,5 +7,14 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/offroad_home.h" +#include + +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" + OffroadHomeSP::OffroadHomeSP(QWidget *parent) : OffroadHome(parent) { + QStackedWidget *left_widget = new QStackedWidget(this); + left_widget->addWidget(new DriveStats(this)); + left_widget->setStyleSheet("border-radius: 10px;"); + + home_layout->insertWidget(0, left_widget); } diff --git a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc new file mode 100644 index 000000000..032abdbbf --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.cc @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + * + * This file is part of sunnypilot and is licensed under the MIT License. + * See the LICENSE.md file in the root directory for more details. + */ + +#include "selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h" + +#include +#include +#include + +#include "common/params.h" +#include "selfdrive/ui/qt/request_repeater.h" +#include "selfdrive/ui/qt/util.h" + +static QLabel* newLabel(const QString& text, const QString &type) { + QLabel* label = new QLabel(text); + label->setProperty("type", type); + return label; +} + +DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { + metric_ = Params().getBool("IsMetric"); + + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 50, 50, 60); + + auto add_stats_layouts = [=](const QString &title, StatsLabels& labels) { + QGridLayout* grid_layout = new QGridLayout; + grid_layout->setVerticalSpacing(10); + grid_layout->setContentsMargins(0, 10, 0, 10); + + int row = 0; + grid_layout->addWidget(newLabel(title, "title"), row++, 0, 1, 3); + grid_layout->addItem(new QSpacerItem(0, 50), row++, 0, 1, 1); + + grid_layout->addWidget(labels.routes = newLabel("0", "number"), row, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance = newLabel("0", "number"), row, 1, Qt::AlignLeft); + grid_layout->addWidget(labels.hours = newLabel("0", "number"), row, 2, Qt::AlignLeft); + + grid_layout->addWidget(newLabel((tr("Drives")), "unit"), row + 1, 0, Qt::AlignLeft); + grid_layout->addWidget(labels.distance_unit = newLabel(getDistanceUnit(), "unit"), row + 1, 1, Qt::AlignLeft); + grid_layout->addWidget(newLabel(tr("Hours"), "unit"), row + 1, 2, Qt::AlignLeft); + + main_layout->addLayout(grid_layout); + }; + + add_stats_layouts(tr("ALL TIME"), all_); + main_layout->addStretch(); + add_stats_layouts(tr("PAST WEEK"), week_); + + if (auto dongleId = getDongleId()) { + QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; + RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_DriveStats", 30); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &DriveStats::parseResponse); + } + + setStyleSheet(R"( + DriveStats { + background-color: #333333; + border-radius: 10px; + } + + QLabel[type="title"] { font-size: 51px; font-weight: 500; } + QLabel[type="number"] { font-size: 78px; font-weight: 500; } + QLabel[type="unit"] { font-size: 51px; font-weight: 300; color: #A0A0A0; } + )"); +} + +void DriveStats::updateStats() { + auto update = [=](const QJsonObject& obj, StatsLabels& labels) { + labels.routes->setText(QString::number((int)obj["routes"].toDouble())); + labels.distance->setText(QString::number(int(obj["distance"].toDouble() * (metric_ ? MILE_TO_KM : 1)))); + labels.distance_unit->setText(getDistanceUnit()); + labels.hours->setText(QString::number((int)(obj["minutes"].toDouble() / 60))); + }; + + QJsonObject json = stats_.object(); + update(json["all"].toObject(), all_); + update(json["week"].toObject(), week_); +} + +void DriveStats::parseResponse(const QString& response, bool success) { + if (!success) return; + + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); + if (doc.isNull()) { + qDebug() << "JSON Parse failed on getting past drives statistics"; + return; + } + stats_ = doc; + updateStats(); +} + +void DriveStats::showEvent(QShowEvent* event) { + bool metric = Params().getBool("IsMetric"); + if (metric_ != metric) { + metric_ = metric; + updateStats(); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h new file mode 100644 index 000000000..0b2802ed8 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/drive_stats.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + * + * This file is part of sunnypilot and is licensed under the MIT License. + * See the LICENSE.md file in the root directory for more details. + */ + +#pragma once + +#include +#include + +class DriveStats : public QFrame { + Q_OBJECT + +public: + explicit DriveStats(QWidget* parent = 0); + +private: + void showEvent(QShowEvent *event) override; + void updateStats(); + inline QString getDistanceUnit() const { return metric_ ? tr("KM") : tr("Miles"); } + + bool metric_; + QJsonDocument stats_; + struct StatsLabels { + QLabel *routes, *distance, *distance_unit, *hours; + } all_, week_; + +private slots: + void parseResponse(const QString &response, bool success); +}; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/prime.cc b/selfdrive/ui/sunnypilot/qt/widgets/prime.cc new file mode 100644 index 000000000..a1bb85c78 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/prime.cc @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + * + * This file is part of sunnypilot and is licensed under the MIT License. + * See the LICENSE.md file in the root directory for more details. + */ + +#include "selfdrive/ui/sunnypilot/qt/widgets/prime.h" + +SetupWidgetSP::SetupWidgetSP(QWidget *parent) : SetupWidget(parent) { + PrimeUserWidget *primeUser = new PrimeUserWidget; + content_layout->insertWidget(0, primeUser); + + primeUser->setVisible(uiState()->prime_state->isSubscribed()); + + QObject::connect(uiState()->prime_state, &PrimeState::changed, [=]() { + primeUser->setVisible(uiState()->prime_state->isSubscribed()); + }); +} diff --git a/selfdrive/ui/sunnypilot/qt/widgets/prime.h b/selfdrive/ui/sunnypilot/qt/widgets/prime.h new file mode 100644 index 000000000..598b0e62e --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/widgets/prime.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + * + * This file is part of sunnypilot and is licensed under the MIT License. + * See the LICENSE.md file in the root directory for more details. + */ + +#pragma once + +#include "selfdrive/ui/qt/widgets/prime.h" + +#include "selfdrive/ui/sunnypilot/ui.h" + +class SetupWidgetSP : public SetupWidget { + Q_OBJECT + +public: + explicit SetupWidgetSP(QWidget *parent = nullptr); +}; diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 244e96b42..50e5656d3 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -275,6 +275,33 @@ إقران + + DriveStats + + Drives + القيادة + + + Hours + ساعات + + + ALL TIME + كامل الوقت + + + PAST WEEK + الأسبوع الماضي + + + KM + كم + + + Miles + ميل + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index a41205d6d..fa068eefd 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -275,6 +275,33 @@ + + DriveStats + + Drives + Fahrten + + + Hours + Stunden + + + ALL TIME + Gesamtzeit + + + PAST WEEK + Letzte Woche + + + KM + KM + + + Miles + Meilen + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index f4780d717..0c1f38382 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -275,6 +275,33 @@ Desactivar para apagar + + DriveStats + + Drives + + + + Hours + + + + ALL TIME + + + + PAST WEEK + + + + KM + + + + Miles + + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 5c2472904..1715ce127 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -275,6 +275,33 @@ + + DriveStats + + Drives + Trajets + + + Hours + Heures + + + ALL TIME + DEPUIS TOUJOURS + + + PAST WEEK + CETTE SEMAINE + + + KM + KM + + + Miles + Miles + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 61fe372e3..a2264e659 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -275,6 +275,33 @@ + + DriveStats + + Drives + 運転履歴 + + + Hours + 時間 + + + ALL TIME + 累計 + + + PAST WEEK + 先週 + + + KM + km + + + Miles + マイル + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 6d6244fb7..951c146bf 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -275,6 +275,33 @@ 동기화 + + DriveStats + + Drives + 주행 + + + Hours + 시간 + + + ALL TIME + 전체 + + + PAST WEEK + 지난 주 + + + KM + km + + + Miles + 마일 + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 0bb217aa0..910ec2664 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -275,6 +275,33 @@ PAREAR + + DriveStats + + Drives + Dirigidas + + + Hours + Horas + + + ALL TIME + TOTAL + + + PAST WEEK + SEMANA PASSADA + + + KM + KM + + + Miles + Milhas + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 19f5c20dd..469e79349 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -275,6 +275,33 @@ จับคู่ + + DriveStats + + Drives + การขับขี่ + + + Hours + ชั่วโมง + + + ALL TIME + ทั้งหมด + + + PAST WEEK + สัปดาห์ที่ผ่านมา + + + KM + กิโลเมตร + + + Miles + ไมล์ + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index e5c3f3f45..48edc6c83 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -275,6 +275,33 @@ + + DriveStats + + Drives + Sürücüler + + + Hours + Saat + + + ALL TIME + TÜM ZAMANLAR + + + PAST WEEK + GEÇEN HAFTA + + + KM + KM + + + Miles + Mil + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 8b6b64d55..2ad90ae0a 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -275,6 +275,33 @@ 配对 + + DriveStats + + Drives + 旅程数 + + + Hours + 小时 + + + ALL TIME + 全部 + + + PAST WEEK + 过去一周 + + + KM + 公里 + + + Miles + 英里 + + DriverViewWindow diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 0515ac82a..3ea95f66c 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -275,6 +275,33 @@ 配對 + + DriveStats + + Drives + 旅程 + + + Hours + 小時 + + + ALL TIME + 總共 + + + PAST WEEK + 上週 + + + KM + 公里 + + + Miles + 英里 + + DriverViewWindow