mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-22 03:45:51 +08:00
update app cards, add clickable label
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ private slots:
|
||||
|
||||
private:
|
||||
QComboBox *m_deviceCombo;
|
||||
QString m_bundleId;
|
||||
void updateDeviceList();
|
||||
};
|
||||
|
||||
|
||||
+24
-21
@@ -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
@@ -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;
|
||||
|
||||
@@ -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(); }
|
||||
@@ -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
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user