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:
uncor3
2025-10-03 06:45:34 -07:00
parent e25f194ee9
commit d6282762b1
14 changed files with 518 additions and 455 deletions
+137 -138
View File
@@ -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);
}