update app cards, add clickable label

This commit is contained in:
uncor3
2025-07-27 09:15:54 +00:00
parent eb43f13077
commit 76e2bf5e5d
11 changed files with 196 additions and 84 deletions
+92 -32
View File
@@ -1,4 +1,6 @@
#include "appdownloadbasedialog.h"
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
@@ -10,21 +12,8 @@
#include <QTimer>
#include <QVBoxLayout>
AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
QWidget *parent)
: QDialog(parent), m_appName(appName), m_downloadProcess(nullptr),
m_progressTimer(nullptr)
void AppDownloadBaseDialog::addProgressBar(int index)
{
// Common UI: progress bar and action button
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setSpacing(20);
layout->setContentsMargins(30, 30, 30, 30);
QLabel *nameLabel = new QLabel(appName);
nameLabel->setStyleSheet(
"font-size: 20px; font-weight: bold; color: #333;");
layout->addWidget(nameLabel);
m_progressBar = new QProgressBar();
m_progressBar->setRange(0, 100);
m_progressBar->setValue(0);
@@ -33,18 +22,38 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
m_progressBar->setStyleSheet(
"QProgressBar { border-radius: 6px; background: #eee; } "
"QProgressBar::chunk { background: #34C759; }");
layout->addWidget(m_progressBar);
m_layout->insertWidget(index, m_progressBar);
}
AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
QWidget *parent)
: QDialog(parent), m_appName(appName), m_downloadProcess(nullptr),
m_progressTimer(nullptr)
{
// Common UI: progress bar and action button
m_layout = new QVBoxLayout(this);
m_layout->setSpacing(20);
m_layout->setContentsMargins(30, 30, 30, 30);
QLabel *nameLabel = new QLabel(appName);
nameLabel->setStyleSheet(
"font-size: 20px; font-weight: bold; color: #333;");
m_layout->addWidget(nameLabel);
m_actionButton = nullptr; // Derived classes set this
}
void AppDownloadBaseDialog::startDownloadProcess(const QStringList &args,
const QString &workingDir)
const QString &outputDir,
int index)
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
m_logFilePath = "./ipatool_download.log";
QString timestamp =
QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
QString logFilePath =
QDir::temp().filePath(QString("%1_%2.log").arg(m_appName, timestamp));
QFile *logFile = new QFile(m_logFilePath);
QFile *logFile = new QFile(logFilePath);
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(this, "Error",
"Failed to open log file for writing.");
@@ -53,25 +62,56 @@ void AppDownloadBaseDialog::startDownloadProcess(const QStringList &args,
logFile->close();
m_downloadProcess = new QProcess(this);
m_downloadProcess->setProcessEnvironment(env);
m_downloadProcess->setWorkingDirectory(workingDir);
// m_downloadProcess->setProcessEnvironment(env);
// m_downloadProcess->setWorkingDirectory(workingDir);
m_downloadProcess->setStandardOutputFile(m_logFilePath, QIODevice::Append);
m_downloadProcess->setStandardErrorFile(m_logFilePath, QIODevice::Append);
m_downloadProcess->setStandardOutputFile(logFilePath, QIODevice::Append);
m_downloadProcess->setStandardErrorFile(logFilePath, QIODevice::Append);
m_downloadProcess->start("ipatool", args);
// TODO: handle errors
addProgressBar(index);
m_progressTimer = new QTimer(this);
connect(m_progressTimer, &QTimer::timeout, this,
&AppDownloadBaseDialog::checkDownloadProgress);
[this, logFilePath, outputDir]() {
checkDownloadProgress(logFilePath, m_appName, outputDir);
});
connect(m_downloadProcess,
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this, logFilePath, outputDir](int exitCode,
QProcess::ExitStatus exitStatus) {
checkDownloadProgress(logFilePath, m_appName, outputDir);
m_progressTimer->stop();
m_progressTimer->deleteLater();
m_downloadProcess->deleteLater();
// m_progressBar->setValue(100);
// QMessageBox::information(
// this, "Download Complete",
// QString("Successfully downloaded
// %1.").arg(m_appName));
// accept();
// } else {
// QMessageBox::critical(
// this, "Download Failed",
// QString("Failed to download %1. Exit code: %2")
// .arg(m_appName)
// .arg(exitCode));
// reject();
// }
});
m_progressTimer->start(1000);
if (m_actionButton)
m_actionButton->setEnabled(false);
}
void AppDownloadBaseDialog::checkDownloadProgress()
void AppDownloadBaseDialog::checkDownloadProgress(const QString &logFilePath,
const QString &appName,
const QString &outputDir)
{
QFile logFile(m_logFilePath);
QFile logFile(logFilePath);
if (!logFile.open(QIODevice::ReadOnly | QIODevice::Text))
return;
@@ -79,19 +119,21 @@ void AppDownloadBaseDialog::checkDownloadProgress()
logFile.close();
int jsonStart = fileContents.indexOf('{');
qDebug() << "JSON Start:" << jsonStart;
if (jsonStart != -1) {
QString jsonString = fileContents.mid(jsonStart);
QJsonParseError parseError;
QJsonDocument doc =
QJsonDocument::fromJson(jsonString.toUtf8(), &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
qDebug() << "Parsed JSON successfully";
QJsonObject jsonObj = doc.object();
QString level = jsonObj.value("level").toString();
bool success = jsonObj.value("success").toBool();
m_progressTimer->stop();
if (m_actionButton)
m_actionButton->setEnabled(true);
// if (m_actionButton)
// m_actionButton->setEnabled(true);
if (level == "error") {
QString errorMsg = jsonObj.contains("error")
@@ -104,11 +146,29 @@ void AppDownloadBaseDialog::checkDownloadProgress()
reject();
return;
} else if (level == "info" && success) {
QString outputPath = jsonObj.value("output").toString();
QMessageBox::information(
this, "Download Successful",
QString("Successfully downloaded %1.\nOutput file: %2")
.arg(m_appName, outputPath));
m_progressBar->setValue(100);
// QMessageBox::information(
// this, "Download Successful",
// QString("Successfully downloaded %1. Would you like to "
// "open the directory?")
// .arg(m_appName));
if (QMessageBox::Yes ==
QMessageBox::question(
this, "Open Directory",
QString("Successfully downloaded. Would you like "
"to open the output directory: %1?")
.arg(outputDir))) {
QDir dir(outputDir);
if (!dir.exists()) {
QMessageBox::warning(
this, "Directory Not Found",
QString("The directory %1 does not exist.")
.arg(outputDir));
} else {
QDesktopServices::openUrl(
QUrl::fromLocalFile(outputDir));
}
}
accept();
return;
}
+7 -3
View File
@@ -5,6 +5,7 @@
#include <QProcess>
#include <QProgressBar>
#include <QPushButton>
#include <QVBoxLayout>
class AppDownloadBaseDialog : public QDialog
{
@@ -15,14 +16,17 @@ public:
protected:
void startDownloadProcess(const QStringList &args,
const QString &workingDir);
void checkDownloadProgress();
const QString &workingDir, int index);
void checkDownloadProgress(const QString &logFilePath,
const QString &appName,
const QString &outputDir);
void addProgressBar(int index);
QProgressBar *m_progressBar;
QTimer *m_progressTimer;
QString m_logFilePath;
QProcess *m_downloadProcess;
QString m_appName;
QPushButton *m_actionButton;
QVBoxLayout *m_layout;
};
#endif // APPDOWNLOADBASEDIALOG_H
+19 -5
View File
@@ -1,14 +1,17 @@
#include "appdownloaddialog.h"
#include "clickablelabel.h"
#include <QDesktopServices>
#include <QFileDialog>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
AppDownloadDialog::AppDownloadDialog(const QString &appName,
const QString &bundleId,
const QString &description,
QWidget *parent)
: AppDownloadBaseDialog(appName, parent),
m_outputDir(QDir::homePath().append("/Downloads"))
m_outputDir(QDir::homePath().append("/Downloads")), m_bundleId(bundleId)
{
setWindowTitle("Download " + appName + " IPA");
setModal(true);
@@ -28,8 +31,13 @@ AppDownloadDialog::AppDownloadDialog(const QString &appName,
dirTextLabel->setStyleSheet("font-size: 14px; color: #333;");
dirLayout->addWidget(dirTextLabel);
m_dirLabel = new QLabel(m_outputDir);
m_dirLabel = new ClickableLabel(this);
m_dirLabel->setText(m_outputDir);
m_dirLabel->setStyleSheet("font-size: 14px; color: #007AFF;");
connect(m_dirLabel, &ClickableLabel::clicked, this, [this]() {
QDesktopServices::openUrl(QUrl::fromLocalFile(m_outputDir));
});
m_dirLabel->setCursor(Qt::PointingHandCursor);
dirLayout->addWidget(m_dirLabel, 1);
m_dirButton = new QPushButton("Choose...");
@@ -68,9 +76,15 @@ void AppDownloadDialog::onDownloadClicked()
{
// Disable directory selection once download starts
m_dirButton->setEnabled(false);
m_actionButton->setEnabled(false);
int buttonIndex = m_layout->indexOf(m_actionButton);
layout()->removeWidget(m_actionButton);
m_actionButton->deleteLater();
QStringList args = {"download",
"-i",
"553834731",
"-b",
m_bundleId,
"-o",
m_outputDir,
"--purchase",
@@ -78,5 +92,5 @@ void AppDownloadDialog::onDownloadClicked()
"iDescriptor",
"--format",
"json"};
startDownloadProcess(args, m_outputDir);
startDownloadProcess(args, m_outputDir, buttonIndex);
}
+4 -2
View File
@@ -2,6 +2,7 @@
#define APPDOWNLOADDIALOG_H
#include "appdownloadbasedialog.h"
#include "clickablelabel.h"
#include <QDialog>
#include <QLabel>
#include <QPushButton>
@@ -10,7 +11,7 @@ class AppDownloadDialog : public AppDownloadBaseDialog
{
Q_OBJECT
public:
explicit AppDownloadDialog(const QString &appName,
explicit AppDownloadDialog(const QString &appName, const QString &bundleId,
const QString &description,
QWidget *parent = nullptr);
@@ -20,7 +21,8 @@ private slots:
private:
QString m_outputDir;
QPushButton *m_dirButton;
QLabel *m_dirLabel;
ClickableLabel *m_dirLabel;
QString m_bundleId;
};
#endif // APPDOWNLOADDIALOG_H
+11 -3
View File
@@ -110,10 +110,11 @@ void AppInstallDialog::onInstallClicked()
"Please connect a device first.");
return;
}
m_deviceCombo->setEnabled(false);
QString selectedDevice = m_deviceCombo->currentData().toString();
QStringList args = {"download",
"-i",
"553834731",
"-b",
m_bundleId,
"-o",
"./",
"--purchase",
@@ -121,5 +122,12 @@ void AppInstallDialog::onInstallClicked()
"iDescriptor",
"--format",
"json"};
startDownloadProcess(args, QDir::currentPath());
m_actionButton->setEnabled(false);
int buttonIndex = m_layout->indexOf(m_actionButton);
layout()->removeWidget(m_actionButton);
m_actionButton->deleteLater();
m_actionButton = nullptr; // Reset to avoid double deletion
startDownloadProcess(args, QDir::currentPath(), buttonIndex);
}
+1
View File
@@ -18,6 +18,7 @@ private slots:
private:
QComboBox *m_deviceCombo;
QString m_bundleId;
void updateDeviceList();
};
+24 -21
View File
@@ -132,6 +132,7 @@ void AppsWidget::setupUI()
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
process.setProcessEnvironment(env);
// process.
// TODO: keychain passphrase
process.start("ipatool", QStringList()
<< "auth" << "info" << "--non-interactive"
<< "--format" << "json"
@@ -217,24 +218,24 @@ void AppsWidget::setupUI()
gridLayout->setSpacing(20);
// Create sample app cards
createAppCard("Instagram", "Photo & Video sharing social network", "",
gridLayout, 0, 0);
createAppCard("WhatsApp", "Free messaging and video calling", "",
gridLayout, 0, 1);
createAppCard("Spotify", "Music streaming and podcast platform", "",
gridLayout, 0, 2);
createAppCard("YouTube", "Video sharing and streaming platform", "",
gridLayout, 1, 0);
createAppCard("Twitter", "Social media and microblogging", "", gridLayout,
1, 1);
createAppCard("TikTok", "Short-form video hosting service", "", gridLayout,
1, 2);
createAppCard("Discord", "Voice, video and text communication", "",
createAppCard("Instagram", "com.burbn.instagram",
"Photo & Video sharing social network", "", gridLayout, 0, 0);
createAppCard("WhatsApp", "net.whatsapp.WhatsApp",
"Free messaging and video calling", "", gridLayout, 0, 1);
createAppCard("Spotify", "com.spotify.client",
"Music streaming and podcast platform", "", gridLayout, 0, 2);
createAppCard("YouTube", "com.google.ios.youtube",
"Video sharing and streaming platform", "", gridLayout, 1, 0);
createAppCard("X", "com.atebits.Tweetie2", "Social media and microblogging",
"", gridLayout, 1, 1);
createAppCard("TikTok", "com.zhiliaoapp.musically",
"Short-form video hosting service", "", gridLayout, 1, 2);
createAppCard("Twitch", "tv.twitch", "Live streaming platform", "",
gridLayout, 2, 0);
createAppCard("Telegram", "Cloud-based instant messaging", "", gridLayout,
2, 1);
createAppCard("Reddit", "Social news aggregation platform", "", gridLayout,
2, 2);
createAppCard("Telegram", "ph.telegra.Telegraph",
"Cloud-based instant messaging", "", gridLayout, 2, 1);
createAppCard("Reddit", "com.reddit.Reddit",
"Social news aggregation platform", "", gridLayout, 2, 2);
gridLayout->setRowStretch(gridLayout->rowCount(), 1);
@@ -242,7 +243,8 @@ void AppsWidget::setupUI()
mainLayout->addWidget(m_scrollArea);
}
void AppsWidget::createAppCard(const QString &name, const QString &description,
void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
const QString &description,
const QString &iconPath, QGridLayout *gridLayout,
int row, int col)
{
@@ -297,7 +299,7 @@ void AppsWidget::createAppCard(const QString &name, const QString &description,
[this, name, description]() { onAppCardClicked(name, description); });
connect(downloadIpaLabel, &QPushButton::clicked, this,
[this, name]() { onDownloadIpaClicked(name); });
[this, name, bundleId]() { onDownloadIpaClicked(name, bundleId); });
cardLayout->addWidget(installLabel);
cardLayout->addWidget(downloadIpaLabel);
@@ -309,10 +311,11 @@ void AppsWidget::createAppCard(const QString &name, const QString &description,
gridLayout->addWidget(cardFrame, row, col);
}
void AppsWidget::onDownloadIpaClicked(const QString &name)
void AppsWidget::onDownloadIpaClicked(const QString &name,
const QString &bundleId)
{
QString description = "Download the IPA file for " + name;
AppDownloadDialog dialog(name, description, this);
AppDownloadDialog dialog(name, bundleId, description, this);
dialog.exec();
}
+4 -4
View File
@@ -40,13 +40,13 @@ public:
private slots:
void onLoginClicked();
void onAppCardClicked(const QString &appName, const QString &description);
void onDownloadIpaClicked(const QString &name);
void onDownloadIpaClicked(const QString &name, const QString &bundleId);
private:
void setupUI();
void createAppCard(const QString &name, const QString &description,
const QString &iconPath, QGridLayout *gridLayout,
int row, int col);
void createAppCard(const QString &name, const QString &bundleId,
const QString &description, const QString &iconPath,
QGridLayout *gridLayout, int row, int col);
QScrollArea *m_scrollArea;
QWidget *m_contentWidget;
QPushButton *m_loginButton;
+10
View File
@@ -0,0 +1,10 @@
#include "clickablelabel.h"
ClickableLabel::ClickableLabel(QWidget *parent, Qt::WindowFlags f)
: QLabel(parent)
{
}
ClickableLabel::~ClickableLabel() {}
void ClickableLabel::mousePressEvent(QMouseEvent *event) { emit clicked(); }
+24
View File
@@ -0,0 +1,24 @@
#ifndef CLICKABLELABEL_H
#define CLICKABLELABEL_H
#include <QLabel>
#include <QWidget>
#include <Qt>
class ClickableLabel : public QLabel
{
Q_OBJECT
public:
explicit ClickableLabel(QWidget *parent = Q_NULLPTR,
Qt::WindowFlags f = Qt::WindowFlags());
~ClickableLabel();
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event);
};
#endif // CLICKABLELABEL_H
-14
View File
@@ -124,13 +124,6 @@
<string>Apps</string>
</attribute>
<layout class="QVBoxLayout" name="appsTabLayout">
<item>
<widget class="QLabel" name="appsLabel">
<property name="text">
<string>Apps will be here</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="toolboxTab">
@@ -138,13 +131,6 @@
<string>Toolbox</string>
</attribute>
<layout class="QVBoxLayout" name="toolboxTabLayout">
<item>
<widget class="QLabel" name="toolboxLabel">
<property name="text">
<string>Toolbox will be here</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="jailbrokenTab">