mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-22 03:45:51 +08:00
implement ssh connection
- Updated AppWidget to utilize QStackedWidget for better UI management, including loading and error states. - Removed unnecessary includes and improved the organization of private methods in AppWidget. - Enhanced DevDiskImagesWidget UI by adding a settings button and improving layout with shadows. - Refactored DeviceInfoWidget to use QGroupBox for better visual grouping of device information. - Replaced QProcess with libssh for SSH connections in JailbrokenWidget, improving reliability and performance. - Added a timer to check SSH data and handle input/output more effectively. - Improved SettingsManager to manage settings dialog display and lifecycle. - Refactored SettingsWidget to be a QDialog for better user experience and removed unnecessary buttons. - Adjusted layout margins across various widgets for a cleaner UI.
This commit is contained in:
+137
-138
@@ -5,18 +5,11 @@
|
||||
#include "appinstalldialog.h"
|
||||
#include "appstoremanager.h"
|
||||
#include "logindialog.h"
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFrame>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
@@ -33,20 +26,16 @@
|
||||
#include <QPixmap>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QScrollArea>
|
||||
#include <QStyle>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
// watch for login and logout events
|
||||
AppsWidget::AppsWidget(QWidget *parent) : QWidget(parent), m_isLoggedIn(false)
|
||||
{
|
||||
// m_searchProcess = new QProcess(this);
|
||||
m_searchWatcher = new QFutureWatcher<QString>(this);
|
||||
m_debounceTimer = new QTimer(this);
|
||||
setupUI();
|
||||
}
|
||||
@@ -60,7 +49,7 @@ void AppsWidget::setupUI()
|
||||
// Header with login
|
||||
QWidget *headerWidget = new QWidget();
|
||||
headerWidget->setFixedHeight(60);
|
||||
headerWidget->setStyleSheet("border-bottom: 1px solid #dee2e6;");
|
||||
headerWidget->setStyleSheet("border-bottom: 1px solid #363d32;");
|
||||
|
||||
QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget);
|
||||
headerLayout->setContentsMargins(20, 10, 20, 10);
|
||||
@@ -73,12 +62,22 @@ void AppsWidget::setupUI()
|
||||
m_statusLabel = new QLabel("Not signed in");
|
||||
m_statusLabel->setStyleSheet("margin-right: 20px;");
|
||||
|
||||
m_loginButton = new QPushButton();
|
||||
m_searchEdit = new QLineEdit();
|
||||
m_searchEdit->setMaximumWidth(400);
|
||||
m_searchEdit->setStyleSheet("QLineEdit { "
|
||||
" padding: 8px; "
|
||||
" border: 1px solid #ccc; "
|
||||
" border-radius: 4px; "
|
||||
" font-size: 14px; "
|
||||
"}");
|
||||
|
||||
// --- Status and Login Button ---
|
||||
m_manager = AppStoreManager::sharedInstance();
|
||||
if (!m_manager) {
|
||||
qDebug() << "AppStoreManager failed to initialize";
|
||||
m_statusLabel->setText("Failed to initialize");
|
||||
m_loginButton = new QPushButton("Failed to initialize");
|
||||
m_loginButton->setText("Failed to initialize");
|
||||
m_loginButton->setEnabled(false);
|
||||
m_loginButton->setStyleSheet(
|
||||
"background-color: #ccc; color: #666; border: none; border-radius: "
|
||||
@@ -91,17 +90,6 @@ void AppsWidget::setupUI()
|
||||
|
||||
mainLayout->addWidget(headerWidget);
|
||||
|
||||
m_searchEdit = new QLineEdit();
|
||||
m_searchEdit->setPlaceholderText(m_isLoggedIn ? "Search for apps..."
|
||||
: "Sign in to search");
|
||||
m_searchEdit->setMaximumWidth(400);
|
||||
m_searchEdit->setStyleSheet("QLineEdit { "
|
||||
" padding: 8px; "
|
||||
" border: 1px solid #ccc; "
|
||||
" border-radius: 4px; "
|
||||
" font-size: 14px; "
|
||||
"}");
|
||||
|
||||
QAction *searchAction = m_searchEdit->addAction(
|
||||
this->style()->standardIcon(QStyle::SP_FileDialogContentsView),
|
||||
QLineEdit::TrailingPosition);
|
||||
@@ -117,23 +105,16 @@ void AppsWidget::setupUI()
|
||||
headerLayout->addWidget(m_statusLabel);
|
||||
headerLayout->addWidget(m_loginButton);
|
||||
|
||||
// Scroll area for apps
|
||||
m_scrollArea = new QScrollArea();
|
||||
m_scrollArea->setWidgetResizable(true);
|
||||
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_scrollArea->setStyleSheet(
|
||||
"QScrollArea { background: transparent; border: none; }");
|
||||
m_scrollArea->viewport()->setStyleSheet("background: transparent;");
|
||||
// Stacked widget for different pages
|
||||
m_stackedWidget = new QStackedWidget();
|
||||
setupDefaultAppsPage();
|
||||
setupLoadingPage();
|
||||
setupErrorPage();
|
||||
|
||||
m_contentWidget = new QWidget();
|
||||
QGridLayout *gridLayout = new QGridLayout(m_contentWidget);
|
||||
gridLayout->setContentsMargins(20, 20, 20, 20);
|
||||
gridLayout->setSpacing(20);
|
||||
mainLayout->addWidget(m_stackedWidget);
|
||||
|
||||
populateDefaultApps();
|
||||
|
||||
m_scrollArea->setWidget(m_contentWidget);
|
||||
mainLayout->addWidget(m_scrollArea);
|
||||
// Show default apps initially
|
||||
showDefaultApps();
|
||||
// Connections
|
||||
connect(m_loginButton, &QPushButton::clicked, this,
|
||||
&AppsWidget::onLoginClicked);
|
||||
@@ -142,8 +123,6 @@ void AppsWidget::setupUI()
|
||||
m_debounceTimer->setSingleShot(true);
|
||||
connect(m_debounceTimer, &QTimer::timeout, this,
|
||||
&AppsWidget::performSearch);
|
||||
connect(m_searchWatcher, &QFutureWatcher<QString>::finished, this,
|
||||
&AppsWidget::onSearchFinished);
|
||||
connect(m_manager, &AppStoreManager::loginSuccessful, this,
|
||||
&AppsWidget::onAppStoreInitialized);
|
||||
connect(m_manager, &AppStoreManager::loggedOut, this,
|
||||
@@ -152,8 +131,6 @@ void AppsWidget::setupUI()
|
||||
|
||||
void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo)
|
||||
{
|
||||
qDebug() << "AppStoreManager initialized successfully";
|
||||
|
||||
if (accountInfo.contains("success") &&
|
||||
accountInfo.value("success").toBool()) {
|
||||
if (accountInfo.contains("email")) {
|
||||
@@ -167,16 +144,103 @@ void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo)
|
||||
m_statusLabel->setText("Not signed in");
|
||||
}
|
||||
|
||||
m_loginButton = new QPushButton(m_isLoggedIn ? "Sign Out" : "Sign In");
|
||||
m_loginButton->setText(m_isLoggedIn ? "Sign Out" : "Sign In");
|
||||
m_loginButton->setStyleSheet(
|
||||
"background-color: #007AFF; color: white; border: none; "
|
||||
"border-radius: "
|
||||
"4px; padding: 8px 16px; font-size: 14px;");
|
||||
m_searchEdit->setPlaceholderText(m_isLoggedIn ? "Search for apps..."
|
||||
: "Sign in to search");
|
||||
}
|
||||
|
||||
void AppsWidget::setupDefaultAppsPage()
|
||||
{
|
||||
m_defaultAppsPage = new QWidget();
|
||||
|
||||
// Scroll area for apps
|
||||
m_scrollArea = new QScrollArea();
|
||||
m_scrollArea->setWidgetResizable(true);
|
||||
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_scrollArea->setStyleSheet(
|
||||
"QScrollArea { background: transparent; border: none; }");
|
||||
m_scrollArea->viewport()->setStyleSheet("background: transparent;");
|
||||
|
||||
m_contentWidget = new QWidget();
|
||||
QGridLayout *gridLayout = new QGridLayout(m_contentWidget);
|
||||
gridLayout->setContentsMargins(20, 20, 20, 20);
|
||||
gridLayout->setSpacing(20);
|
||||
|
||||
m_scrollArea->setWidget(m_contentWidget);
|
||||
|
||||
QVBoxLayout *pageLayout = new QVBoxLayout(m_defaultAppsPage);
|
||||
pageLayout->setContentsMargins(0, 0, 0, 0);
|
||||
pageLayout->addWidget(m_scrollArea);
|
||||
|
||||
m_stackedWidget->addWidget(m_defaultAppsPage);
|
||||
}
|
||||
|
||||
void AppsWidget::setupLoadingPage()
|
||||
{
|
||||
m_loadingPage = new QWidget();
|
||||
|
||||
QVBoxLayout *loadingLayout = new QVBoxLayout(m_loadingPage);
|
||||
loadingLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
m_loadingIndicator = new QProcessIndicator();
|
||||
m_loadingIndicator->setType(QProcessIndicator::line_rotate);
|
||||
m_loadingIndicator->setFixedSize(64, 32);
|
||||
|
||||
m_loadingLabel = new QLabel("Loading...");
|
||||
m_loadingLabel->setAlignment(Qt::AlignCenter);
|
||||
m_loadingLabel->setStyleSheet(
|
||||
"font-size: 16px; color: #666; margin-top: 20px;");
|
||||
|
||||
loadingLayout->addWidget(m_loadingIndicator, 0, Qt::AlignCenter);
|
||||
loadingLayout->addWidget(m_loadingLabel, 0, Qt::AlignCenter);
|
||||
|
||||
m_stackedWidget->addWidget(m_loadingPage);
|
||||
}
|
||||
|
||||
void AppsWidget::setupErrorPage()
|
||||
{
|
||||
m_errorPage = new QWidget();
|
||||
|
||||
QVBoxLayout *errorLayout = new QVBoxLayout(m_errorPage);
|
||||
errorLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
m_errorLabel = new QLabel("Error occurred");
|
||||
m_errorLabel->setAlignment(Qt::AlignCenter);
|
||||
m_errorLabel->setWordWrap(true);
|
||||
m_errorLabel->setStyleSheet("font-size: 16px; color: #666;");
|
||||
|
||||
errorLayout->addWidget(m_errorLabel, 0, Qt::AlignCenter);
|
||||
|
||||
m_stackedWidget->addWidget(m_errorPage);
|
||||
}
|
||||
|
||||
void AppsWidget::showDefaultApps()
|
||||
{
|
||||
clearAppGrid();
|
||||
populateDefaultApps();
|
||||
m_stackedWidget->setCurrentWidget(m_defaultAppsPage);
|
||||
}
|
||||
|
||||
void AppsWidget::showLoading(const QString &message)
|
||||
{
|
||||
m_loadingLabel->setText(message);
|
||||
m_loadingIndicator->start();
|
||||
m_stackedWidget->setCurrentWidget(m_loadingPage);
|
||||
}
|
||||
|
||||
void AppsWidget::showError(const QString &message)
|
||||
{
|
||||
m_loadingIndicator->stop();
|
||||
m_errorLabel->setText(message);
|
||||
m_stackedWidget->setCurrentWidget(m_errorPage);
|
||||
}
|
||||
|
||||
void AppsWidget::populateDefaultApps()
|
||||
{
|
||||
clearAppGrid();
|
||||
QGridLayout *gridLayout =
|
||||
qobject_cast<QGridLayout *>(m_contentWidget->layout());
|
||||
if (!gridLayout)
|
||||
@@ -221,20 +285,10 @@ void AppsWidget::clearAppGrid()
|
||||
}
|
||||
}
|
||||
|
||||
void AppsWidget::showStatusMessage(const QString &message)
|
||||
{
|
||||
clearAppGrid();
|
||||
QGridLayout *gridLayout =
|
||||
qobject_cast<QGridLayout *>(m_contentWidget->layout());
|
||||
if (!gridLayout)
|
||||
return;
|
||||
|
||||
QLabel *statusLabel = new QLabel(message);
|
||||
statusLabel->setAlignment(Qt::AlignCenter);
|
||||
statusLabel->setWordWrap(true);
|
||||
statusLabel->setStyleSheet("font-size: 16px; color: #666;");
|
||||
gridLayout->addWidget(statusLabel, 0, 0, 1, -1, Qt::AlignCenter);
|
||||
}
|
||||
// void AppsWidget::showStatusMessage(const QString &message)
|
||||
// {
|
||||
// showError(message);
|
||||
// }
|
||||
|
||||
void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
|
||||
const QString &description,
|
||||
@@ -384,104 +438,48 @@ void AppsWidget::onSearchTextChanged() { m_debounceTimer->start(300); }
|
||||
|
||||
void AppsWidget::performSearch()
|
||||
{
|
||||
if (m_searchWatcher->isRunning()) {
|
||||
m_searchWatcher->cancel();
|
||||
m_searchWatcher->waitForFinished();
|
||||
}
|
||||
|
||||
QString searchTerm = m_searchEdit->text().trimmed();
|
||||
if (searchTerm.isEmpty()) {
|
||||
populateDefaultApps();
|
||||
showDefaultApps();
|
||||
return;
|
||||
}
|
||||
|
||||
showStatusMessage(QString("Searching for \"%1\"...").arg(searchTerm));
|
||||
showLoading(QString("Searching for \"%1\"...").arg(searchTerm));
|
||||
|
||||
AppStoreManager *manager = AppStoreManager::sharedInstance();
|
||||
if (!manager) {
|
||||
showStatusMessage("Failed to initialize App Store manager.");
|
||||
showError("Failed to initialize App Store manager.");
|
||||
return;
|
||||
}
|
||||
|
||||
manager->searchApps(
|
||||
searchTerm, 20, [this](bool success, const QString &results) {
|
||||
if (!success || results.isEmpty()) {
|
||||
showStatusMessage("No apps found or search failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc =
|
||||
QJsonDocument::fromJson(results.toUtf8(), &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "JSON parse error:" << parseError.errorString()
|
||||
<< " on output: " << results;
|
||||
showStatusMessage("Failed to parse search results.");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootObj = doc.object();
|
||||
if (!rootObj.value("success").toBool()) {
|
||||
QString errorMessage =
|
||||
rootObj.value("error").toString("Unknown search error.");
|
||||
showStatusMessage(
|
||||
QString("Search error: %1").arg(errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray resultsArray = rootObj.value("results").toArray();
|
||||
if (resultsArray.isEmpty()) {
|
||||
showStatusMessage("No apps found.");
|
||||
return;
|
||||
}
|
||||
|
||||
clearAppGrid();
|
||||
QGridLayout *gridLayout =
|
||||
qobject_cast<QGridLayout *>(m_contentWidget->layout());
|
||||
if (!gridLayout)
|
||||
return;
|
||||
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
const int maxCols = 3;
|
||||
|
||||
for (const QJsonValue &appValue : resultsArray) {
|
||||
QJsonObject appObj = appValue.toObject();
|
||||
QString name = appObj.value("trackName").toString();
|
||||
QString bundleId = appObj.value("bundleId").toString();
|
||||
QString description =
|
||||
"Version: " + appObj.value("version").toString();
|
||||
|
||||
createAppCard(name, bundleId, description, "", gridLayout, row,
|
||||
col);
|
||||
|
||||
col++;
|
||||
if (col >= maxCols) {
|
||||
col = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
gridLayout->setRowStretch(gridLayout->rowCount(), 1);
|
||||
});
|
||||
manager->searchApps(searchTerm, 20,
|
||||
[this](bool success, const QString &results) {
|
||||
onSearchFinished(success, results);
|
||||
});
|
||||
}
|
||||
|
||||
void AppsWidget::onSearchFinished()
|
||||
void AppsWidget::onSearchFinished(bool success, const QString &results)
|
||||
{
|
||||
QString jsonOutput = m_searchWatcher->result();
|
||||
if (jsonOutput.isEmpty()) {
|
||||
showStatusMessage("No apps found or search failed.");
|
||||
// FIXME: cancel fetch instead of just ignoring results
|
||||
QString searchTerm = m_searchEdit->text().trimmed();
|
||||
if (searchTerm.isEmpty()) {
|
||||
showDefaultApps();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!success || results.isEmpty()) {
|
||||
showError("No apps found or search failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc =
|
||||
QJsonDocument::fromJson(jsonOutput.toUtf8(), &parseError);
|
||||
QJsonDocument doc = QJsonDocument::fromJson(results.toUtf8(), &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "JSON parse error:" << parseError.errorString()
|
||||
<< " on output: " << jsonOutput;
|
||||
showStatusMessage("Failed to parse search results.");
|
||||
<< " on output: " << results;
|
||||
showError("Failed to parse search results.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -489,13 +487,13 @@ void AppsWidget::onSearchFinished()
|
||||
if (!rootObj.value("success").toBool()) {
|
||||
QString errorMessage =
|
||||
rootObj.value("error").toString("Unknown search error.");
|
||||
showStatusMessage(QString("Search error: %1").arg(errorMessage));
|
||||
showError(QString("Search error: %1").arg(errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray resultsArray = rootObj.value("results").toArray();
|
||||
if (resultsArray.isEmpty()) {
|
||||
showStatusMessage("No apps found.");
|
||||
showError("No apps found.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -524,4 +522,5 @@ void AppsWidget::onSearchFinished()
|
||||
}
|
||||
}
|
||||
gridLayout->setRowStretch(gridLayout->rowCount(), 1);
|
||||
m_stackedWidget->setCurrentWidget(m_defaultAppsPage);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user