diff --git a/CMakeLists.txt b/CMakeLists.txt index c072041..7dbbe77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,6 +140,9 @@ file(GLOB PROJECT_SOURCES src/*.cpp src/core/helpers/*.cpp src/core/services/*.cpp + src/platform/*.cpp + src/platform/*.mm + src/platform/*.m src/*.h src/*.ui resources.qrc diff --git a/icons/MdiLightningBolt.png b/icons/MdiLightningBolt.png new file mode 100644 index 0000000..b4daaf2 Binary files /dev/null and b/icons/MdiLightningBolt.png differ diff --git a/icons/MingcuteSettings7Line.png b/icons/MingcuteSettings7Line.png new file mode 100644 index 0000000..6f19580 Binary files /dev/null and b/icons/MingcuteSettings7Line.png differ diff --git a/resources.qrc b/resources.qrc index 58b6d47..cc8c805 100644 --- a/resources.qrc +++ b/resources.qrc @@ -2,6 +2,8 @@ icons/ArrowMoveDownRight.svg icons/video-x-generic.png + icons/MdiLightningBolt.png + icons/MingcuteSettings7Line.png qml/MapView.qml resources/dump.js resources/iphone.png diff --git a/src/appcontext.cpp b/src/appcontext.cpp index ac2a509..9e78da4 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -153,6 +153,11 @@ void AppContext::instanceRemoveDevice(QString _udid) // return true; } +int AppContext::getConnectedDeviceCount() const +{ + return m_devices.size() + m_recoveryDevices.size(); +} + void AppContext::removeDevice(QString _udid) { diff --git a/src/appcontext.h b/src/appcontext.h index f330187..23f10b4 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -26,6 +26,7 @@ public: QList getAllRecoveryDevices(); ~AppContext(); void instanceRemoveDevice(QString _udid); + int getConnectedDeviceCount() const; private: QMap m_devices; diff --git a/src/batterywidget.cpp b/src/batterywidget.cpp index 533334d..5617deb 100644 --- a/src/batterywidget.cpp +++ b/src/batterywidget.cpp @@ -10,8 +10,8 @@ BatteryWidget::BatteryWidget(float value, bool isCharging, QWidget *parent) : QWidget(parent), m_value(value), m_isCharging(isCharging) { - setMinimumSize(50, 40); - setMaximumSize(60, 40); + setMinimumSize(30, 30); + setMaximumSize(40, 40); } void BatteryWidget::resizeEvent(QResizeEvent *) @@ -111,7 +111,7 @@ void BatteryWidget::paintEvent(QPaintEvent *) pen.setColor(Qt::white); painter.setPen(pen); QFont textFont = QFont(); - textFont.setPixelSize(widgetFrame.height() / 2); + textFont.setPixelSize(widgetFrame.height() / 1.65); painter.setFont(textFont); QFontMetrics fm(textFont); QString percentageLevelString = QString("%1%").arg(m_value); @@ -121,13 +121,4 @@ void BatteryWidget::paintEvent(QPaintEvent *) QPointF textPosition = QPointF(widgetFrame.center().x() - textWidth / 2, widgetFrame.center().y() + textHeight / 3); painter.drawText(textPosition, percentageLevelString); - - float chargerSize = widgetFrame.height() / 2; - - // if (isCharging) { - // QPixmap pixmap(":/img/charge.png"); - // painter.drawPixmap(widgetFrame.center().x() - chargerSize * 1.5, - // widgetFrame.top() + chargerSize / 2, chargerSize, - // chargerSize, pixmap); - // } } \ No newline at end of file diff --git a/src/customtabwidget.cpp b/src/customtabwidget.cpp new file mode 100644 index 0000000..6250e93 --- /dev/null +++ b/src/customtabwidget.cpp @@ -0,0 +1,329 @@ +#include "customtabwidget.h" +#include +#include +#include +#include +#include +#include + +// CustomTab implementation +CustomTab::CustomTab(const QString &text, QWidget *parent) + : QPushButton(text, parent), m_notificationLabel(nullptr), + m_notificationCount(0) +{ + setCheckable(true); + setFixedHeight(54); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + // Set up notification label + m_notificationLabel = new QLabel(this); + m_notificationLabel->setAlignment(Qt::AlignCenter); + m_notificationLabel->hide(); + m_notificationLabel->setStyleSheet("QLabel {" + " background-color: #e6eef9;" + " border-radius: 16px;" + " color: #333;" + " font-weight: 500;" + " min-width: 32px;" + " min-height: 32px;" + " max-width: 32px;" + " max-height: 32px;" + "}"); + + updateNotificationDisplay(); +} + +void CustomTab::setNotificationCount(int count) +{ + m_notificationCount = count; + updateNotificationDisplay(); +} + +void CustomTab::setIcon(const QIcon &icon) +{ + QPushButton::setIcon(icon); + setIconSize(QSize(20, 20)); +} + +void CustomTab::updateNotificationDisplay() +{ + if (m_notificationCount > 0) { + m_notificationLabel->setText(QString::number(m_notificationCount)); + m_notificationLabel->show(); + + // Position notification label to the right of the text + QFontMetrics fm(font()); + int textWidth = fm.horizontalAdvance(text()); + int iconWidth = iconSize().width(); + int totalContentWidth = (iconWidth > 0 ? iconWidth + 8 : 0) + textWidth; + + int x = (width() - totalContentWidth) / 2 + totalContentWidth + 12; + int y = (height() - 32) / 2; + + m_notificationLabel->setGeometry(x, y, 32, 32); + } else { + m_notificationLabel->hide(); + } +} + +void CustomTab::paintEvent(QPaintEvent *event) +{ + QPushButton::paintEvent(event); + updateNotificationDisplay(); + + // Update notification label style based on checked state + if (isChecked()) { + m_notificationLabel->setStyleSheet("QLabel {" + " background-color: #185ee0;" + " border-radius: 16px;" + " color: white;" + " font-weight: 500;" + " min-width: 32px;" + " min-height: 32px;" + " max-width: 32px;" + " max-height: 32px;" + "}"); + } else { + m_notificationLabel->setStyleSheet("QLabel {" + " background-color: #e6eef9;" + " border-radius: 16px;" + " color: #333;" + " font-weight: 500;" + " min-width: 32px;" + " min-height: 32px;" + " max-width: 32px;" + " max-height: 32px;" + "}"); + } +} + +// CustomTabWidget implementation +CustomTabWidget::CustomTabWidget(QWidget *parent) + : QWidget(parent), m_currentIndex(0) +{ + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setContentsMargins(0, 0, 0, 0); + m_mainLayout->setSpacing(0); + + // Create tab bar container + m_tabBar = new QWidget(); + m_tabBar->setFixedHeight(70); // 54px height + 16px padding + m_tabLayout = new QHBoxLayout(m_tabBar); + // m_tabLayout->setContentsMargins(12, 8, 12, 8); + m_tabLayout->setSpacing(0); + + // Style the tab bar + m_tabBar->setStyleSheet("QWidget {" + // " background-color: white;" + " border-radius: 35px;" + "}"); + + // Add drop shadow effect + QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(); + shadow->setBlurRadius(20); + shadow->setColor(QColor(24, 94, 224, 38)); // rgba(24, 94, 224, 0.15) + shadow->setOffset(0, 6); + m_tabBar->setGraphicsEffect(shadow); + + m_buttonGroup = new QButtonGroup(this); + m_buttonGroup->setExclusive(true); + + // Create stacked widget for content + m_stackedWidget = new QStackedWidget(); + + // Add widgets to layout + m_mainLayout->addWidget(m_tabBar); + m_mainLayout->addWidget(m_stackedWidget, 1); + + setupGlider(); +} + +void CustomTabWidget::setupGlider() +{ + m_glider = new QWidget(m_tabBar); + m_glider->setStyleSheet("QWidget {" + " background-color: #185ee0;" + " border-radius: 1px;" + "}"); + // Set initial size - will be updated in animateGlider + m_glider->setFixedSize(100, 2); // 2px height for bottom border effect + m_glider->lower(); // Make sure glider is behind tabs + + m_gliderAnimation = new QPropertyAnimation(m_glider, "pos"); + m_gliderAnimation->setDuration(250); + m_gliderAnimation->setEasingCurve(QEasingCurve::OutCubic); +} + +int CustomTabWidget::addTab(QWidget *widget, const QString &label) +{ + return addTab(widget, QIcon(), label); +} + +int CustomTabWidget::addTab(QWidget *widget, const QIcon &icon, + const QString &label) +{ + CustomTab *tab = new CustomTab(label, m_tabBar); + if (!icon.isNull()) { + tab->setIcon(icon); + } + connect(tab, &CustomTab::clicked, this, &CustomTabWidget::onTabClicked); + int index = m_tabs.count(); + m_tabs.append(tab); + m_widgets.append(widget); + + m_tabLayout->addWidget(tab); + m_stackedWidget->addWidget(widget); + m_buttonGroup->addButton(tab, index); + + // Set first tab as checked by default + if (index == 0) { + tab->setChecked(true); + } + + return index; +} + +void CustomTabWidget::setCurrentIndex(int index) +{ + if (index < 0 || index >= m_tabs.count() || index == m_currentIndex) { + return; + } + + m_currentIndex = index; + m_tabs[index]->setChecked(true); + m_stackedWidget->setCurrentIndex(index); + updateTabStyles(); + animateGlider(index); + + emit currentChanged(index); +} + +void CustomTabWidget::finalizeStyles() +{ + updateTabStyles(); + // Position glider for first tab + QTimer::singleShot(0, [this]() { animateGlider(0); }); +} + +int CustomTabWidget::currentIndex() const { return m_currentIndex; } + +QWidget *CustomTabWidget::widget(int index) const +{ + if (index < 0 || index >= m_widgets.count()) { + return nullptr; + } + return m_widgets[index]; +} + +void CustomTabWidget::setTabNotification(int index, int count) +{ + if (index >= 0 && index < m_tabs.count()) { + m_tabs[index]->setNotificationCount(count); + } +} + +void CustomTabWidget::onTabClicked() +{ + CustomTab *clickedTab = qobject_cast(sender()); + if (!clickedTab) + return; + + int index = m_tabs.indexOf(clickedTab); + if (index != -1) { + setCurrentIndex(index); + } +} + +void CustomTabWidget::animateGlider(int index) +{ + if (index < 0 || index >= m_tabs.count()) + return; + + CustomTab *targetTab = m_tabs[index]; + if (!targetTab) + return; + + // Get the actual position and size of the target tab + QPoint targetTabPos = targetTab->pos(); + QSize targetTabSize = targetTab->size(); + + // Set glider width to match tab width and height to 2px for bottom border + m_glider->setFixedSize(targetTabSize.width(), 2); + + // Position glider at the bottom of the target tab + int targetX = targetTabPos.x(); + int targetY = + targetTabPos.y() + targetTabSize.height() - 2; // Position at bottom + + m_gliderAnimation->stop(); + m_gliderAnimation->setStartValue(m_glider->pos()); + m_gliderAnimation->setEndValue(QPoint(targetX, targetY)); + m_gliderAnimation->start(); +} + +void CustomTabWidget::updateTabStyles() +{ + for (int i = 0; i < m_tabs.count(); ++i) { + CustomTab *tab = m_tabs[i]; + if (tab->isChecked()) { + tab->setStyleSheet("CustomTab {" + " color: #185ee0;" + " font-weight: 500;" + " font-size: 20px;" + " border: none;" + " border-radius: 27px;" + " background-color: transparent;" + "}" + "CustomTab:hover {" + " background-color: transparent;" + "}"); + } else { + tab->setStyleSheet("CustomTab {" + " color: #666;" + " font-weight: 500;" + " font-size: 20px;" + " border: none;" + " border-radius: 27px;" + " background-color: transparent;" + "}" + "CustomTab:hover {" + " color: #185ee0;" + " background-color: transparent;" + "}"); + } + } +} + +void CustomTabWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + // Update glider position when widget is resized + if (m_currentIndex >= 0 && m_currentIndex < m_tabs.count()) { + // Use a timer to ensure layout has been updated + QTimer::singleShot(0, [this]() { animateGlider(m_currentIndex); }); + } +} + +// #ifdef Q_OS_MAC +// void CustomTabWidget::ensureTitlebarIntegration() +// { +// // Ensure the tab bar maintains the correct height and margins for +// titlebar integration m_tabBar->setFixedHeight(98); // 70px + 28px +// titlebar height m_tabLayout->setContentsMargins(12, 36, 12, 8); // Add +// top margin for titlebar + +// // Ensure the parent window attribute is maintained +// if (QMainWindow *mainWindow = qobject_cast(window())) { +// mainWindow->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, +// false); +// } + +// // Update glider position after titlebar integration changes +// if (m_currentIndex >= 0 && m_currentIndex < m_tabs.count()) { +// QTimer::singleShot(0, [this]() { +// animateGlider(m_currentIndex); +// }); +// } +// } +// #endif \ No newline at end of file diff --git a/src/customtabwidget.h b/src/customtabwidget.h new file mode 100644 index 0000000..327b1b6 --- /dev/null +++ b/src/customtabwidget.h @@ -0,0 +1,72 @@ +#ifndef CUSTOMTABWIDGET_H +#define CUSTOMTABWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CustomTab : public QPushButton +{ + Q_OBJECT + +public: + explicit CustomTab(const QString &text, QWidget *parent = nullptr); + void setNotificationCount(int count); + void setIcon(const QIcon &icon); + +private: + QLabel *m_notificationLabel; + int m_notificationCount; + void updateNotificationDisplay(); + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +class CustomTabWidget : public QWidget +{ + Q_OBJECT + +public: + explicit CustomTabWidget(QWidget *parent = nullptr); + void finalizeStyles(); + int addTab(QWidget *widget, const QString &label); + int addTab(QWidget *widget, const QIcon &icon, const QString &label); + void setCurrentIndex(int index); + int currentIndex() const; + QWidget *widget(int index) const; + void setTabNotification(int index, int count); + +signals: + void currentChanged(int index); + +private slots: + void onTabClicked(); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private: + QHBoxLayout *m_tabLayout; + QVBoxLayout *m_mainLayout; + QWidget *m_tabBar; + QStackedWidget *m_stackedWidget; + QButtonGroup *m_buttonGroup; + QWidget *m_glider; + QPropertyAnimation *m_gliderAnimation; + QList m_tabs; + QList m_widgets; + int m_currentIndex; + + void setupGlider(); + void animateGlider(int index); + void updateTabStyles(); +}; + +#endif // CUSTOMTABWIDGET_H \ No newline at end of file diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 97c7efe..3a3df60 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -5,6 +5,7 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" #include +#include #include #include #include @@ -75,30 +76,50 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) QString::number(device->deviceInfo.diskInfo.totalDiskCapacity / (1000 * 1000 * 1000)) + " GB"); + + diskCapacityLabel->setAttribute(Qt::WA_StyledBackground, true); + diskCapacityLabel->setStyleSheet("background-color: rgba(0, 255, 30, 0.12);" + "padding: 4px;" + "border-radius: 4px;"); + m_chargingStatusLabel = new QLabel(device->deviceInfo.batteryInfo.isCharging ? "Charging" : "Not Charging"); - m_chargingStatusLabel->setStyleSheet("font-size: 1rem;"); + m_chargingStatusLabel->setStyleSheet( + device->deviceInfo.batteryInfo.isCharging ? "color: green;" + : "color: white;"); - m_chargingWattsLabel = - new QLabel(QString::number(device->deviceInfo.batteryInfo.watts) + "W"); - - m_cableTypeLabel = - new QLabel(device->deviceInfo.batteryInfo.usbConnectionType == - BatteryInfo::ConnectionType::USB - ? "USB" - : "USB-C"); + // Create the layout without a parent widget + QHBoxLayout *chargingLayout = new QHBoxLayout(); + chargingLayout->setContentsMargins(0, 0, 0, 0); + chargingLayout->setSpacing(5); + // Create icon label + m_lightningIconLabel = new QLabel(); + QPixmap lightningIcon(":/icons/MdiLightningBolt.png"); + QPixmap scaledIcon = lightningIcon.scaled(16, 16, Qt::KeepAspectRatio, + Qt::SmoothTransformation); + m_lightningIconLabel->setPixmap(scaledIcon); m_batteryWidget = new BatteryWidget(device->deviceInfo.batteryInfo.currentBatteryLevel, device->deviceInfo.batteryInfo.isCharging, this); + // Add the widgets to the new layout + chargingLayout->addWidget(m_chargingStatusLabel); + chargingLayout->addWidget(m_lightningIconLabel); + chargingLayout->addWidget(m_batteryWidget); + + m_chargingWattsWithCableTypeLabel = new QLabel( + QString::number(device->deviceInfo.batteryInfo.watts) + "W" + "/" + + (device->deviceInfo.batteryInfo.usbConnectionType == + BatteryInfo::ConnectionType::USB + ? "USB" + : "USB-C")); + headerLayout->addWidget(devProductType); headerLayout->addWidget(diskCapacityLabel); - headerLayout->addWidget(m_chargingStatusLabel); - headerLayout->addWidget(m_batteryWidget); - headerLayout->addWidget(m_chargingWattsLabel); - headerLayout->addWidget(m_cableTypeLabel); + headerLayout->addLayout(chargingLayout); + headerLayout->addWidget(m_chargingWattsWithCableTypeLabel); infoLayout->addWidget(headerWidget); // add spacer @@ -107,14 +128,47 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) // Add maximum stretch between header and grid infoLayout->addStretch(); - // Grid for device details + // --- Neumorphic Grid Widget --- + + // 1. Create a container for the shadows + QWidget *shadowContainer = new QWidget(); + // The container must be transparent to not hide the main window background + shadowContainer->setStyleSheet("background: transparent;"); + // Use a layout to make the gridWidget fill the container + QVBoxLayout *shadowLayout = new QVBoxLayout(shadowContainer); + shadowLayout->setContentsMargins(15, 15, 15, + 15); // Margins to make space for shadows + + // 2. Create the dark (bottom-right) shadow and apply to the container + QGraphicsDropShadowEffect *darkShadow = new QGraphicsDropShadowEffect(); + darkShadow->setBlurRadius(30); + darkShadow->setColor(QColor(0, 0, 0, 70)); // Dark, semi-transparent color + darkShadow->setOffset(0, 0); + shadowContainer->setGraphicsEffect(darkShadow); + + // 3. Create the grid widget (the main content) QWidget *gridWidget = new QWidget(); gridWidget->setObjectName("infoGrid"); - gridWidget->setStyleSheet("QWidget#infoGrid { " - " border: 1px solid #ccc; " - " border-radius: 6px; " - "}"); - QGridLayout *gridLayout = new QGridLayout(); + // Set a background color that matches the main window, with rounded corners + gridWidget->setStyleSheet( + "QWidget#infoGrid {" + " background-color: #2e2e2e;" // Match your window background + " border-radius: 8px;" + "}"); + + // 4. Create the light (top-left) shadow and apply to the grid widget + QGraphicsDropShadowEffect *lightShadow = new QGraphicsDropShadowEffect(); + lightShadow->setBlurRadius(30); + lightShadow->setColor( + QColor(255, 255, 255, 40)); // Light, semi-transparent color + lightShadow->setOffset(0, 0); + gridWidget->setGraphicsEffect(lightShadow); + + // Add gridWidget to the container's layout + shadowLayout->addWidget(gridWidget); + + QGridLayout *gridLayout = + new QGridLayout(gridWidget); // Set layout on gridWidget gridLayout->setSpacing(8); gridLayout->setColumnStretch(1, 1); // Allow value column to stretch gridLayout->setColumnStretch( @@ -228,7 +282,8 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) } } - infoLayout->addWidget(gridWidget); + infoLayout->addWidget( + shadowContainer); // Add the container to the main layout // infoLayout->addStretch(); // Pushes footer to the bottom // Footer @@ -310,14 +365,27 @@ void DeviceInfoWidget::updateBatteryInfo() else parseDeviceBattery(ioreg, d); /*UI*/ - m_chargingStatusLabel->setText(d.batteryInfo.isCharging ? "Charging" - : "Not Charging"); - m_chargingWattsLabel->setText(QString::number(d.batteryInfo.watts) + "W"); - m_cableTypeLabel->setText(d.batteryInfo.usbConnectionType == - BatteryInfo::ConnectionType::USB - ? "USB" - : "USB-C"); + updateChargingStatusIcon(); + m_chargingWattsWithCableTypeLabel->setText( + QString::number(d.batteryInfo.watts) + "W" + "/" + + (d.batteryInfo.usbConnectionType == BatteryInfo::ConnectionType::USB + ? "USB" + : "USB-C")); m_batteryWidget->updateContext(d.batteryInfo.isCharging, d.batteryInfo.currentBatteryLevel); +} + +void DeviceInfoWidget::updateChargingStatusIcon() +{ + if (m_device->deviceInfo.batteryInfo.isCharging) { + m_chargingStatusLabel->setText("Charging"); + m_chargingStatusLabel->setStyleSheet("color: green;"); + m_lightningIconLabel->show(); + + } else { + m_chargingStatusLabel->setText("Not Charging"); + m_chargingStatusLabel->setStyleSheet("color: white;"); + m_lightningIconLabel->hide(); + } } \ No newline at end of file diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 9b7a19b..22281bc 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -21,10 +21,11 @@ private: iDescriptorDevice *m_device; QTimer *m_updateTimer; void updateBatteryInfo(); + void updateChargingStatusIcon(); QLabel *m_chargingStatusLabel; - QLabel *m_chargingWattsLabel; - QLabel *m_cableTypeLabel; + QLabel *m_chargingWattsWithCableTypeLabel; BatteryWidget *m_batteryWidget; + QLabel *m_lightningIconLabel; }; #endif // DEVICEINFOWIDGET_H diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index 8bcc9c6..d16f974 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -5,37 +5,46 @@ #include "iDescriptor.h" #include "installedappswidget.h" #include -#include +#include #include DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent) : QWidget{parent}, device(device) { + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); - QWidget *centralWidget = new QWidget(this); - tabWidget = new QTabWidget(this); - tabWidget->tabBar()->hide(); - QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget); - mainLayout->addWidget(tabWidget); + stackedWidget = new QStackedWidget(this); + mainLayout->addWidget(stackedWidget); - FileExplorerWidget *explorer = new FileExplorerWidget(device, this); - explorer->setMinimumHeight(300); + // Create and add widgets to the stacked widget + DeviceInfoWidget *deviceInfoWidget = new DeviceInfoWidget(device, this); + InstalledAppsWidget *installedAppsWidget = + new InstalledAppsWidget(device, this); + GalleryWidget *galleryWidget = new GalleryWidget(device, this); + FileExplorerWidget *fileExplorerWidget = + new FileExplorerWidget(device, this); - GalleryWidget *gallery = new GalleryWidget(device, this); - gallery->setMinimumHeight(300); - setLayout(mainLayout); + // Set minimum heights + galleryWidget->setMinimumHeight(300); + fileExplorerWidget->setMinimumHeight(300); - tabWidget->addTab(new DeviceInfoWidget(device, this), ""); - tabWidget->addTab(new InstalledAppsWidget(device, this), ""); - unsigned int galleryIndex = tabWidget->addTab(gallery, ""); - tabWidget->addTab(explorer, ""); + // Add widgets to stack (index 0, 1, 2, 3) + stackedWidget->addWidget(deviceInfoWidget); // Index 0 - Info + stackedWidget->addWidget(installedAppsWidget); // Index 1 - Apps + stackedWidget->addWidget(galleryWidget); // Index 2 - Gallery + stackedWidget->addWidget(fileExplorerWidget); // Index 3 - Files - // TODO : one time ? - connect(tabWidget, &QTabWidget::currentChanged, this, - [this, galleryIndex, gallery](int index) { - if (index == galleryIndex) { + // Set default to Info tab + stackedWidget->setCurrentIndex(0); + + // Connect to current changed signal for lazy loading + connect(stackedWidget, &QStackedWidget::currentChanged, this, + [this, galleryWidget](int index) { + if (index == 2) { // Gallery tab qDebug() << "Switched to Gallery tab"; - gallery->load(); + galleryWidget->load(); } }); } @@ -43,13 +52,13 @@ DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent) void DeviceMenuWidget::switchToTab(const QString &tabName) { if (tabName == "Info") { - tabWidget->setCurrentIndex(0); + stackedWidget->setCurrentIndex(0); } else if (tabName == "Apps") { - tabWidget->setCurrentIndex(1); + stackedWidget->setCurrentIndex(1); } else if (tabName == "Gallery") { - tabWidget->setCurrentIndex(2); + stackedWidget->setCurrentIndex(2); } else if (tabName == "Files") { - tabWidget->setCurrentIndex(3); + stackedWidget->setCurrentIndex(3); } else { qDebug() << "Tab not found:" << tabName; } diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index c68e7d4..6363dbb 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -1,7 +1,7 @@ #ifndef DEVICEMENUWIDGET_H #define DEVICEMENUWIDGET_H #include "iDescriptor.h" -#include +#include #include class DeviceMenuWidget : public QWidget { @@ -12,8 +12,8 @@ public: void switchToTab(const QString &tabName); // ~DeviceMenuWidget(); private: - QTabWidget *tabWidget; // Pointer to the tab widget - iDescriptorDevice *device; // Pointer to the iDescriptor device + QStackedWidget *stackedWidget; // Pointer to the stacked widget + iDescriptorDevice *device; // Pointer to the iDescriptor device signals: }; diff --git a/src/devicesidebarwidget.cpp b/src/devicesidebarwidget.cpp index 79e28aa..4cd70d5 100644 --- a/src/devicesidebarwidget.cpp +++ b/src/devicesidebarwidget.cpp @@ -218,7 +218,7 @@ const std::string &DeviceSidebarItem::getDeviceUuid() const { return m_uuid; } DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setContentsMargins(10, 10, 10, 10); mainLayout->setSpacing(0); // Create scroll area diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index 025db7d..2eda016 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -1,5 +1,7 @@ #pragma once #include +#include + // A custom QGraphicsView that keeps the content fitted with aspect ratio on // resize class ResponsiveGraphicsView : public QGraphicsView @@ -18,4 +20,8 @@ protected: } QGraphicsView::resizeEvent(event); } -}; \ No newline at end of file +}; + +#ifdef Q_OS_MAC +void setupMacOSWindow(QMainWindow *window); +#endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7589afd..5867462 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5 +1,6 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" +#include "customtabwidget.h" #include "detailwindow.h" #include "settingswidget.h" #include @@ -7,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,6 +17,7 @@ #include "appswidget.h" #include "devicemanagerwidget.h" +#include "iDescriptor-ui.h" #include "iDescriptor.h" #include "libirecovery.h" #include "toolboxwidget.h" @@ -24,6 +27,7 @@ #include #include #include +#include #include #include @@ -33,6 +37,10 @@ #include "fileexplorerwidget.h" #include "jailbrokenwidget.h" #include "recoverydeviceinfowidget.h" +#include +#include +#include +#include #include #include #include @@ -116,16 +124,67 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); - setWindowTitle("iDescriptor"); + + // Create custom tab widget + m_customTabWidget = new CustomTabWidget(this); + m_customTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, + false); + + setContentsMargins(0, 0, 0, 0); +#ifdef Q_OS_MAC + setupMacOSWindow(this); + setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false); +#endif + setCentralWidget(m_customTabWidget); + + // Create device manager and stacked widget for main tab + m_mainStackedWidget = new QStackedWidget(); + + // No devices page + QWidget *noDevicesPage = new QWidget(); + QVBoxLayout *noDeviceLayout = new QVBoxLayout(noDevicesPage); + noDeviceLayout->addStretch(); + QHBoxLayout *labelLayout = new QHBoxLayout(); + labelLayout->addStretch(); + QLabel *noDeviceLabel = new QLabel("No devices detected"); + noDeviceLabel->setAlignment(Qt::AlignCenter); + labelLayout->addWidget(noDeviceLabel); + labelLayout->addStretch(); + noDeviceLayout->addLayout(labelLayout); + noDeviceLayout->addStretch(); m_deviceManager = new DeviceManagerWidget(this); - ui->stackedWidget->insertWidget(1, m_deviceManager); + + m_mainStackedWidget->addWidget(noDevicesPage); + m_mainStackedWidget->addWidget(m_deviceManager); + connect(m_deviceManager, &DeviceManagerWidget::updateNoDevicesConnected, this, &MainWindow::updateNoDevicesConnected); + // Add tabs with icons + QIcon deviceIcon(":/icons/MdiLightningBolt.png"); + m_customTabWidget->addTab(m_mainStackedWidget, deviceIcon, "iDevice"); + m_customTabWidget->addTab(new AppsWidget(this), "Apps"); + m_customTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); + + auto *jailbrokenWidget = new JailbrokenWidget(this); + m_customTabWidget->addTab(jailbrokenWidget, "Jailbroken"); + m_customTabWidget->finalizeStyles(); + + // todo: is this ok ? + auto connection = std::make_shared(); + *connection = + connect(m_customTabWidget, &CustomTabWidget::currentChanged, this, + [this, jailbrokenWidget, connection](int index) { + if (index == 3) { // Jailbroken tab + jailbrokenWidget->initWidget(); + QObject::disconnect(*connection); + } + }); + // settings button QPushButton *settingsButton = new QPushButton(); - settingsButton->setIcon(QIcon::fromTheme("preferences-system")); + settingsButton->setIcon(QIcon(":/icons/MingcuteSettings7Line.png")); settingsButton->setToolTip("Settings"); settingsButton->setFlat(true); settingsButton->setCursor(Qt::PointingHandCursor); @@ -141,23 +200,23 @@ MainWindow::MainWindow(QWidget *parent) settingsDialog.setLayout(layout); settingsDialog.exec(); }); - // ui->centralwidget->layout()->addWidget(settingsButton); - ui->mainTabWidget->widget(1)->layout()->addWidget(new AppsWidget(this)); - ui->mainTabWidget->widget(2)->layout()->addWidget(new ToolboxWidget(this)); - auto *jailbrokenWidget = new JailbrokenWidget(this); - ui->mainTabWidget->widget(3)->layout()->addWidget(jailbrokenWidget); + m_connectedDeviceCountLabel = new QLabel("iDescriptor: no devices"); + m_connectedDeviceCountLabel->setContentsMargins(5, 0, 5, 0); + m_connectedDeviceCountLabel->setStyleSheet( + "QLabel:hover { background-color : #13131319; }"); - // TODO: is this a good idea? - auto connection = std::make_shared(); - *connection = connect(ui->mainTabWidget, &QTabWidget::currentChanged, this, - [this, jailbrokenWidget, connection](int index) { - if (index == 3) { // Jailbroken tab - jailbrokenWidget->initWidget(); - QObject::disconnect(*connection); - } - }); + ui->statusbar->addWidget(m_connectedDeviceCountLabel); + + ui->statusbar->setContentsMargins(0, 0, 0, 0); + + // QWidget *statusSpacer = new QWidget(); + // statusSpacer->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Preferred); + // statusSpacer->setAttribute(Qt::WA_TransparentForMouseEvents); + // ui->statusbar->addWidget(statusSpacer); ui->statusbar->addPermanentWidget(settingsButton); + irecv_error_t res_recovery = irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr); @@ -169,16 +228,38 @@ MainWindow::MainWindow(QWidget *parent) if (res != IDEVICE_E_SUCCESS) { printf("ERROR: Unable to subscribe to device events.\n"); } + createMenus(); +} + +void MainWindow::createMenus() +{ + QMenu *actionsMenu = menuBar()->addMenu("&Actions"); + + // Add a custom "About" action for your app + QAction *aboutAct = new QAction("&About iDescriptor", this); + connect(aboutAct, &QAction::triggered, this, [=]() { + QMessageBox::about(this, "About iDescriptor", + "iDescriptor
" + "A modern device management tool."); + }); + actionsMenu->addAction(aboutAct); } void MainWindow::updateNoDevicesConnected() { qDebug() << "Is there no devices connected? " << AppContext::sharedInstance()->noDevicesConnected(); - if (AppContext::sharedInstance()->noDevicesConnected()) - return ui->stackedWidget->setCurrentIndex( - 0); // Show "No Devices Connected" page - ui->stackedWidget->setCurrentIndex(1); // Show device list page + if (AppContext::sharedInstance()->noDevicesConnected()) { + + m_connectedDeviceCountLabel->setText("iDescriptor: no devices"); + return m_mainStackedWidget->setCurrentIndex( + 0); // Show "No Devices Connected" page + } + int deviceCount = AppContext::sharedInstance()->getConnectedDeviceCount(); + m_connectedDeviceCountLabel->setText( + "iDescriptor: " + QString::number(deviceCount) + + (deviceCount == 1 ? " device" : " devices") + " connected"); + m_mainStackedWidget->setCurrentIndex(1); // Show device list page } void MainWindow::onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj) @@ -187,7 +268,7 @@ void MainWindow::onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj) // TODO: handle return; try { - ui->stackedWidget->setCurrentIndex(1); + m_mainStackedWidget->setCurrentIndex(1); RecoveryDeviceInfo *device = qobject_cast(recoveryDeviceInfoObj); if (!device) { diff --git a/src/mainwindow.h b/src/mainwindow.h index c9d1e68..61f73f7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,11 +1,12 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "customtabwidget.h" #include "devicemanagerwidget.h" #include "devicemenuwidget.h" #include "iDescriptor.h" #include "libirecovery.h" +#include #include -#include QT_BEGIN_NAMESPACE namespace Ui @@ -21,21 +22,20 @@ class MainWindow : public QMainWindow public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); - -signals: - void deviceAdded(QString udid); // Signal for device connections + void onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj); + void onRecoveryDeviceRemoved(QObject *deviceInfoObj); public slots: - void onRecoveryDeviceAdded( - QObject *device_info); // Slot for recovery device connections - void onRecoveryDeviceRemoved( - QObject *device_info); // Slot for recovery device disconnections void onDeviceInitFailed(QString udid, lockdownd_error_t err); - -private: void updateNoDevicesConnected(); +private: + void createMenus(); + Ui::MainWindow *ui; - DeviceManagerWidget *m_deviceManager; // Add this member + CustomTabWidget *m_customTabWidget; + DeviceManagerWidget *m_deviceManager; + QStackedWidget *m_mainStackedWidget; + QLabel *m_connectedDeviceCountLabel; }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c380de1..692c31e 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -10,142 +10,6 @@ 600 - - MainWindow - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - iDevice - - - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - No devices detected - - - Qt::AlignmentFlag::AlignCenter - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - Apps - - - - - - Toolbox - - - - - - Jailbroken - - - - - - - - - - - - @@ -157,6 +21,8 @@ + + diff --git a/src/platform/macos.mm b/src/platform/macos.mm new file mode 100644 index 0000000..8ccbc77 --- /dev/null +++ b/src/platform/macos.mm @@ -0,0 +1,42 @@ +#include +#include +#include + +void setupMacOSWindow(QMainWindow *window) +{ + + if (!window) { + qWarning() << "setupMacOSWindow: window is null"; + return; + } + + NSView *nativeView = reinterpret_cast(window->winId()); + NSWindow *nativeWindow = [nativeView window]; + + if (!nativeWindow) { + qWarning() << "setupMacOSWindow: native window is null"; + return; + } + + qDebug() << "Setting up macOS window styles"; + + window->setUnifiedTitleAndToolBarOnMac(true); + + [nativeWindow setStyleMask:[nativeWindow styleMask] | + NSWindowStyleMaskFullSizeContentView | + NSWindowTitleHidden]; + [nativeWindow setTitleVisibility:NSWindowTitleHidden]; + [nativeWindow setTitlebarAppearsTransparent:YES]; + + NSToolbar *toolbar = + [[NSToolbar alloc] initWithIdentifier:@"HiddenInsetToolbar"]; + toolbar.showsBaselineSeparator = + NO; // equivalent to HideToolbarSeparator: true + [nativeWindow setToolbar:toolbar]; + // [toolbar setVisible:NO]; + // todo : is it ok ? + [toolbar release]; + // [nativeWindow setContentBorderThickness:0.0 forEdge:NSMinYEdge]; + + [nativeWindow center]; +} \ No newline at end of file diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 9f77d2b..c4db591 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -71,6 +71,7 @@ ToolboxWidget::ToolboxWidget(QWidget *parent) : QWidget{parent} void ToolboxWidget::setupUI() { QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(5, 5, 5, 5); // Device selection section QHBoxLayout *deviceLayout = new QHBoxLayout(); @@ -80,6 +81,7 @@ void ToolboxWidget::setupUI() deviceLayout->addWidget(m_deviceLabel); deviceLayout->addWidget(m_deviceCombo); + deviceLayout->setContentsMargins(0, 0, 0, 0); deviceLayout->addStretch(); mainLayout->addLayout(deviceLayout); @@ -88,6 +90,9 @@ void ToolboxWidget::setupUI() 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(); m_gridLayout = new QGridLayout(m_contentWidget); @@ -164,7 +169,7 @@ QWidget *ToolboxWidget::createToolbox(const QString &title, QFrame *frame = new QFrame(); frame->setObjectName("toolboxFrame"); frame->setFrameStyle(QFrame::Box); - frame->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; " + frame->setStyleSheet("#toolboxFrame { " "border-radius: 5px; padding: 5px; }"); frame->setFixedSize(200, 120); @@ -261,13 +266,12 @@ void ToolboxWidget::updateToolboxStates() toolbox->setEnabled(enabled); if (enabled) { - toolbox->setStyleSheet("#toolboxFrame { border: 1px solid #ccc; " + toolbox->setStyleSheet("#toolboxFrame { " "border-radius: 5px; padding: 5px; }"); } else { - toolbox->setStyleSheet( - "#toolboxFrame { border: 1px solid #ccc; border-radius: 5px; " - "padding: " - "5px; background-color: #f0f0f0; color: #999; }"); + toolbox->setStyleSheet("#toolboxFrame { border-radius: 5px; " + "padding: 5px;" + "opacity: 0.45; }"); } } }