下载页面雏形,修复SpkPopup(改用弹出),添加应用列表固定图标缓存

This commit is contained in:
RigoLigoRLC 2021-12-12 00:25:19 +08:00
parent f5a31affff
commit 179e57b9b5
19 changed files with 354 additions and 26 deletions

@ -69,6 +69,7 @@ set(SOURCE_FILES
inc/spkloading.h gui/spkloading.cpp
inc/spksidebartree.h gui/spksidebartree.cpp
inc/spkappitem.h gui/spkappitem.cpp
inc/spkdownloadentry.h gui/spkdownloadentry.cpp
inc/spkpopup.h gui/spkpopup.cpp
inc/spkstretchlayout.h gui/spkstretchlayout.cpp
inc/spkfocuslineedit.h
@ -77,6 +78,7 @@ set(SOURCE_FILES
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/page/spkpagedownloads.h gui/page/spkpagedownloads.cpp
inc/spkstore.h src/spkstore.cpp
inc/spkuimsg.h src/spkuimsg.cpp

@ -2,6 +2,7 @@
#include "page/spkpageappdetails.h"
#include "spkutils.h"
#include "spkappitem.h"
namespace SpkUi
{
@ -112,8 +113,8 @@ namespace SpkUi
mAuthor->SetTitle(tr("Author"));
mContributor = new SpkDetailEntry;
mContributor->SetTitle(tr("Contributor"));
mSite = new SpkDetailEntry;
mSite->SetTitle(tr("Website"));
// mSite = new SpkDetailEntry;
// mSite->SetTitle(tr("Website"));
mArch = new SpkDetailEntry;
mArch->SetTitle(tr("Architecture"));
mSize = new SpkDetailEntry;

@ -7,6 +7,9 @@ namespace SpkUi
{
SpkPageAppList::SpkPageAppList(QWidget *parent) : SpkPageBase(parent)
{
mLoadingIcon = new QPixmap(QIcon(":/icons/loading-icon.svg").pixmap(SpkAppItem::IconSize_));
mBrokenIcon = new QPixmap(QIcon(":/icons/broken-icon.svg").pixmap(SpkAppItem::IconSize_));
mAppsWidget = new QWidget;
mAppsArea = new QScrollArea(this);
mMainLay = new QVBoxLayout(this);
@ -63,6 +66,7 @@ namespace SpkUi
auto iconRes = RES->RequestResource(id, pkgName, SpkResource::ResourceType::AppIcon,
iconUrl, 0);
// TODO: cache scaled icons
QPixmap icon;
if(iconRes.status == SpkResource::ResourceStatus::Ready)
{
@ -72,13 +76,13 @@ namespace SpkUi
Qt::SmoothTransformation));
else
{
item->SetIcon(QIcon(":/icons/broken-icon.svg").pixmap(SpkAppItem::IconSize_));
item->SetIcon(*mBrokenIcon);
RES->PurgeCachedResource(pkgName, SpkResource::ResourceType::AppIcon, 0);
}
}
//TODO: prepare icons for loading entries
// else
// item->SetIcon(QPixmap(":/icons/loading_icon.svg").scaled(SpkAppItem::IconSize_));
//TODO: [TEST] prepare icons for loading entries
else
item->SetIcon(*mLoadingIcon);
mAppItemList.append(item);
mItemLay->addWidget(item);
@ -110,11 +114,11 @@ namespace SpkUi
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
else
item->SetIcon(QIcon(":/icons/broken-icon.svg").pixmap(SpkAppItem::IconSize_));
item->SetIcon(*mBrokenIcon);
}
else if(result.status == SpkResource::ResourceStatus::Failed)
{
item->SetIcon(QIcon(":/icons/broken-icon.svg").pixmap(SpkAppItem::IconSize_));
item->SetIcon(*mBrokenIcon);
RES->PurgeCachedResource(item->property("pkg_name").toString(),
SpkResource::ResourceType::AppIcon, 0);
}

@ -0,0 +1,29 @@
#include "page/spkpagedownloads.h"
#include "spkutils.h"
SpkUi::SpkPageDownloads::SpkPageDownloads(QWidget *parent) :
SpkPageBase(parent)
{
mMainLay = new QVBoxLayout(this);
mLayEntries = new QVBoxLayout;
mScrollWidget = new QWidget;
mScrollArea = new QScrollArea(this);
mScrollWidget->setLayout(mLayEntries);
mScrollArea->setWidget(mScrollWidget);
mScrollArea->setWidgetResizable(true);
mMainLay->addWidget(mScrollArea);
setLayout(mMainLay);
}
SpkUi::SpkPageDownloads::~SpkPageDownloads()
{
// TODO
}
void SpkUi::SpkPageDownloads::DownloadProgress(qint64 downloadedBytes, qint64 totalBytes, int id)
{
// TODO
}

111
gui/spkdownloadentry.cpp Normal file

@ -0,0 +1,111 @@

#include "spkdownloadentry.h"
#include "spklogging.h"
constexpr QSize SpkDownloadEntry::IconSize;
SpkDownloadEntry::SpkDownloadEntry(QWidget *parent)
{
mIcon = new QLabel;
mAppName = new ElidedLabel;
mMessage = new QLabel;
mProgress = new QProgressBar;
mLoading = new SpkLoading;
mBtnDelete = new QPushButton;
mBtnActions = new QPushButton;
mLayInfo = new QVBoxLayout;
mLayMsgs = new QHBoxLayout;
mLayMain = new QHBoxLayout;
mLoading->setVisible(false);
mIcon->setFixedSize(IconSize);
mProgress->setRange(0, 1000);
mLayMsgs->addWidget(mAppName);
mLayMsgs->addStretch();
mLayMsgs->addWidget(mMessage);
mLayInfo->addLayout(mLayMsgs);
mLayInfo->addWidget(mProgress);
mLayInfo->setAlignment(Qt::AlignVCenter);
mLayMain->addWidget(mIcon);
mLayMain->addLayout(mLayInfo);
mLayMain->addWidget(mLoading);
mLayMain->addWidget(mBtnActions);
mLayMain->addWidget(mBtnDelete);
setLayout(mLayMain);
}
SpkDownloadEntry::~SpkDownloadEntry()
{
// TODO
}
void SpkDownloadEntry::SetBasicInfo(QString name, QPixmap icon)
{
mAppName->setText(name);
mIcon->setPixmap(icon.scaled(IconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
void SpkDownloadEntry::SetStatus(DownloadEntryStatus status)
{
switch(status)
{
case Waiting:
mMessage->setText(tr("Waiting for download"));
mProgress->setVisible(false);
mBtnActions->setVisible(false);
mBtnDelete->setVisible(true);
break;
case Downloading:
mMessage->setText(tr(""));
mProgress->setVisible(true);
mBtnActions->setVisible(true);
break;
case Paused:
mMessage->setText(tr("Paused"));
break;
case Failed:
mMessage->setText(tr("Download Failed"));
mProgress->setVisible(false);
break;
case ToBeInstalled:
mMessage->setText(tr("Download Finished"));
mProgress->setVisible(false);
break;
case Installing:
mMessage->setText("");
mProgress->setVisible(false);
mLoading->setVisible(true);
mLoading->Begin();
case Installed:
mMessage->setText(tr("Installed"));
mLoading->End();
mLoading->setVisible(false);
break;
case InstallFailed:
mMessage->setText(tr("Install Failed"));
mLoading->End();
mLoading->setVisible(false);
break;
case Invalid:
break;
}
}
void SpkDownloadEntry::SetProgress(int p)
{
mProgress->setValue(p);
}

@ -435,8 +435,13 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
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));
DownloadsItem = new QTreeWidgetItem(QStringList(tr("Downloads")));
DownloadsItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false);
DownloadsItem->setData(0, SpkSidebarSelector::RoleItemCategoryPageId, SpkStackedPages::PgDownloads);
#ifndef NDEBUG
UiTestItem = new QTreeWidgetItem(QStringList(tr("UI TEST")));
UiTestItem->setData(0, SpkSidebarSelector::RoleItemIsCategory, false);
@ -525,6 +530,10 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
PageAppDetails->setProperty("spk_pageid", SpkStackedPages::PgAppDetails);
sorter[PgAppDetails] = PageAppDetails;
PageDownloads = new SpkUi::SpkPageDownloads(this);
PageDownloads->setProperty("spk_pageid", SpkStackedPages::PgDownloads);
sorter[PgDownloads] = PageDownloads;
#ifndef NDEBUG // If only in debug mode should we initialize QSS test page
PageQssTest = new SpkUi::SpkPageUiTest(this);
PageQssTest->setProperty("spk_pageid", SpkStackedPages::PgQssTest);

@ -4,6 +4,7 @@
#include <QGuiApplication>
#include <QScreen>
#include "spkui_general.h"
#include "spkmainwindow.h"
#include "spkmsgbox.h"
#include "spkstore.h"

@ -1,13 +1,14 @@
#include "spkmainwindow.h"
#include "spkpopup.h"
#include <QDebug>
namespace SpkUi
{
SpkPopup::SpkPopup(QWidget *parent, int aMillis) : QWidget(parent)
{
setAttribute(Qt::WA_TransparentForMouseEvents);
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
setWindowFlags(Qt::FramelessWindowHint);
mText = new QLabel();
mText->setStyleSheet("border-radius: 11px;"
"background-color: rgba(0,0,0,150);"
@ -22,19 +23,33 @@ namespace SpkUi
// Qt::WA_TranslucentBackground will cause the entire background of QLabel transparent.
// Therefore we need a container (SpkPopup) with a transparent background as the canvas layer
// of the actual displayed text.
mAnimFadeIn = new QPropertyAnimation(this, "windowOpacity");
mAnimFadeOut = new QPropertyAnimation(this, "windowOpacity");
mAnim = new QSequentialAnimationGroup(this);
mAnimFadeIn->setStartValue(0);
mAnimFadeIn->setEndValue(1);
// Disabled as translucency doesn't work well on every platform
// mAnimFadeIn = new QPropertyAnimation(this, "windowOpacity");
// mAnimFadeOut = new QPropertyAnimation(this, "windowOpacity");
// mAnimFadeIn->setStartValue(0.0);
// mAnimFadeIn->setEndValue(1.0);
// mAnimFadeOut->setStartValue(1.0);
// mAnimFadeOut->setEndValue(0.0);
// Using moving animation instead
mAnimFadeIn = new QPropertyAnimation(this, "pos");
mAnimFadeOut = new QPropertyAnimation(this, "pos");
mAnimFadeIn->setDuration(250);
mAnimFadeOut->setStartValue(1);
mAnimFadeOut->setEndValue(0);
mAnimFadeOut->setDuration(250);
mAnimFadeIn->setEasingCurve(QEasingCurve::InQuad);
mAnimFadeOut->setEasingCurve(QEasingCurve::InQuad);
mAnim->addAnimation(mAnimFadeIn);
mAnim->addPause(aMillis);
mAnim->addAnimation(mAnimFadeOut);
setVisible(false);
connect(mAnim, &QAnimationGroup::stateChanged,
[=](QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
{
qDebug() << "OldState" << oldState << "NewState" << newState;
});
}
void SpkPopup::Show(QString aText)
@ -44,11 +59,22 @@ namespace SpkUi
QSize parentSize = parentWidget()->size();
mText->setText(aText);
adjustSize();
move(QPoint((parentSize.width() - width()) / 2, parentSize.height() - height() - 30) +
parentWidget()->pos());
move(QPoint((parentSize.width() - width()) / 2, parentSize.height() - height() - 30)/* +
parentWidget()->pos()*/);
setMaximumWidth(parentSize.width() - 200);
setWindowOpacity(1);
show();
mAnimFadeIn->setStartValue(QPoint((parentSize.width() - width()) / 2,
parentSize.height() + height()));
mAnimFadeIn->setEndValue(QPoint((parentSize.width() - width()) / 2,
parentSize.height() - height() - 80));
mAnimFadeOut->setStartValue(QPoint((parentSize.width() - width()) / 2,
parentSize.height() - height() - 80));
mAnimFadeOut->setEndValue(QPoint((parentSize.width() - width()) / 2,
parentSize.height() + height()));
qDebug() << "Popup size " << size() << "position" << pos() << "parent size" << parentWidget()->size();
mAnim->start();
}
}

@ -37,6 +37,10 @@ namespace SpkUi
SpkStretchLayout *mItemLay;
QList<SpkAppItem *> mAppItemList;
// Cached icons
QPixmap *mLoadingIcon,
*mBrokenIcon;
QIntValidator *mPageValidator;
int mCategoryId, mCurrentPage;

@ -2,6 +2,7 @@
#pragma once
#include "spkstore.h"
#include <QTimer>
/**
* @note SpkDownloadMgr does NOT do download scheduling and other things; it's only a multithreaded
@ -68,6 +69,8 @@ class SpkDownloadMgr : public QObject
QFile mDestFile;
QString mDestFolder, mCurrentRemotePath;
RemoteFileInfo mCurrentRemoteFileInfo;
QTimer mProgressEmitterTimer;
qint64 mDownloadedBytes;
int mCurrentDownloadId;
int mActiveWorkerCount;
@ -88,6 +91,7 @@ class SpkDownloadMgr : public QObject
private slots:
void WorkerFinish();
void WorkerDownloadProgress(); ///< Be connected to ***QNetworkReply::readyRead***
void ProgressTimer();
private:
void LinkReplyWithMe(QNetworkReply*);
@ -95,7 +99,7 @@ class SpkDownloadMgr : public QObject
void TryScheduleFailureRetries(int i); ///< Try schedule on a specific task slot.
signals:
void DownloadProgressed(qint64 bytes, qint64 total);
void DownloadProgressed(qint64 bytes, qint64 total, int id);
void DownloadStopped(TaskResult status, int id);

52
inc/spkdownloadentry.h Normal file

@ -0,0 +1,52 @@
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include "qt/elidedlabel.h"
#include "spkloading.h"
#include <QProgressBar>
#include <QHBoxLayout>
#include <QVBoxLayout>
class SpkDownloadEntry : public QWidget
{
Q_OBJECT
public:
explicit SpkDownloadEntry(QWidget* parent = nullptr);
~SpkDownloadEntry();
static constexpr QSize IconSize { 64, 64 };
enum DownloadEntryStatus
{
Invalid = -1,
Waiting,
Downloading,
Paused,
Failed,
ToBeInstalled,
Installing,
Installed,
InstallFailed
};
public slots:
void SetBasicInfo(QString name, QPixmap icon);
void SetStatus(DownloadEntryStatus status);
void SetProgress(int);
private:
QLabel *mIcon, *mMessage;
ElidedLabel *mAppName;
QProgressBar *mProgress;
QPushButton *mBtnDelete,
*mBtnActions; // Actions include Retry Pause Install etc, one status at a time
SpkLoading *mLoading;
QHBoxLayout *mLayMsgs, *mLayMain;
QVBoxLayout *mLayInfo;
};

@ -18,6 +18,7 @@
#include "page/spkpageuitest.h"
#include "page/spkpageapplist.h"
#include "page/spkpageappdetails.h"
#include "page/spkpagedownloads.h"
class QNetworkReply;
@ -28,6 +29,7 @@ namespace SpkUi
PgInvalid = -1,
PgAppList,
PgAppDetails,
PgDownloads,
PgQssTest // Must be at last
};
@ -152,6 +154,7 @@ namespace SpkUi
QTreeWidgetItem *CategoryParentItem,
*AppDetailsItem,
*DownloadsItem,
*UiTestItem;
// Title bar search bar
@ -163,6 +166,7 @@ namespace SpkUi
SpkPageUiTest *PageQssTest;
SpkPageAppList *PageAppList;
SpkPageAppDetails *PageAppDetails;
SpkPageDownloads *PageDownloads;
};
}

@ -45,6 +45,8 @@ class SpkResource : public QObject
inline QString GetCachePath(const ResourceTask &task);
ResourceResult CacheLookup(QString pkgName, ResourceType type, QVariant info);
/**
* @brief When the resource context was changed, the new context needs to acquire the resource
* manager, so the resource manager can download resource for the new context.

@ -8,9 +8,10 @@
#include <QtNetwork/QNetworkAccessManager>
#include "spklogging.h"
#include "spkmainwindow.h"
#include "spkresource.h"
class SpkMainWindow;
/**
* @brief SpkStore class is the core of the store client side program, it is constructed first and
* handling all processing after the launch. All client side data should be held by it,

@ -15,5 +15,6 @@
<file>icons/settings.svg</file>
<file>icons/daynight-dark.svg</file>
<file>icons/daynight.svg</file>
<file>icons/loading-icon.svg</file>
</qresource>
</RCC>

@ -1,7 +1,10 @@
#include "spkdownload.h"
#include "spkutils.h"
#include "spkui_general.h"
#include "spkpopup.h"
#include <QEventLoop>
#include <QDir>
SpkDownloadMgr::SpkDownloadMgr(QObject *parent)
{
@ -18,6 +21,12 @@ SpkDownloadMgr::SpkDownloadMgr(QObject *parent)
mCurrentDownloadId = -1;
mActiveWorkerCount = 0;
mDownloadedBytes = 0;
mProgressEmitterTimer.setInterval(800);
connect(&mProgressEmitterTimer, &QTimer::timeout,
this, &SpkDownloadMgr::ProgressTimer);
}
SpkDownloadMgr::~SpkDownloadMgr()
@ -139,6 +148,8 @@ bool SpkDownloadMgr::StartNewDownload(QString path, int downloadId)
i.Reply->setProperty("failCount", 0); // Used for fail retry algorithm
}
mProgressEmitterTimer.start();
return true;
}
@ -150,7 +161,27 @@ bool SpkDownloadMgr::PauseCurrentDownload()
bool SpkDownloadMgr::CancelCurrentDownload()
{
// UNIMPLEMENTED
// Don't proceed when no downloads are there
if(mCurrentDownloadId == -1)
return false;
// Terminate all workers
for(auto &i : mScheduledWorkers)
{
auto r = i.Reply;
r->blockSignals(true);
r->abort();
r->deleteLater();
}
// Terminate and delete the temporary file
mDestFile.close();
if(!mDestFile.remove())
{
sErr(tr("SpkDownloadMgr: Cannot remove destination file %1 of a cancelled task")
.arg(mDestFile.fileName()));
SpkUi::Popup->Show(tr("The destination file of the cancelled task can't be deleted!"));
}
return false;
}
@ -178,6 +209,9 @@ void SpkDownloadMgr::WorkerFinish()
mScheduledWorkers.clear();
mFailureRetryQueue.clear();
mCurrentDownloadId = -1;
mDownloadedBytes = 0;
mProgressEmitterTimer.stop();
}
}
else
@ -217,6 +251,12 @@ void SpkDownloadMgr::WorkerDownloadProgress()
mDestFile.seek(worker.BeginOffset + worker.BytesRecvd);
mDestFile.write(replyData);
worker.BytesRecvd += replyData.size();
mDownloadedBytes += replyData.size();
}
void SpkDownloadMgr::ProgressTimer()
{
emit DownloadProgressed(mDownloadedBytes, mCurrentRemoteFileInfo.Size, mCurrentDownloadId);
}
void SpkDownloadMgr::LinkReplyWithMe(QNetworkReply *reply)

@ -63,6 +63,18 @@ void SpkResource::ResourceDownloaded()
ResourceResult ret;
ret.status = ResourceStatus::Ready;
if(reply->error() != QNetworkReply::NoError)
{
ret.status = ResourceStatus::Failed;
sWarn(tr("SpkResource: %1 cannot be downloaded, error code %2")
.arg(reply->url().toString())
.arg(reply->error()));
// Tell ResourceContext
if(!reply->property("outdated").toBool())
AcquisitionFinish(id, ret);
}
ret.data = reply->readAll();
// Save cache to disk
@ -106,12 +118,13 @@ void SpkResource::Acquire(SpkPageBase *dest, bool stopOngoing, bool clearQueue)
// And abort as requested.
if(stopOngoing)
i->abort();
delete i;
mWorkingRequests.remove(i);
}
mWorkingRequests.clear();
if(stopOngoing)
{
mWorkingRequests.clear();
mRequestSemaphore->release(mMaximumConcurrent); // Release all semaphore users
}
@ -138,7 +151,23 @@ ResourceResult SpkResource::LocateCachedResource(const ResourceTask &task)
// If there is the desired file, then we return the resource in binary
auto cacheFullPath = GetCachePath(task);
if(list.contains(SpkUtils::CutFileName(cacheFullPath)))
bool isCacheHit;
// Wildcard support
if(task.path == "*")
{
cacheFullPath.chop(1);
auto filterResult = list.filter(SpkUtils::CutFileName(cacheFullPath));
isCacheHit = !filterResult.isEmpty();
if(isCacheHit)
cacheFullPath = SpkUtils::CutPath(cacheFullPath) + '/' + filterResult[0];
}
else
{
isCacheHit = list.contains(SpkUtils::CutFileName(cacheFullPath));
}
if(isCacheHit)
{
// qInfo() << "Cache hit:" << GetCachePath(task);
QFile cacheFile(cacheFullPath);
@ -198,7 +227,13 @@ void SpkResource::PurgeCachedResource(const QString &aPkgName, SpkResource::Reso
QString SpkResource::GetCachePath(const ResourceTask &task)
{
return mCacheDirectory + task.pkgName + '/' + ResourceName.value(task.type) + '.' +
task.info.toString() + '.' + SpkUtils::CutFileName(task.path);
task.info.toString() + '.' + SpkUtils::CutFileName(task.path);
}
ResourceResult SpkResource::CacheLookup(QString pkgName, ResourceType type, QVariant info)
{
ResourceTask task { .pkgName = pkgName, .path = "*", .type = type, .info = info, .id = -1 };
return LocateCachedResource(task);
}
const QMap<SpkResource::ResourceType, QString> SpkResource::ResourceName

@ -7,6 +7,7 @@
#include "dtk/spkdtkplugin.h"
#include "gitver.h"
#include "spkmainwindow.h"
#include "spkpopup.h"
#include "spkstore.h"
#include "spkutils.h"

@ -1,6 +1,7 @@
#include <arpa/inet.h>
#include <QDebug>
#include <QJsonObject>
#include <QJsonDocument>
#include "spkutils.h"
void SpkUtils::VerifySingleRequest(QPointer<QNetworkReply> aReply)