mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-22 03:45:51 +08:00
feat(gallery): add refresh functionality
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
<file>resources/icons/MaterialSymbolsCloseRounded.png</file>
|
<file>resources/icons/MaterialSymbolsCloseRounded.png</file>
|
||||||
<file>resources/icons/MaterialSymbolsLightKeyboardArrowUp.png</file>
|
<file>resources/icons/MaterialSymbolsLightKeyboardArrowUp.png</file>
|
||||||
<file>resources/icons/MaterialSymbolsLightKeyboardArrowDown.png</file>
|
<file>resources/icons/MaterialSymbolsLightKeyboardArrowDown.png</file>
|
||||||
|
<file>resources/icons/IcOutlineRefresh.png</file>
|
||||||
<file>qml/MapView.qml</file>
|
<file>qml/MapView.qml</file>
|
||||||
<file>resources/iphone.png</file>
|
<file>resources/iphone.png</file>
|
||||||
<file>resources/ios-version.png</file>
|
<file>resources/ios-version.png</file>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
+46
-19
@@ -70,22 +70,30 @@ GalleryWidget::GalleryWidget(const std::shared_ptr<iDescriptorDevice> device,
|
|||||||
// Add stacked widget to main layout
|
// Add stacked widget to main layout
|
||||||
setLayout(m_mainLayout);
|
setLayout(m_mainLayout);
|
||||||
|
|
||||||
QVBoxLayout *errorLayout = new QVBoxLayout();
|
connect(m_loadingWidget, &ZLoadingWidget::retryClicked, this,
|
||||||
errorLayout->setAlignment(Qt::AlignCenter);
|
&GalleryWidget::refresh);
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
setControlsEnabled(false); // Disable controls until album is selected
|
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()
|
void GalleryWidget::reload()
|
||||||
{
|
{
|
||||||
m_loaded = false;
|
m_loaded = false;
|
||||||
@@ -144,7 +152,7 @@ void GalleryWidget::setupControlsLayout()
|
|||||||
static_cast<int>(PhotoModel::VideosOnly));
|
static_cast<int>(PhotoModel::VideosOnly));
|
||||||
m_filterComboBox->setCurrentIndex(
|
m_filterComboBox->setCurrentIndex(
|
||||||
static_cast<int>(PhotoModel::All)); // Default to All
|
static_cast<int>(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);
|
m_filterComboBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
// Export buttons
|
// Export buttons
|
||||||
@@ -162,6 +170,13 @@ void GalleryWidget::setupControlsLayout()
|
|||||||
m_backButton->setMaximumWidth(30);
|
m_backButton->setMaximumWidth(30);
|
||||||
m_backButton->hide(); // Hidden initially
|
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 signals
|
||||||
connect(m_sortComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
connect(m_sortComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
this, &GalleryWidget::onSortOrderChanged);
|
this, &GalleryWidget::onSortOrderChanged);
|
||||||
@@ -180,6 +195,7 @@ void GalleryWidget::setupControlsLayout()
|
|||||||
|
|
||||||
// Add widgets to layout
|
// Add widgets to layout
|
||||||
m_controlsLayout->addWidget(m_backButton);
|
m_controlsLayout->addWidget(m_backButton);
|
||||||
|
m_controlsLayout->addWidget(m_refreshButton);
|
||||||
m_controlsLayout->addWidget(m_importButton);
|
m_controlsLayout->addWidget(m_importButton);
|
||||||
m_controlsLayout->addWidget(sortLabel);
|
m_controlsLayout->addWidget(sortLabel);
|
||||||
m_controlsLayout->addWidget(m_sortComboBox);
|
m_controlsLayout->addWidget(m_sortComboBox);
|
||||||
@@ -296,7 +312,7 @@ void GalleryWidget::onExportAll()
|
|||||||
// if we are exporting from album selection view
|
// if we are exporting from album selection view
|
||||||
if (m_loadingWidget->currentWidget() == m_albumSelectionWidget) {
|
if (m_loadingWidget->currentWidget() == m_albumSelectionWidget) {
|
||||||
|
|
||||||
// gel all available albums
|
// get all available albums
|
||||||
QStringList paths;
|
QStringList paths;
|
||||||
for (int row = 0; row < m_albumListView->model()->rowCount(); ++row) {
|
for (int row = 0; row < m_albumListView->model()->rowCount(); ++row) {
|
||||||
QModelIndex index = m_albumListView->model()->index(row, 0);
|
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);
|
auto *exportAlbum = new ExportAlbum(m_device, paths, this);
|
||||||
exportAlbum->show();
|
exportAlbum->show();
|
||||||
return;
|
return;
|
||||||
@@ -319,7 +341,6 @@ void GalleryWidget::onExportAll()
|
|||||||
QMessageBox::information(this, "No Items", "No items to export.");
|
QMessageBox::information(this, "No Items", "No items to export.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString message =
|
QString message =
|
||||||
QString("Export all %1 items currently shown?").arg(filePaths.size());
|
QString("Export all %1 items currently shown?").arg(filePaths.size());
|
||||||
int reply = QMessageBox::question(this, "Export All", message,
|
int reply = QMessageBox::question(this, "Export All", message,
|
||||||
@@ -463,6 +484,8 @@ void GalleryWidget::setupPhotoGalleryView()
|
|||||||
|
|
||||||
connect(m_listView, &QListView::customContextMenuRequested, this,
|
connect(m_listView, &QListView::customContextMenuRequested, this,
|
||||||
&GalleryWidget::onPhotoContextMenu);
|
&GalleryWidget::onPhotoContextMenu);
|
||||||
|
|
||||||
|
m_albumModel = new QStandardItemModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GalleryWidget::onError()
|
void GalleryWidget::onError()
|
||||||
@@ -475,12 +498,13 @@ void GalleryWidget::onError()
|
|||||||
|
|
||||||
void GalleryWidget::onAlbumListLoaded(const QList<QString> &dcimTree)
|
void GalleryWidget::onAlbumListLoaded(const QList<QString> &dcimTree)
|
||||||
{
|
{
|
||||||
|
qDebug() << "Albums loaded:" << dcimTree.size();
|
||||||
if (dcimTree.isEmpty()) {
|
if (dcimTree.isEmpty()) {
|
||||||
qDebug() << "DCIM seems to be empty or inaccessible";
|
m_loadingWidget->showError("No albums found on device");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_albumModel = new QStandardItemModel(this);
|
m_albumModel->clear();
|
||||||
|
|
||||||
for (const QString &albumName : dcimTree) {
|
for (const QString &albumName : dcimTree) {
|
||||||
auto *item = new QStandardItem(albumName);
|
auto *item = new QStandardItem(albumName);
|
||||||
@@ -518,14 +542,17 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath)
|
|||||||
|
|
||||||
connect(m_model, &PhotoModel::albumPathSet, this, [this]() {
|
connect(m_model, &PhotoModel::albumPathSet, this, [this]() {
|
||||||
// Switch to photo gallery view once album is loaded
|
// Switch to photo gallery view once album is loaded
|
||||||
|
m_loadingWidget->stop(false);
|
||||||
m_loadingWidget->switchToWidget(m_photoGalleryWidget);
|
m_loadingWidget->switchToWidget(m_photoGalleryWidget);
|
||||||
// Enable controls and show back button
|
// Enable controls and show back button
|
||||||
setControlsEnabled(true);
|
setControlsEnabled(true);
|
||||||
m_backButton->show();
|
m_backButton->show();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_model, &PhotoModel::timedOut, this, [this]() {
|
connect(m_model, &PhotoModel::albumPathSetFailed, this, [this]() {
|
||||||
m_loadingWidget->showError("Timed out loading album");
|
m_loadingWidget->stop(false);
|
||||||
|
m_backButton->show();
|
||||||
|
m_loadingWidget->showError("Failed to load album");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update export button states based on selection
|
// Update export button states based on selection
|
||||||
|
|||||||
+3
-1
@@ -63,6 +63,7 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void reload();
|
void reload();
|
||||||
|
void refresh();
|
||||||
void setupControlsLayout();
|
void setupControlsLayout();
|
||||||
void setupAlbumSelectionView();
|
void setupAlbumSelectionView();
|
||||||
void setupPhotoGalleryView();
|
void setupPhotoGalleryView();
|
||||||
@@ -84,8 +85,8 @@ private:
|
|||||||
QVBoxLayout *m_mainLayout;
|
QVBoxLayout *m_mainLayout;
|
||||||
QHBoxLayout *m_controlsLayout;
|
QHBoxLayout *m_controlsLayout;
|
||||||
ZLoadingWidget *m_loadingWidget;
|
ZLoadingWidget *m_loadingWidget;
|
||||||
QPushButton *m_retryButton;
|
|
||||||
QPushButton *m_importButton;
|
QPushButton *m_importButton;
|
||||||
|
|
||||||
// Album selection view
|
// Album selection view
|
||||||
QWidget *m_albumSelectionWidget = nullptr;
|
QWidget *m_albumSelectionWidget = nullptr;
|
||||||
QListView *m_albumListView = nullptr;
|
QListView *m_albumListView = nullptr;
|
||||||
@@ -102,6 +103,7 @@ private:
|
|||||||
QPushButton *m_exportSelectedButton;
|
QPushButton *m_exportSelectedButton;
|
||||||
QPushButton *m_exportAllButton;
|
QPushButton *m_exportAllButton;
|
||||||
ZIconWidget *m_backButton = nullptr;
|
ZIconWidget *m_backButton = nullptr;
|
||||||
|
ZIconWidget *m_refreshButton = nullptr;
|
||||||
|
|
||||||
// Export manager
|
// Export manager
|
||||||
ExportManager *m_exportManager;
|
ExportManager *m_exportManager;
|
||||||
|
|||||||
+34
-13
@@ -123,19 +123,39 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap,
|
void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap,
|
||||||
unsigned int row)
|
unsigned int rowHint)
|
||||||
{
|
{
|
||||||
// check bounds
|
Q_UNUSED(pixmap);
|
||||||
if (row < m_photos.size()) {
|
|
||||||
const PhotoInfo &photo = m_photos.at(row);
|
QMutexLocker locker(&m_mutex);
|
||||||
if (photo.filePath == path) {
|
|
||||||
QModelIndex idx = createIndex(row, 0);
|
int row = -1;
|
||||||
emit dataChanged(idx, idx, {Qt::DecorationRole});
|
|
||||||
}
|
// if row hint still valid and matches this path
|
||||||
|
if (rowHint < static_cast<unsigned int>(m_photos.size()) &&
|
||||||
|
m_photos.at(static_cast<int>(rowHint)).filePath == path) {
|
||||||
|
row = static_cast<int>(rowHint);
|
||||||
} else {
|
} else {
|
||||||
// FIXME: happens when we filter down to videos only
|
// fallback: search by path in current model
|
||||||
qDebug() << "Out of bounds in PhotoModel::onThumbnailReady";
|
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()
|
bool PhotoModel::populatePhotoPaths()
|
||||||
@@ -294,6 +314,7 @@ PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const
|
|||||||
return PhotoInfo::Image;
|
return PhotoInfo::Image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
void PhotoModel::setAlbumPath(const QString &albumPath)
|
void PhotoModel::setAlbumPath(const QString &albumPath)
|
||||||
{
|
{
|
||||||
qDebug() << "Setting new album path:" << albumPath;
|
qDebug() << "Setting new album path:" << albumPath;
|
||||||
@@ -313,9 +334,9 @@ void PhotoModel::setAlbumPath(const QString &albumPath)
|
|||||||
<< m_albumPath;
|
<< m_albumPath;
|
||||||
emit albumPathSet();
|
emit albumPathSet();
|
||||||
} else {
|
} else {
|
||||||
// qDebug() << "Failed to populate photo paths for album:"
|
qDebug() << "Failed to populate photo paths for album:"
|
||||||
// << m_albumPath;
|
<< m_albumPath;
|
||||||
// emit albumPathFailed();
|
emit albumPathSetFailed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -109,7 +109,7 @@ private slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void albumPathSet();
|
void albumPathSet();
|
||||||
void timedOut();
|
void albumPathSetFailed();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PHOTOMODEL_H
|
#endif // PHOTOMODEL_H
|
||||||
Reference in New Issue
Block a user