From 2b12c38f50093d1999cb6120d5423b7081ac0769 Mon Sep 17 00:00:00 2001 From: RigoLigoRLC Date: Sun, 19 Sep 2021 20:22:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=94=E7=94=A8=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + docs/structure_and_naming.md | 9 ++ gui/page/spkpageappdetails.cpp | 182 +++++++++++++++++++++++ gui/page/spkpageapplist.cpp | 1 + gui/page/spkpageuitest.cpp | 13 ++ gui/spkappitem.cpp | 12 ++ gui/spkmainwindow.cpp | 133 ++++++++++++++++- gui/spkqsshelper.cpp | 5 +- gui/spkstretchlayout.cpp | 8 +- inc/page/spkpageappdetails.h | 55 +++++++ inc/page/spkpageapplist.h | 2 +- inc/page/spkpageuitest.h | 5 + inc/spkappitem.h | 6 + inc/spkmainwindow.h | 21 ++- inc/spkqsshelper.h | 1 + inc/spkutils.h | 1 + resource/stylesheets/mainwindow_dark.css | 24 ++- src/spkutils.cpp | 11 ++ 18 files changed, 473 insertions(+), 17 deletions(-) create mode 100644 gui/page/spkpageappdetails.cpp create mode 100644 inc/page/spkpageappdetails.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 814e0b7..803c8e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ set(SOURCE_FILES inc/page/spkpagebase.h gui/page/spkpagebase.cpp inc/page/spkpageuitest.h gui/page/spkpageuitest.cpp inc/page/spkpageapplist.h gui/page/spkpageapplist.cpp + inc/page/spkpageappdetails.h gui/page/spkpageappdetails.cpp inc/spkstore.h src/spkstore.cpp inc/spkuimsg.h src/spkuimsg.cpp diff --git a/docs/structure_and_naming.md b/docs/structure_and_naming.md index 4cafd97..70d0ac0 100644 --- a/docs/structure_and_naming.md +++ b/docs/structure_and_naming.md @@ -40,4 +40,13 @@ C++ 规范要求的和第三方库有较大差异的除外。例如,`SpkUiMess 如非第三方代码,一般以 `Spk` 前缀标明。 实现 UI 的类都应归于 `SpkUi` 命名空间。 +## 类定义 + +### 一般规则 + +Qt 类的 `Q_OBJECT` 应置于类定义最顶端。 +`public` ,`protected` 和 `private` 等标签中只应该包含一类成员, +如单个 `public` 标签内只能包含方法,或成员。 +信号、槽等不得与普通方法混合。 + \ No newline at end of file diff --git a/gui/page/spkpageappdetails.cpp b/gui/page/spkpageappdetails.cpp new file mode 100644 index 0000000..09929dc --- /dev/null +++ b/gui/page/spkpageappdetails.cpp @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-3.0-only + +#include "page/spkpageappdetails.h" +#include "spkutils.h" + + +namespace SpkUi +{ + constexpr QSize SpkPageAppDetails::IconSize; + + void SpkPageAppDetails::LoadAppResources(QString aPkgName, QString aIcon, QStringList aScreenshots, + QStringList aTags) + { + QPixmap pic; + + // Load icon + auto res = RES->RequestResource(0, aPkgName, SpkResource::ResourceType::AppIcon, aIcon); + if(res.status == SpkResource::ResourceStatus::Ready) + { + if(pic.loadFromData(res.data)) + mAppIcon->setPixmap(pic.scaled(IconSize, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + else + { + mAppIcon->setPixmap(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_)); + RES->PurgeCachedResource(aPkgName, SpkResource::ResourceType::AppIcon, 0); + } + } + + // Load screenshots + if(aScreenshots.isEmpty()) + return; + + int shotId = 0; + for(auto &i : aScreenshots) + { + shotId++; + res = RES->RequestResource(shotId, aPkgName, SpkResource::ResourceType::AppScreenshot, aIcon, + shotId); + if(res.status == SpkResource::ResourceStatus::Ready) + { + if(pic.loadFromData(res.data)) + ;// TODO + else + { + // TODO + // mAppIcon->setPixmap(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_)); + RES->PurgeCachedResource(aPkgName, SpkResource::ResourceType::AppScreenshot, 0); + } + } + } + + // TODO: tags + } + + SpkPageAppDetails::SpkPageAppDetails(QWidget *parent) : SpkPageBase(parent) + { + mMainArea = new QScrollArea; + mMainArea->setWidgetResizable(true); + mMainArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + mLay4MainArea = new QVBoxLayout(this); + mLay4MainArea->addWidget(mMainArea); + + mMainLay = new QVBoxLayout(mMainArea); + mMainLay->setSizeConstraint(QLayout::SetMinAndMaxSize); + + mAppIcon = new QLabel; + + mAppTitle = new QLabel; + mAppTitle->setObjectName("styDetTitle"); + + mAppDescription = new QLabel; + mAppDescription->setObjectName("styDetDesc"); + mAppDescription->setWordWrap(true); + mAppShortDesc = new QLabel; + mAppShortDesc->setObjectName("styDetDesc"); + mAppShortDesc->setWordWrap(true); + // NOTE: Seems Qt have trouble dealing with wrapped text here. Removing the following operations + // to mAppShortDesc will result in broken layout. Very possible that it's a Qt bug. + mAppShortDesc->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + mAppShortDesc->setMinimumWidth(100); + mVersion = new QLabel; + mPkgName = new QLabel; + mPkgName->setObjectName("styDetPkg"); + + mTitleLay = new QVBoxLayout; + mTitleLay->setAlignment(Qt::AlignTop); + mTitleLay->addWidget(mAppTitle); + mTitleLay->addWidget(mVersion); + mTitleLay->addWidget(mAppShortDesc); + mTitleLay->addWidget(mPkgName); + mTitleLay->setSpacing(0); + + mIconTitleLay = new QHBoxLayout; + mIconTitleLay->setAlignment(Qt::AlignLeft); + mIconTitleLay->addWidget(mAppIcon); + mIconTitleLay->addSpacing(15); + mIconTitleLay->addLayout(mTitleLay); + + mIconTitleWidget = new QWidget; + mIconTitleWidget->setLayout(mIconTitleLay); + + mAuthor = new SpkDetailEntry; + mAuthor->SetTitle(tr("Author")); + mContributor = new SpkDetailEntry; + mContributor->SetTitle(tr("Contributor")); + mSite = new SpkDetailEntry; + mSite->SetTitle(tr("Website")); + mArch = new SpkDetailEntry; + mArch->SetTitle(tr("Architecture")); + mSize = new SpkDetailEntry; + mSize->SetTitle(tr("Size")); + + mDetailLay = new SpkStretchLayout; + mDetailLay->setSpacing(12); + mDetailLay->addWidget(mAuthor); + mDetailLay->addWidget(mContributor); + mDetailLay->addWidget(mSize); + mDetailLay->addWidget(mArch); + mDetailLay->addWidget(mSite); + +// mDetailWidget = new QWidget; +// mDetailWidget->setLayout(mDetailLay); +// mDetailWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + mMainLay->setAlignment(Qt::AlignTop); + mMainLay->addWidget(mIconTitleWidget); + mMainLay->addLayout(mDetailLay); + mMainLay->addWidget(mAppDescription); +// mMainLay->addStretch(); + mWid4MainArea = new QWidget; + mWid4MainArea->setLayout(mMainLay); + + mMainArea->setWidget(mWid4MainArea); + } + + void SpkPageAppDetails::ResourceAcquisitionFinished(int id, ResourceResult result) + { + QPixmap icon; + if(!id) + { + // id == 0, icon + if(result.status == SpkResource::ResourceStatus::Ready) + { + if(icon.loadFromData(result.data)) + mAppIcon->setPixmap(icon.scaled(SpkAppItem::IconSize_, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + else + mAppIcon->setPixmap(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_)); + } + else if(result.status == SpkResource::ResourceStatus::Failed) + { + mAppIcon->setPixmap(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_)); + RES->PurgeCachedResource(mPkgName->text(), SpkResource::ResourceType::AppIcon, 0); + } + } + else + { + // TODO: screenshots + } + } + + void SpkPageAppDetails::Activated() + { + RES->Acquire(this, false); + } + + SpkDetailEntry::SpkDetailEntry(QWidget *parent) : QWidget(parent) + { + setLayout(&mLay); + mLay.addWidget(&mTitle); + mLay.addWidget(&mField); + mTitle.setAlignment(Qt::AlignLeft); + mField.setAlignment(Qt::AlignRight); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setMinimumWidth(300); + setAutoFillBackground(true); + } +} \ No newline at end of file diff --git a/gui/page/spkpageapplist.cpp b/gui/page/spkpageapplist.cpp index 75966e3..2f44bb5 100644 --- a/gui/page/spkpageapplist.cpp +++ b/gui/page/spkpageapplist.cpp @@ -53,6 +53,7 @@ namespace SpkUi auto item = new SpkAppItem(appId, this); auto id = mAppItemList.size(); + connect(item, &SpkAppItem::clicked, this, &SpkPageAppList::ApplicationClicked); item->SetTitle(name); item->SetDescription(description); item->setProperty("pkg_name", pkgName); diff --git a/gui/page/spkpageuitest.cpp b/gui/page/spkpageuitest.cpp index 7aa93a6..6a7dc4c 100644 --- a/gui/page/spkpageuitest.cpp +++ b/gui/page/spkpageuitest.cpp @@ -51,6 +51,18 @@ SpkUi::SpkPageUiTest::SpkPageUiTest(QWidget *parent) : QSplitter(parent) "Phasellus finibus risus id aliquam pulvinar."); AppItem->SetIcon(QIcon::fromTheme("dialog-information").pixmap(72, 72)); + Detail1 = new SpkDetailEntry; Detail1->SetTitle("Foo"); Detail1->SetValue("1"); + Detail2 = new SpkDetailEntry; Detail2->SetTitle("Foo"); Detail2->SetValue("1"); + Detail3 = new SpkDetailEntry; Detail3->SetTitle("Foo"); Detail3->SetValue("1"); + + DetailsLay = new SpkStretchLayout; + DetailsLay->addWidget(Detail1); + DetailsLay->addWidget(Detail2); + DetailsLay->addWidget(Detail3); + + DetailsWidget = new QWidget; + DetailsWidget->setLayout(DetailsLay); + PopupText = new QLineEdit(this); PopupText->setObjectName("spk_pg_qsstest_poptext"); PopupText->setText("Hello, world"); @@ -85,6 +97,7 @@ SpkUi::SpkPageUiTest::SpkPageUiTest(QWidget *parent) : QSplitter(parent) VLayTestWidgets->addWidget(ShowPopup); VLayTestWidgets->addWidget(ShowAbout); VLayTestWidgets->addWidget(AppItem); + VLayTestWidgets->addWidget(DetailsWidget); Group = new QGroupBox(this); Group->setObjectName("spk_pg_qsstest_groupbox"); diff --git a/gui/spkappitem.cpp b/gui/spkappitem.cpp index e3f806d..a7a5c92 100644 --- a/gui/spkappitem.cpp +++ b/gui/spkappitem.cpp @@ -50,3 +50,15 @@ void SpkAppItem::paintEvent(QPaintEvent *e) QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } + +void SpkAppItem::mousePressEvent(QMouseEvent *e) +{ + mPressCond = true; +} + +void SpkAppItem::mouseReleaseEvent(QMouseEvent *e) +{ + if(mPressCond) + emit clicked(mAppId); + mPressCond = false; +} diff --git a/gui/spkmainwindow.cpp b/gui/spkmainwindow.cpp index e9d834d..eea6fbf 100644 --- a/gui/spkmainwindow.cpp +++ b/gui/spkmainwindow.cpp @@ -157,7 +157,7 @@ void SpkMainWindow::PopulateAppList(QJsonObject appData, QString &&keyword) static auto err = [](){ sErr("Received invalid application list data!"); - SpkUiMessage::SendStoreNotification(tr("An invalid response was received. Please try again!")); + SpkUiMessage::SendStoreNotification(tr("Received an invalid response. Please try again!")); return; }; int pgCurrent, pgTotal, totalApps; @@ -178,7 +178,7 @@ void SpkMainWindow::PopulateAppList(QJsonObject appData, QString &&keyword) auto apps = appData.value("data").toArray(); - foreach(auto i, apps) + for(auto &&i : apps) { if(i.isObject()) { @@ -206,10 +206,112 @@ void SpkMainWindow::PopulateAppList(QJsonObject appData, QString &&keyword) } } +void SpkMainWindow::EnterAppDetails(int aAppId) +{ + using namespace SpkUtils; + VerifySingleRequest(mAppDetailsGetReply); + QJsonObject reqData; + QJsonDocument reqDoc; + reqData.insert("application_id", QJsonValue(aAppId)); + reqDoc.setObject(reqData); + mAppDetailsGetReply = STORE->SendApiRequest("application/get_application_detail", reqDoc); + DeleteReplyLater(mAppDetailsGetReply); + connect(mAppDetailsGetReply, &QNetworkReply::finished, + this, &SpkMainWindow::AppDetailsDataReceived); + setCursor(Qt::BusyCursor); +} + +void SpkMainWindow::AppDetailsDataReceived() +{ + QJsonValue retval; + if(!SpkUtils::VerifyReplyJson(mAppDetailsGetReply, retval) || !retval.isObject()) + { + sErrPop(tr("Failed to open app details page! Type of retval: %1.").arg(retval.type())); + return; + } + SwitchToPage(SpkUi::PgAppList); + PopulateAppDetails(retval.toObject()); + setCursor(Qt::ArrowCursor); +} + +void SpkMainWindow::PopulateAppDetails(QJsonObject appDetails) +{ + QString pkgName, author, contributor, site, iconPath, arch, version, details, shortDesc, name; + QStringList screenshots, tags; + int packageSize; + static auto err = + [](){ + sErr("Received invalid application details!"); + SpkUiMessage::SendStoreNotification(tr("Received an invalid response. Please try again!")); + return; + }; + + if(appDetails.contains("package") && appDetails.value("package").isString()) + pkgName = appDetails.value("package").toString(); + else return err(); + if(appDetails.contains("application_name_zh") && appDetails.value("application_name_zh").isString()) + name = appDetails.value("application_name_zh").toString(); + else name = pkgName; + if(appDetails.contains("version") && appDetails.value("version").isString()) + version = appDetails.value("version").toString(); + else return err(); + if(appDetails.contains("icons") && appDetails.value("icons").isString()) + iconPath= appDetails.value("icons").toString(); + if(appDetails.contains("author") && appDetails.value("author").isString()) + author = appDetails.value("author").toString(); + if(appDetails.contains("contributor") && appDetails.value("contributor").isString()) + contributor = appDetails.value("contributor").toString(); + if(appDetails.contains("website") && appDetails.value("website").isString()) + site = appDetails.value("website").toString(); + if(appDetails.contains("description") && appDetails.value("description").isString()) + shortDesc = appDetails.value("description").toString(); + if(appDetails.contains("more") && appDetails.value("more").isString()) + details = appDetails.value("more").toString(); + if(appDetails.contains("arch") && appDetails.value("arch").isString()) + arch = appDetails.value("arch").toString(); + if(appDetails.contains("size") && appDetails.value("size").isDouble()) + packageSize = appDetails.value("size").toInt(); + + QJsonArray imgs; + if(appDetails.contains("img_urls") && appDetails.value("img_urls").isArray()) + imgs = appDetails.value("img_urls").toArray(); + if(!imgs.isEmpty()) + for(auto &&i : imgs) + if(i.isString()) screenshots << i.toString(); + + QJsonArray tags_j; + if(appDetails.contains("tags") && appDetails.value("tags").isArray()) + imgs = appDetails.value("tags").toArray(); + if(!tags_j.isEmpty()) + for(auto &&i : tags_j) + if(i.isString()) tags << i.toString(); + + // Details string has a strangely appended LF. IDK but still should remove it. + shortDesc = shortDesc.trimmed(); + + auto w = ui->PageAppDetails; + w->mPkgName->setText(pkgName); + w->mAppTitle->setText(name); + w->mAppShortDesc->setText(shortDesc); + w->mAppDescription->setText(details); + w->mAuthor->SetValue(author); + w->mContributor->SetValue(contributor); + w->mSite->SetValue(site); + w->mArch->SetValue(arch); + w->mSize->SetValue(SpkUtils::BytesToSize(packageSize)); + w->mVersion->setText(version); + SwitchToPage(SpkUi::PgAppDetails); + ui->AppDetailsItem->setHidden(false); + ui->CategoryWidget->setCurrentItem(ui->AppDetailsItem); + w->LoadAppResources(pkgName, iconPath, screenshots, tags); +} + // ==================== Main Window Initialization ==================== void SpkMainWindow::Initialize() { + connect(ui->SidebarMgr, &SpkUi::SpkSidebarSelector::SwitchToPage, + this, &SpkMainWindow::SwitchToPage); connect(ui->SidebarMgr, &SpkUi::SpkSidebarSelector::SwitchToCategory, this, &SpkMainWindow::EnterCategoryList); connect(ui->PageAppList, &SpkUi::SpkPageAppList::SwitchListPage, @@ -218,6 +320,8 @@ void SpkMainWindow::Initialize() this, &SpkMainWindow::SearchKeyword); connect(ui->SearchEdit, &QLineEdit::returnPressed, [=](){ emit SearchKeyword(ui->SearchEdit->text(), 1); }); + connect(ui->PageAppList, &SpkUi::SpkPageAppList::ApplicationClicked, + this, &SpkMainWindow::EnterAppDetails); } // ==================== Main Widget Initialization ==================== @@ -292,11 +396,28 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent) CategoryWidget->setColumnCount(1); CategoryWidget->setHeaderHidden(true); CategoryWidget->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + + //============ Sidebar entries BEGIN ============ + AppDetailsItem = new QTreeWidgetItem(QStringList(tr("App Details"))); + AppDetailsItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false); + AppDetailsItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgAppDetails); CategoryParentItem = new QTreeWidgetItem(QStringList(tr("Categories"))); CategoryParentItem->setFlags(CategoryParentItem->flags().setFlag(Qt::ItemIsSelectable, false)); - CategoryParentItem->setExpanded(true); +#ifndef NDEBUG + UiTestItem = new QTreeWidgetItem(QStringList(tr("UI TEST"))); + UiTestItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false); + UiTestItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgQssTest); +#endif + //============ Sidebar entries END ============ + SidebarMgr->AddUnusableItem(CategoryParentItem); + CategoryWidget->addTopLevelItem(AppDetailsItem); CategoryWidget->addTopLevelItem(CategoryParentItem); + CategoryWidget->addTopLevelItem(UiTestItem); + + // Must be done after added into a view. + AppDetailsItem->setHidden(true); // Hide until we actually open up a Details page + CategoryParentItem->setExpanded(true); // FIXMEIFPOSSIBLE: Fusion adds extra gradient. // Details: https://forum.qt.io/topic/128190/fusion-style-kept-adding-an-extra- @@ -355,7 +476,7 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent) //============ Pages ============= - // Red-Black tree based map will be able to sort things + // Red-Black tree based map will be able to sort things. Just for convenience of ordering pages. QMap sorter; // Initialize pages @@ -363,6 +484,10 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent) PageAppList->setProperty("spk_pageid", SpkStackedPages::PgAppList); sorter[PgAppList] = PageAppList; + PageAppDetails = new SpkUi::SpkPageAppDetails(this); + PageAppDetails->setProperty("spk_pageid", SpkStackedPages::PgAppDetails); + sorter[PgAppDetails] = PageAppDetails; + #ifndef NDEBUG // If only in debug mode should we initialize QSS test page PageQssTest = new SpkUi::SpkPageUiTest(this); PageQssTest->setProperty("spk_pageid", SpkStackedPages::PgQssTest); diff --git a/gui/spkqsshelper.cpp b/gui/spkqsshelper.cpp index a675919..9a5177a 100644 --- a/gui/spkqsshelper.cpp +++ b/gui/spkqsshelper.cpp @@ -26,7 +26,8 @@ const std::map SpkUi::Qss::ColorSet2Tok { TextOnSelection, "TXACC" }, { TextOnGlobalBgnd, "TXGBG" }, { TextOnControlsBgnd, "TXCBG" }, - { TextLighter, "TXL" }, + { TextLighter, "TXL1" }, + { TextEvenLighter, "TXL2" }, { TextDisabled, "TXD" }, { GlossyEdge, "GLS" }, { ShadesEdge, "SHD" } @@ -51,6 +52,7 @@ const std::map SpkUi::Qss::DarkColorSet { TextOnGlobalBgnd, ColorTextOnBackground(0x282828) }, { TextOnControlsBgnd, ColorTextOnBackground(0x282828) }, { TextLighter, 0xd5d5d5 }, + { TextEvenLighter, 0x505050 }, { TextDisabled, 0xbebebe }, { GlossyEdge, 0x656565 }, { ShadesEdge, 0x7b7b7b } @@ -75,6 +77,7 @@ const std::map SpkUi::Qss::LightColorSet { TextOnGlobalBgnd, ColorTextOnBackground(0xf8f8f8) }, { TextOnControlsBgnd, ColorTextOnBackground(0xf8f8f8) }, { TextLighter, 0x2a2a2a }, + { TextEvenLighter, 0xa0a0a0 }, { TextDisabled, 0x8a8a8a }, { GlossyEdge, 0xc5c5c5 }, { ShadesEdge, 0x9d9d9d } diff --git a/gui/spkstretchlayout.cpp b/gui/spkstretchlayout.cpp index fff292d..035eae2 100644 --- a/gui/spkstretchlayout.cpp +++ b/gui/spkstretchlayout.cpp @@ -21,7 +21,7 @@ QSize SpkStretchLayout::sizeHint() const { if(mItems.isEmpty()) return { 300, 300 }; - auto w = geometry().width(); + auto w = geometry().width() - spacing(); auto it = mItems.first(); int countPerLine = w / (it->minimumSize().width() + spacing()); int lines = ceil((double)mItems.size() / countPerLine); @@ -75,14 +75,16 @@ void SpkStretchLayout::setGeometry(const QRect &rect) else // Stretch items. size = QSize {(w / countPerLine - spc), itm->maximumSize().height() }; + auto origin = geometry().topLeft(); + QLayoutItem *o; for(int i = 0; i < mItems.size(); i++) { o = mItems.at(i); QRect geo; geo.setSize(size); - geo.moveTo((i % countPerLine) * (size.width() + spc) + spc, - (i / countPerLine) * (size.height() + spacing()) + spc); + geo.moveTo((i % countPerLine) * (size.width() + spc) + spc + origin.x(), + (i / countPerLine) * (size.height() + spacing()) + spc + origin.y()); o->setGeometry(geo); } } diff --git a/inc/page/spkpageappdetails.h b/inc/page/spkpageappdetails.h new file mode 100644 index 0000000..197b900 --- /dev/null +++ b/inc/page/spkpageappdetails.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include "page/spkpagebase.h" +#include "spkstretchlayout.h" + +namespace SpkUi +{ + class SpkDetailEntry; + + class SpkPageAppDetails : public SpkPageBase + { + Q_OBJECT + public: + SpkPageAppDetails(QWidget *parent = nullptr); + + void LoadAppResources(QString pkgName, QString icon, QStringList screenshots, QStringList tags); + + private: + + public slots: + void ResourceAcquisitionFinished(int id, ResourceResult result); + void Activated(); + + public: + static constexpr QSize IconSize { 144, 144 }; + + QScrollArea *mMainArea; + QWidget *mDetailWidget, *mIconTitleWidget, *mWid4MainArea; + QLabel *mAppTitle, *mAppIcon, *mAppDescription, *mAppShortDesc, *mPkgName, *mVersion; + SpkDetailEntry *mAuthor, *mContributor, *mSite, *mArch, *mSize; + SpkStretchLayout *mDetailLay; + QVBoxLayout *mMainLay, *mTitleLay, *mLay4MainArea; + QHBoxLayout *mIconTitleLay; + + }; + + class SpkDetailEntry : public QWidget + { + Q_OBJECT + public: + SpkDetailEntry(QWidget *parent = nullptr); + void SetTitle(const QString &s) { mTitle.setText(s); } + void SetValue(const QString &s) { mField.setText(s); } + + private: + QLabel mTitle, mField; + QHBoxLayout mLay; + }; +} \ No newline at end of file diff --git a/inc/page/spkpageapplist.h b/inc/page/spkpageapplist.h index c0df7f6..1c25b97 100644 --- a/inc/page/spkpageapplist.h +++ b/inc/page/spkpageapplist.h @@ -43,7 +43,7 @@ namespace SpkUi QString mKeyword; signals: - void ApplicationClicked(QString name, QString pkgName); + void ApplicationClicked(int appId); void SwitchListPage(int categoryId, int page); void SwitchSearchPage(QString keyword, int page); diff --git a/inc/page/spkpageuitest.h b/inc/page/spkpageuitest.h index ea7cbcd..06faee8 100644 --- a/inc/page/spkpageuitest.h +++ b/inc/page/spkpageuitest.h @@ -12,6 +12,8 @@ #include #include #include "spkappitem.h" +#include "spkstretchlayout.h" +#include "page/spkpageappdetails.h" #include "spkloading.h" @@ -40,6 +42,9 @@ namespace SpkUi QSlider *SlideV; SpkLoading *Loading; SpkAppItem *AppItem; + SpkStretchLayout *DetailsLay; + SpkDetailEntry *Detail1, *Detail2, *Detail3; + QWidget *DetailsWidget; QLineEdit *PopupText; QPushButton *ShowPopup, diff --git a/inc/spkappitem.h b/inc/spkappitem.h index 74e733d..134ed24 100644 --- a/inc/spkappitem.h +++ b/inc/spkappitem.h @@ -18,6 +18,8 @@ class SpkAppItem : public QWidget protected: void paintEvent(QPaintEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); public: static constexpr int IconSize = 72; @@ -29,7 +31,11 @@ class SpkAppItem : public QWidget ElidedLabel *mDescription; int mAppId; + bool mPressCond; + QVBoxLayout *mLayText; QHBoxLayout *mMainLay; + signals: + void clicked(int); }; diff --git a/inc/spkmainwindow.h b/inc/spkmainwindow.h index 8cee22d..cdbf8b1 100644 --- a/inc/spkmainwindow.h +++ b/inc/spkmainwindow.h @@ -16,6 +16,7 @@ #include "spkfocuslineedit.h" #include "page/spkpageuitest.h" #include "page/spkpageapplist.h" +#include "page/spkpageappdetails.h" class QNetworkReply; @@ -25,6 +26,7 @@ namespace SpkUi { PgInvalid = -1, PgAppList, + PgAppDetails, PgQssTest // Must be at last }; @@ -86,7 +88,7 @@ namespace SpkUi mLastSelectedItem = nullptr; } mLastCheckedBtn = b; - emit SwitchToPage(b->property("spk_pageno").toInt()); + emit SwitchToPage(static_cast(b->property("spk_pageno").toInt())); } void TreeItemSelected(QTreeWidgetItem *item, int column) { @@ -103,7 +105,8 @@ namespace SpkUi if(item->data(column, RoleItemIsCategory).toBool()) emit SwitchToCategory(item->data(column, RoleItemCategoryPageId).toInt(), 0); else - emit SwitchToPage(item->data(column, RoleItemCategoryPageId).toInt()); + emit SwitchToPage(static_cast( + item->data(column, RoleItemCategoryPageId).toInt())); } void UnusableItemSelected(QTreeWidgetItem *i) { @@ -120,7 +123,7 @@ namespace SpkUi signals: void SwitchToCategory(int aCategoryId, int aPage); - void SwitchToPage(int aPageId); + void SwitchToPage(SpkStackedPages aPageId); }; class SpkMainWidget : public QFrame @@ -146,7 +149,9 @@ namespace SpkUi QMap *CategoryItemMap; SpkSidebarSelector *SidebarMgr; - QTreeWidgetItem *CategoryParentItem; + QTreeWidgetItem *CategoryParentItem, + *AppDetailsItem, + *UiTestItem; // Title bar search bar SpkFocusLineEdit *SearchEdit; @@ -156,6 +161,7 @@ namespace SpkUi //Pages SpkPageUiTest *PageQssTest; SpkPageAppList *PageAppList; + SpkPageAppDetails *PageAppDetails; }; } @@ -175,7 +181,8 @@ class SpkMainWindow : public SpkWindow private: QPointer mCategoryGetReply, - mCategoryAppListGetReply; + mCategoryAppListGetReply, + mAppDetailsGetReply; SpkUi::SpkStackedPages mCurrentPage = SpkUi::PgInvalid; public slots: @@ -191,7 +198,11 @@ class SpkMainWindow : public SpkWindow // Search a keyword (and switch pages) void SearchKeyword(QString aKeyword, int aPage); void SearchDataReceived(); + // Enter the details page of an application (and switch pages) + void EnterAppDetails(int aAppId); + void AppDetailsDataReceived(); private: void PopulateAppList(QJsonObject appData, QString &&keyword); + void PopulateAppDetails(QJsonObject appDetails); }; diff --git a/inc/spkqsshelper.h b/inc/spkqsshelper.h index d89a938..8ea854a 100644 --- a/inc/spkqsshelper.h +++ b/inc/spkqsshelper.h @@ -30,6 +30,7 @@ namespace SpkUi TextOnGlobalBgnd, TextOnControlsBgnd, TextLighter, + TextEvenLighter, TextDisabled, GlossyEdge, ShadesEdge, diff --git a/inc/spkutils.h b/inc/spkutils.h index 9140fa1..60e7b6e 100644 --- a/inc/spkutils.h +++ b/inc/spkutils.h @@ -26,4 +26,5 @@ namespace SpkUtils QString CutFileName(QString); QString CutPath(QString); + QString BytesToSize(size_t s, int prec = 2); } diff --git a/resource/stylesheets/mainwindow_dark.css b/resource/stylesheets/mainwindow_dark.css index 815b1bd..f371f51 100644 --- a/resource/stylesheets/mainwindow_dark.css +++ b/resource/stylesheets/mainwindow_dark.css @@ -14,7 +14,8 @@ TXACC: Text on Selection/Activation TXGBG: Text on Global background TXCBG: Text on controls background - TXL: Text slightly lighter + TXL1: Text slightly lighter + TXL2: Text lighter even more TXD: Text disabled GLS: Glossy edge on controls SHD: Shades edge on controls @@ -106,7 +107,7 @@ ElidedLabel #styAboutDesc { - color: TXL + color: TXL1 } SpkAppItem @@ -126,12 +127,29 @@ SpkAppItem::hover font-size: 17px; } +#styDetTitle +{ + font-weight: 600; + font-size: 36px; +} + #styAppItmDesc { - color: TXL; + color: TXL1; font-size: 12px; } +#styDetDesc +{ + color: TXL1; + font-size: 14px; +} + +#styDetPkg +{ + color: TXL2 +} + #styChkBtn { border-width: 0px; diff --git a/src/spkutils.cpp b/src/spkutils.cpp index 5638e49..060a159 100644 --- a/src/spkutils.cpp +++ b/src/spkutils.cpp @@ -88,3 +88,14 @@ QString SpkUtils::CutPath(QString path) { return path.section('/', 1, -2, QString::SectionIncludeLeadingSep); } + +QString SpkUtils::BytesToSize(size_t s, int prec) +{ + if(s > (1 << 30)) + return QString::number(double (s) / (1 << 30), 'f', prec) + " GB"; + if(s > (1 << 20)) + return QString::number(double (s) / (1 << 20), 'f', prec) + " MB"; + if(s > (1 << 10)) + return QString::number(double (s) / (1 << 10), 'f', prec) + " KB"; + return QString::number(s) + " B"; +}