From dbec7a2e49bf9496cfbc502e8a6395e3161d7809 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Tue, 7 Apr 2026 18:21:19 +0000 Subject: [PATCH] feat(gallery): add refresh functionality --- resources.qrc | 1 + resources/icons/IcOutlineRefresh.png | Bin 0 -> 14467 bytes src/gallerywidget.cpp | 65 +++++++++++++++++++-------- src/gallerywidget.h | 4 +- src/photomodel.cpp | 47 +++++++++++++------ src/photomodel.h | 2 +- 6 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 resources/icons/IcOutlineRefresh.png diff --git a/resources.qrc b/resources.qrc index 0b55cfb..b170140 100644 --- a/resources.qrc +++ b/resources.qrc @@ -42,6 +42,7 @@ resources/icons/MaterialSymbolsCloseRounded.png resources/icons/MaterialSymbolsLightKeyboardArrowUp.png resources/icons/MaterialSymbolsLightKeyboardArrowDown.png + resources/icons/IcOutlineRefresh.png qml/MapView.qml resources/iphone.png resources/ios-version.png diff --git a/resources/icons/IcOutlineRefresh.png b/resources/icons/IcOutlineRefresh.png new file mode 100644 index 0000000000000000000000000000000000000000..a5575603912daf60b8d7e2b8d80c2782de18b4fb GIT binary patch literal 14467 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 YSxUmX3&d{<;1@{yw7&YwlVrdD0}I{-3IG5A literal 0 HcmV?d00001 diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 2c107b4..9921e0a 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -70,22 +70,30 @@ GalleryWidget::GalleryWidget(const std::shared_ptr device, // Add stacked widget to main layout setLayout(m_mainLayout); - QVBoxLayout *errorLayout = new QVBoxLayout(); - errorLayout->setAlignment(Qt::AlignCenter); - QLabel *errorLabel = new QLabel("Failed to load albums."); - errorLabel->setStyleSheet("font-weight: bold; color: red;"); - errorLayout->addWidget(errorLabel); - m_retryButton = new QPushButton("Retry", this); - errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); - m_loadingWidget->setupErrorWidget(errorLayout); - connect(m_retryButton, &QPushButton::clicked, this, [this]() { - m_loadingWidget->showLoading(); - QTimer::singleShot(100, this, &GalleryWidget::reload); - }); + connect(m_loadingWidget, &ZLoadingWidget::retryClicked, this, + &GalleryWidget::refresh); setControlsEnabled(false); // Disable controls until album is selected } +void GalleryWidget::refresh() +{ + bool inAlbumSelection = + (m_loadingWidget->currentWidget() == m_albumSelectionWidget); + + m_loadingWidget->showLoading(); + // refresh the album list + if (inAlbumSelection) { + qDebug() << "Refreshing album list..."; + QTimer::singleShot(100, this, &GalleryWidget::reload); + return; + } + if (m_model) { + qDebug() << "Refreshing current album:" << m_currentAlbumPath; + m_model->setAlbumPath(m_currentAlbumPath); + } +} + void GalleryWidget::reload() { m_loaded = false; @@ -144,7 +152,7 @@ void GalleryWidget::setupControlsLayout() static_cast(PhotoModel::VideosOnly)); m_filterComboBox->setCurrentIndex( static_cast(PhotoModel::All)); // Default to All - m_filterComboBox->setMinimumWidth(100); // Ensure text fits + m_filterComboBox->setMinimumWidth(90); // Ensure text fits m_filterComboBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // Export buttons @@ -162,6 +170,13 @@ void GalleryWidget::setupControlsLayout() m_backButton->setMaximumWidth(30); m_backButton->hide(); // Hidden initially + // Refresh button + m_refreshButton = new ZIconWidget( + QIcon(":/resources/icons/IcOutlineRefresh.png"), "Refresh Album"); + m_refreshButton->setMaximumWidth(30); + connect(m_refreshButton, &ZIconWidget::clicked, this, + &GalleryWidget::refresh); + // Connect signals connect(m_sortComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &GalleryWidget::onSortOrderChanged); @@ -180,6 +195,7 @@ void GalleryWidget::setupControlsLayout() // Add widgets to layout m_controlsLayout->addWidget(m_backButton); + m_controlsLayout->addWidget(m_refreshButton); m_controlsLayout->addWidget(m_importButton); m_controlsLayout->addWidget(sortLabel); m_controlsLayout->addWidget(m_sortComboBox); @@ -296,7 +312,7 @@ void GalleryWidget::onExportAll() // if we are exporting from album selection view if (m_loadingWidget->currentWidget() == m_albumSelectionWidget) { - // gel all available albums + // get all available albums QStringList paths; for (int row = 0; row < m_albumListView->model()->rowCount(); ++row) { QModelIndex index = m_albumListView->model()->index(row, 0); @@ -305,6 +321,12 @@ void GalleryWidget::onExportAll() } } + if (paths.isEmpty()) { + QMessageBox::information(this, "No Albums", + "No albums available for export."); + return; + } + auto *exportAlbum = new ExportAlbum(m_device, paths, this); exportAlbum->show(); return; @@ -319,7 +341,6 @@ void GalleryWidget::onExportAll() QMessageBox::information(this, "No Items", "No items to export."); return; } - QString message = QString("Export all %1 items currently shown?").arg(filePaths.size()); int reply = QMessageBox::question(this, "Export All", message, @@ -463,6 +484,8 @@ void GalleryWidget::setupPhotoGalleryView() connect(m_listView, &QListView::customContextMenuRequested, this, &GalleryWidget::onPhotoContextMenu); + + m_albumModel = new QStandardItemModel(this); } void GalleryWidget::onError() @@ -475,12 +498,13 @@ void GalleryWidget::onError() void GalleryWidget::onAlbumListLoaded(const QList &dcimTree) { + qDebug() << "Albums loaded:" << dcimTree.size(); if (dcimTree.isEmpty()) { - qDebug() << "DCIM seems to be empty or inaccessible"; + m_loadingWidget->showError("No albums found on device"); return; } - m_albumModel = new QStandardItemModel(this); + m_albumModel->clear(); for (const QString &albumName : dcimTree) { auto *item = new QStandardItem(albumName); @@ -518,14 +542,17 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) connect(m_model, &PhotoModel::albumPathSet, this, [this]() { // Switch to photo gallery view once album is loaded + m_loadingWidget->stop(false); m_loadingWidget->switchToWidget(m_photoGalleryWidget); // Enable controls and show back button setControlsEnabled(true); m_backButton->show(); }); - connect(m_model, &PhotoModel::timedOut, this, [this]() { - m_loadingWidget->showError("Timed out loading album"); + connect(m_model, &PhotoModel::albumPathSetFailed, this, [this]() { + m_loadingWidget->stop(false); + m_backButton->show(); + m_loadingWidget->showError("Failed to load album"); }); // Update export button states based on selection diff --git a/src/gallerywidget.h b/src/gallerywidget.h index d97434f..f3a6357 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -63,6 +63,7 @@ private slots: private: void reload(); + void refresh(); void setupControlsLayout(); void setupAlbumSelectionView(); void setupPhotoGalleryView(); @@ -84,8 +85,8 @@ private: QVBoxLayout *m_mainLayout; QHBoxLayout *m_controlsLayout; ZLoadingWidget *m_loadingWidget; - QPushButton *m_retryButton; QPushButton *m_importButton; + // Album selection view QWidget *m_albumSelectionWidget = nullptr; QListView *m_albumListView = nullptr; @@ -102,6 +103,7 @@ private: QPushButton *m_exportSelectedButton; QPushButton *m_exportAllButton; ZIconWidget *m_backButton = nullptr; + ZIconWidget *m_refreshButton = nullptr; // Export manager ExportManager *m_exportManager; diff --git a/src/photomodel.cpp b/src/photomodel.cpp index b35b00e..5d5a077 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -123,19 +123,39 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const } void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap, - unsigned int row) + unsigned int rowHint) { - // check bounds - if (row < m_photos.size()) { - const PhotoInfo &photo = m_photos.at(row); - if (photo.filePath == path) { - QModelIndex idx = createIndex(row, 0); - emit dataChanged(idx, idx, {Qt::DecorationRole}); - } + Q_UNUSED(pixmap); + + QMutexLocker locker(&m_mutex); + + int row = -1; + + // if row hint still valid and matches this path + if (rowHint < static_cast(m_photos.size()) && + m_photos.at(static_cast(rowHint)).filePath == path) { + row = static_cast(rowHint); } else { - // FIXME: happens when we filter down to videos only - qDebug() << "Out of bounds in PhotoModel::onThumbnailReady"; + // fallback: search by path in current model + for (int i = 0; i < m_photos.size(); ++i) { + if (m_photos.at(i).filePath == path) { + row = i; + break; + } + } } + + if (row == -1) { + // Thumbnail arrived for an item that is no longer in the model + qDebug() << "PhotoModel::onThumbnailReady: path not in current model:" + << path << "(rowHint =" << rowHint + << ", size =" << m_photos.size() << ")"; + return; + } + + QModelIndex idx = createIndex(row, 0); + locker.unlock(); // avoid holding mutex while emitting + emit dataChanged(idx, idx, {Qt::DecorationRole}); } bool PhotoModel::populatePhotoPaths() @@ -294,6 +314,7 @@ PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const return PhotoInfo::Image; } +int count = 0; void PhotoModel::setAlbumPath(const QString &albumPath) { qDebug() << "Setting new album path:" << albumPath; @@ -313,9 +334,9 @@ void PhotoModel::setAlbumPath(const QString &albumPath) << m_albumPath; emit albumPathSet(); } else { - // qDebug() << "Failed to populate photo paths for album:" - // << m_albumPath; - // emit albumPathFailed(); + qDebug() << "Failed to populate photo paths for album:" + << m_albumPath; + emit albumPathSetFailed(); } }); } diff --git a/src/photomodel.h b/src/photomodel.h index 3dd348b..2d3cdaa 100644 --- a/src/photomodel.h +++ b/src/photomodel.h @@ -109,7 +109,7 @@ private slots: signals: void albumPathSet(); - void timedOut(); + void albumPathSetFailed(); }; #endif // PHOTOMODEL_H \ No newline at end of file