diff --git a/CMakeLists.txt b/CMakeLists.txt index f485b06..f12a017 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ set(APP_SOURCES ${CMAKE_SOURCE_DIR}/src/singleinstance.cpp ${CMAKE_SOURCE_DIR}/src/windoweventfilter.cpp ${CMAKE_SOURCE_DIR}/src/appcontroller.cpp + ${CMAKE_SOURCE_DIR}/src/gallery_manager.cpp ${CMAKE_SOURCE_DIR}/src/settingsmanager.cpp) set(APP_HEADERS @@ -44,6 +45,7 @@ set(APP_HEADERS ${CMAKE_SOURCE_DIR}/src/singleinstance.h ${CMAKE_SOURCE_DIR}/src/windoweventfilter.h ${CMAKE_SOURCE_DIR}/src/appcontroller.h + ${CMAKE_SOURCE_DIR}/src/gallery_manager.h ${CMAKE_SOURCE_DIR}/src/settingsmanager.h) qt5_add_resources(APP_RESOURCES diff --git a/src/appcontroller.cpp b/src/appcontroller.cpp index 9aad8e5..d8b5528 100644 --- a/src/appcontroller.cpp +++ b/src/appcontroller.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2024 Furi Labs +// Copyright (C) 2026 Furi Labs // // Authors: // Bardia Moshiri @@ -14,6 +14,7 @@ #include #include "appcontroller.h" #include "flashlightcontroller.h" +#include "gallery_manager.h" #include "filemanager.h" #include "thumbnailgenerator.h" #include "qrcodehandler.h" @@ -149,6 +150,7 @@ void AppController::setupEngine() m_engine->rootContext()->setContextProperty("flashlightController", m_flashlightController); m_engine->rootContext()->setContextProperty("fileManager", m_fileManager); m_engine->rootContext()->setContextProperty("thumbnailGenerator", m_thumbnailGenerator); + m_engine->rootContext()->setContextProperty("galleryManager", GalleryManager::get_gallery_manager_instance()); m_engine->rootContext()->setContextProperty("QRCodeHandler", m_qrCodeHandler); ZXingQt::registerQmlAndMetaTypes(); diff --git a/src/appcontroller.h b/src/appcontroller.h index dc0e6aa..355978b 100644 --- a/src/appcontroller.h +++ b/src/appcontroller.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2024 Furi Labs +// Copyright (C) 2026 Furi Labs // // Authors: // Bardia Moshiri diff --git a/src/filemanager.cpp b/src/filemanager.cpp index 8e1d1e5..e02af46 100644 --- a/src/filemanager.cpp +++ b/src/filemanager.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2023 Droidian Project -// Copyright (C) 2024 Furi Labs +// Copyright (C) 2026 Furi Labs // // Authors: // Bardia Moshiri @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -182,189 +183,59 @@ QString FileManager::getFileSize(const QString &fileUrl) { return QString::number(size) + " bytes"; } -// ***************** Picture Metadata ***************** +static qint64 fileMtimeMs(const QString &pathOrUrl) { + const QUrl u(pathOrUrl); + const QString path = u.isLocalFile() ? u.toLocalFile() : pathOrUrl; -easyexif::EXIFInfo FileManager::getPictureMetaData(const QString &fileUrl){ - - QString filePath = fileUrl; - int colonIndex = filePath.indexOf(':'); - - if (colonIndex != -1) { - filePath.remove(0, colonIndex + 1); - } - - QFile mediaFile(filePath); - if (!mediaFile.open(QIODevice::ReadOnly)) { - qDebug() << "Can't open media file: " << filePath; - } - - QByteArray fileContent = mediaFile.readAll(); - if (fileContent.isEmpty()) { - qDebug() << "Can't open media file: " << filePath; - } - mediaFile.close(); - - easyexif::EXIFInfo result; - int code = result.parseFrom(reinterpret_cast(fileContent.data()), fileContent.size()); - if (code) { - qWarning() << "Error parsing EXIF: code" << code; - } - - return result; -} - -QString FileManager::getTimeFormat() { - - QProcess process; - process.start("gsettings", QStringList() << "get" << "org.gnome.desktop.interface" << "clock-format"); - process.waitForFinished(); - - return process.readAllStandardOutput().trimmed(); + QFileInfo fi(path); + return fi.exists() ? fi.lastModified().toMSecsSinceEpoch() : 0; } -QString FileManager::getPictureDate(const QString &fileUrl) { - - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - std::tm tm = {}; - std::istringstream ss(metadata.DateTime); +static qint64 parseMkvDateEpochMs(const QString &mkvInfoOutput) { + const QStringList lines = mkvInfoOutput.split('\n'); - ss >> std::get_time(&tm, "%Y:%m:%d %H:%M:%S"); - if (ss.fail()) { - return "Invalid date/time"; - } - - char buffer[80]; - QString timeFormat = getTimeFormat(); - - if (timeFormat == "'24h'") { - strftime(buffer, sizeof(buffer), "%b %d, %Y \n %H:%M", &tm); - } else { - strftime(buffer, sizeof(buffer), "%b %d, %Y \n %I:%M %p", &tm); - } - - return QString::fromStdString(buffer); -} -QString FileManager::getCameraHardware(const QString &fileUrl) { - - if (fileUrl == "") { - return QString(""); - } + for (const QString &line : lines) { + if (!line.contains("Date")) + continue; - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); + const QString dateLine = line.trimmed(); + const int firstColon = dateLine.indexOf(':'); + if (firstColon < 0) + continue; - std::string make = metadata.Make; - std::string model = metadata.Model; + const QString dateTimeStr = dateLine.mid(firstColon + 1).trimmed(); - return QString("%1 %2").arg(QString::fromStdString(make)).arg(QString::fromStdString(model)); -} - -QString FileManager::getDimensions(const QString &fileUrl) { - - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - int width = metadata.ImageWidth; - int height = metadata.ImageHeight; - - return QString("%1 x %2").arg(QString::number(width)).arg(QString::number(height)); -} - -QString FileManager::getFStop(const QString &fileUrl) { // Aperture settings - - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - float fNumber = metadata.FNumber; - - return QString("f/%1").arg(QString::number(fNumber)); -} - -QString FileManager::getExposure(const QString &fileUrl) { // Exposure Time - - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - unsigned int exposure = static_cast(1.0 / metadata.ExposureTime); - - return QString("1/%1 s").arg(QString::number(exposure)); -} - -QString FileManager::getISOSpeed(const QString &fileUrl) { - - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - int iso = metadata.ISOSpeedRatings; - - return QString("ISO: %1").arg(QString::number(iso)); -} - -QString FileManager::getExposureBias(const QString &fileUrl) { - - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - float exposureBias = metadata.ExposureBiasValue; - - - return QString("%1 EV").arg(QString::number(exposureBias)); -} + QDateTime dt = QDateTime::fromString(dateTimeStr, "yyyy-MM-dd HH:mm:ss t"); + if (!dt.isValid()) { + dt = QDateTime::fromString(dateTimeStr, "yyyy-MM-dd HH:mm:ss 'UTC'"); + } + if (!dt.isValid()) { + dt = QDateTime::fromString(dateTimeStr, "yyyy-MM-dd HH:mm:ss"); + } -QString FileManager::focalLengthStandard(const QString &fileUrl) { + if (dt.isValid()) + return dt.toMSecsSinceEpoch(); - if (fileUrl == "") { - return QString(""); + break; } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - unsigned short focalLength = metadata.FocalLengthIn35mm; - - return QString("35mm focal length: %1 mm").arg(QString::number(focalLength)); + return 0; } -QString FileManager::focalLength(const QString &fileUrl) { +qint64 FileManager::getMediaEpochMs(const QString &fileUrl) { + const QString filePath = QUrl(fileUrl).toLocalFile(); + QFileInfo fi(filePath); + if (!fi.exists()) return -1; - if (fileUrl == "") { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - float focalLength = metadata.FocalLength; - - return QString("%1 mm").arg(QString::number(focalLength)); + return fi.lastModified().toMSecsSinceEpoch(); // Filesystem timestamp } -bool FileManager::getFlash(const QString &fileUrl) { +QString FileManager::getTimeFormat() { - if (fileUrl == "") { - return false; - } + QProcess process; + process.start("gsettings", QStringList() << "get" << "org.gnome.desktop.interface" << "clock-format"); + process.waitForFinished(); - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - return metadata.Flash == '1'; + return process.readAllStandardOutput().trimmed(); } // ***************** Video Metadata ***************** @@ -467,118 +338,7 @@ QString FileManager::getVideoDate(const QString &fileUrl) { return QString("Date not found."); } -QString FileManager::getVideoDimensions(const QString &fileUrl) { - QString output = runMkvInfo(fileUrl); - QStringList outputLines = output.split('\n'); - QString width, height; - - for (const QString &line : outputLines) { - if (line.contains("Pixel width")) { - width = line.split(':').last().trimmed(); - } else if (line.contains("Pixel height")) { - height = line.split(':').last().trimmed(); - } - } - - if (!width.isEmpty() && !height.isEmpty()) { - return QString("%1x%2").arg(width).arg(height); - } else { - qDebug() << "Dimensions not found."; - return QString("Dimensions not found."); - } -} - -QString FileManager::getDuration(const QString &fileUrl) { - QString output = runMkvInfo(fileUrl); - QStringList outputLines = output.split('\n'); - for (const QString &line : outputLines) { - if (line.contains("Duration")) { - QString string = QString( "Duration: ") + line.trimmed(); - qDebug() << string; - return string; - } - } - return QString("Duration not found."); -} - -QString FileManager::getMultiplexingApplication(const QString &fileUrl) { - QString output = runMkvInfo(fileUrl); - QStringList outputLines = output.split('\n'); - for (const QString &line : outputLines) { - if (line.contains("Multiplexing application:")) { - QString multiplexingApplication = line.split(':').last().trimmed(); - return QString("%1").arg(multiplexingApplication); - } - } - return QString("Multiplexing Application: Not found"); -} - -QString FileManager::getWritingApplication(const QString &fileUrl) { - QString output = runMkvInfo(fileUrl); - QStringList outputLines = output.split('\n'); - for (const QString &line : outputLines) { - if (line.contains("Writing application")) { - QString string = line.trimmed(); - return string; - } - } - qDebug() << "Writing application not found."; - return ""; -} - -QString FileManager::getDocumentType(const QString &fileUrl) { - QString output = runMkvInfo(fileUrl); - QStringList outputLines = output.split('\n'); - for (const QString &line : outputLines) { - if (line.contains("Document type:")) { - QString documentType = line.split(':').last().trimmed(); - return QString("File Type: %1").arg(documentType); - } - } - return QString("File Type: Not found"); -} - -QString FileManager::getCodecId(const QString &fileUrl) { - QString output = runMkvInfo(fileUrl); - QStringList outputLines = output.split('\n'); - for (const QString &line : outputLines) { - if (line.contains("Codec ID:")) { - QString codecId = line.split(':').last().trimmed(); - return QString("Codec ID: %1").arg(codecId); - } - } - return QString("Codec ID: Not found"); -} - // ***************** GPS Metadata ***************** - -bool FileManager::gpsMetadataAvailable(const QString &fileUrl) { - if (fileUrl == "") { - return false; - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - if (metadata.GeoLocation.Latitude != 0.0 || metadata.GeoLocation.Longitude != 0.0) { - return true; - } - - return false; -} - -QString FileManager::getGpsMetadata(const QString &fileUrl) { - - if (fileUrl == "" || !gpsMetadataAvailable(fileUrl)) { - return QString(""); - } - - easyexif::EXIFInfo metadata = getPictureMetaData(fileUrl); - - return QString("Latitude: %1\nLongitude: %2") - .arg(metadata.GeoLocation.Latitude, 0, 'f', 6) - .arg(metadata.GeoLocation.Longitude, 0, 'f', 6); -} - QStringList FileManager::getCurrentLocation() { QStringList coordinates; if (*m_locationAvailable == 1) { diff --git a/src/filemanager.h b/src/filemanager.h index b281cdf..3351147 100644 --- a/src/filemanager.h +++ b/src/filemanager.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2023 Droidian Project -// Copyright (C) 2024 Furi Labs +// Copyright (C) 2026 Furi Labs // // Authors: // Bardia Moshiri @@ -29,31 +29,13 @@ class FileManager : public QObject Q_INVOKABLE QString getConfigFile(); Q_INVOKABLE bool deleteImage(const QString &fileUrl); Q_INVOKABLE QString getFileSize(const QString &fileUrl); -// ***************** Picture Metada ***************** - Q_INVOKABLE easyexif::EXIFInfo getPictureMetaData(const QString &fileUrl); - Q_INVOKABLE QString getPictureDate(const QString &fileUrl); - Q_INVOKABLE QString getCameraHardware(const QString &fileUrl); - Q_INVOKABLE QString getDimensions(const QString &fileUrl); - Q_INVOKABLE QString getFStop(const QString &fileUrl); - Q_INVOKABLE QString getExposure(const QString &fileUrl); - Q_INVOKABLE QString getISOSpeed(const QString &fileUrl); - Q_INVOKABLE QString getExposureBias(const QString &fileUrl); - Q_INVOKABLE QString focalLengthStandard(const QString &fileUrl); - Q_INVOKABLE QString focalLength(const QString &fileUrl); - Q_INVOKABLE bool getFlash(const QString &fileUrl); + Q_INVOKABLE qint64 getMediaEpochMs(const QString &fileUrl); + // ***************** Video Metadata ***************** Q_INVOKABLE void getVideoMetadata(const QString &fileUrl); Q_INVOKABLE QString runMkvInfo(const QString &fileUrl); Q_INVOKABLE QString getVideoDate(const QString &fileUrl); - Q_INVOKABLE QString getVideoDimensions(const QString &fileUrl); - Q_INVOKABLE QString getDuration(const QString &fileUrl); - Q_INVOKABLE QString getMultiplexingApplication(const QString &fileUrl); - Q_INVOKABLE QString getWritingApplication(const QString &fileUrl); - Q_INVOKABLE QString getDocumentType(const QString &fileUrl); - Q_INVOKABLE QString getCodecId(const QString &fileUrl); // ***************** GPS Metadata ***************** - Q_INVOKABLE bool gpsMetadataAvailable(const QString &fileUrl); - Q_INVOKABLE QString getGpsMetadata(const QString &fileUrl); Q_INVOKABLE QStringList getCurrentLocation(); Q_INVOKABLE void turnOffGps(); Q_INVOKABLE void turnOnGps(); diff --git a/src/gallery_manager.cpp b/src/gallery_manager.cpp new file mode 100644 index 0000000..9dd627a --- /dev/null +++ b/src/gallery_manager.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2026 Furi Labs +// +// Authors: +// Joaquin Philco + +#include "gallery_manager.h" +#include +#include + +GalleryManager::GalleryManager(QObject *parent) + : QObject(parent) +{ +} + +GalleryManager::~GalleryManager() +{ +} + +GalleryManager *GalleryManager::get_gallery_manager_instance() +{ + static GalleryManager *instance = nullptr; + if (!instance) + instance = new GalleryManager; + return instance; +} + +void GalleryManager::onQmlRequestedScan() +{ + QProcess::startDetached("/usr/bin/io.FuriOS.Gallery", {}); +} \ No newline at end of file diff --git a/src/gallery_manager.h b/src/gallery_manager.h new file mode 100644 index 0000000..3becf05 --- /dev/null +++ b/src/gallery_manager.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2026 Furi Labs +// +// Authors: +// Joaquin Philco + +#pragma once + +#include + +class GalleryManager : public QObject { + Q_OBJECT + +public: + static GalleryManager *get_gallery_manager_instance(); + +public slots: + void onQmlRequestedScan(); + +private: + explicit GalleryManager(QObject *parent = nullptr); + ~GalleryManager(); + + GalleryManager(const GalleryManager &) = delete; + GalleryManager &operator=(const GalleryManager &) = delete; +}; \ No newline at end of file diff --git a/src/qml/Camera.qml b/src/qml/Camera.qml index 855e792..4b9677a 100644 --- a/src/qml/Camera.qml +++ b/src/qml/Camera.qml @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2024 Furi Labs +// Copyright (C) 2026 Furi Labs // // Authors: // Joaquin Philco @@ -194,10 +194,6 @@ Item { if (settings.soundOn === 1) { sound.play() } - - if (mediaView.index < 0) { - mediaView.folder = StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/furios-camera" - } } onImageSaved: { @@ -272,13 +268,13 @@ Item { pinch.target: camZoom pinch.maximumScale: camera.maximumDigitalZoom / camZoom.zoomFactor pinch.minimumScale: 0 - enabled: !mediaView.visible && !window.videoCaptured + enabled: !window.videoCaptured MouseArea { id: dragArea hoverEnabled: true anchors.fill: parent - enabled: !mediaView.visible && !window.videoCaptured + enabled: !window.videoCaptured property real startX: 0 property real startY: 0 property int swipeThreshold: 80 diff --git a/src/qml/MediaReview.qml b/src/qml/MediaReview.qml deleted file mode 100644 index 5fc4bd8..0000000 --- a/src/qml/MediaReview.qml +++ /dev/null @@ -1,862 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2023 Droidian Project -// Copyright (C) 2024 Furi Labs -// -// Authors: -// Bardia Moshiri -// Erik Inkinen -// Alexander Rutz -// Joaquin Philco - -import QtQuick 2.15 -import QtMultimedia 5.15 -import QtQuick.Layouts 1.15 -import QtQuick.Controls 2.15 -import Qt.labs.folderlistmodel 2.15 -import Qt.labs.platform 1.1 - -Rectangle { - id: viewRect - property int index: -1 - property var lastImg: index == -1 ? "" : imgModel.get(viewRect.index, "fileUrl") - property string currentFileUrl: viewRect.index === -1 || imgModel.get(viewRect.index, "fileUrl") === undefined ? "" : imgModel.get(viewRect.index, "fileUrl").toString() - property var folder: cslate.state == "VideoCapture" ? - StandardPaths.writableLocation(StandardPaths.MoviesLocation) + "/furios-camera" : - StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/furios-camera" - property var deletePopUp: "closed" - property bool hideMediaInfo: false - property bool showShapes: true - property real scalingRatio: scalingRatio - property var scaleRatio: 1.0 - property var vCenterOffsetValue: 0 - property var textSize: viewRect.height * 0.018 - property var mediaState: MediaPlayer.StoppedState - property var videoAudio: false - signal playbackRequest() - signal scanImageComponent() - signal closed - color: "black" - visible: false - - onCurrentFileUrlChanged: { - viewRect.scanImageComponent() - } - - function openPopup(title, body, buttons, data) { - popupTitle = title - popupBody = body - popupButtons = buttons - popupData = data - popupState = "opened" - } - - onVisibleChanged: { - if (!visible) qrCodeComponent.lastValidResult = null - } - - Connections { - target: thumbnailGenerator - - function onThumbnailGenerated(image) { - viewRect.lastImg = thumbnailGenerator.toQmlImage(image); - } - } - - FolderListModel { - id: imgModel - folder: viewRect.folder - showDirs: false - nameFilters: cslate.state == "VideoCapture" ? ["*.mkv"] : ["*.jpg"] - - onStatusChanged: { - if (imgModel.status == FolderListModel.Ready) { - viewRect.index = imgModel.count - 1 - if (cslate.state == "VideoCapture" && viewRect.currentFileUrl.endsWith(".mkv")) { - thumbnailGenerator.setVideoSource(viewRect.currentFileUrl) - } else { - viewRect.lastImg = viewRect.currentFileUrl - } - } - } - } - - Loader { - id: mediaLoader - anchors.fill: parent - visible: parent.visible - property string loadedComponentType: "" - - sourceComponent: { - if (viewRect.index === -1) { - loadedComponentType = "empty"; - return emptyDirectoryComponent; - } else if (imgModel.get(viewRect.index, "fileUrl") === undefined) { - loadedComponentType = "null"; - return null; - } else if (imgModel.get(viewRect.index, "fileUrl").toString().endsWith(".mkv")) { - loadedComponentType = "video"; - return videoOutputComponent; - } else { - loadedComponentType = "image"; - return imageComponent; - } - } - - onVisibleChanged: { - if (visible && loadedComponentType === "image") { - viewRect.scanImageComponent.connect(mediaLoader.item.scanImage) - } - } - } - - function swipeGesture(deltaX, deltaY, swipeThreshold) { - if (Math.abs(deltaY) > Math.abs(deltaX)) { - if (deltaY < -swipeThreshold) { // Upward swipe - viewRect.scaleRatio = 0.7 - viewRect.vCenterOffsetValue = -(viewRect.height * 0.19) - drawerAnimation.to = parent.height - 70 - metadataDrawer.height - drawerAnimation.start() - } else if (deltaY > swipeThreshold) { // Downward swipe - viewRect.scaleRatio = 1.0 - viewRect.vCenterOffsetValue = 0 - drawerAnimation.to = parent.height - drawerAnimation.start() - } - qrCodeComponent.lastValidResult = null - viewRect.hideMediaInfo = false - } else if (Math.abs(deltaX) > swipeThreshold) { - if (deltaX > 0) { // Swipe right - if (viewRect.index > 0) { - viewRect.index -= 1 - } - } else { // Swipe left - if (viewRect.index < imgModel.count - 1) { - viewRect.index += 1 - } - } - qrCodeComponent.lastValidResult = null - viewRect.hideMediaInfo = false - } else { // Touch - qrCodeComponent.lastValidResult = null - if (viewRect.hideMediaInfo === false) { - viewRect.scaleRatio = 1.0 - viewRect.vCenterOffsetValue = 0 - } else { - if (metadataDrawer.y <= 600) { - viewRect.scaleRatio = 0.5 - viewRect.vCenterOffsetValue = -(viewRect.height * 0.19) - } - } - - viewRect.hideMediaInfo = !viewRect.hideMediaInfo - } - } - - function scalePoint(point, readWidth, readHeight, imgWidth, imgHeight) { - var scaledX = (point.x / readWidth) * imgWidth; - var scaledY = (point.y / readHeight) * imgHeight; - return Qt.point(scaledX, scaledY); - } - - function getScaledCorners(position, readWidth, readHeight, imgWidth, imgHeight) { - var scaledTopLeft = scalePoint(position.topLeft, readWidth, readHeight, imgWidth, imgHeight); - var scaledTopRight = scalePoint(position.topRight, readWidth, readHeight, imgWidth, imgHeight); - var scaledBottomLeft = scalePoint(position.bottomLeft, readWidth, readHeight, imgWidth, imgHeight); - var scaledBottomRight = scalePoint(position.bottomRight, readWidth, readHeight, imgWidth, imgHeight); - - return { - topLeft: scaledTopLeft, - topRight: scaledTopRight, - bottomLeft: scaledBottomLeft, - bottomRight: scaledBottomRight - }; - } - - Component { - id: emptyDirectoryComponent - - Item { - id: emptyDirectoryItem - anchors.fill: parent - - Column { - anchors.centerIn: parent - - Button { - implicitWidth: 200 * viewRect.scalingRatio - implicitHeight: 200 * viewRect.scalingRatio - - icon.source: "icons/emblemPhotosSymbolic.svg" - icon.width: Math.round(200 * viewRect.scalingRatio) - icon.height: Math.round(200 * viewRect.scalingRatio) - icon.color: "#8a8a8f" - - anchors.horizontalCenter: parent.horizontalCenter - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - } - - Text { - text: "No media found" - color: "#8a8a8f" - font.bold: true - font.pixelSize: textSize * 2 - style: Text.Raised - elide: Text.ElideRight - anchors.horizontalCenter: parent.horizontalCenter - } - } - } - } - - Component { - id: imageComponent - Item { - id: imageContainer - anchors.fill: parent - property var positionData - - Image { - id: image - width: viewRect.width - autoTransform: true - transformOrigin: Item.Center - scale: viewRect.scaleRatio - fillMode: Image.PreserveAspectFit - smooth: true - source: (viewRect.currentFileUrl && !viewRect.currentFileUrl.endsWith(".mkv")) ? viewRect.currentFileUrl : "" - - y: parent.height / 2 - height / 2 + viewRect.vCenterOffsetValue - - Behavior on scale { - NumberAnimation { - duration: 300 - easing.type: Easing.InOutQuad - } - } - Behavior on y { - NumberAnimation{ - duration: 300 - easing.type: Easing.InOutQuad - } - } - } - - function scanImageURL() { - var result = QRCodeHandler.scanImageURL(currentFileUrl, image.width, image.height) - - if (result.isValid) { - imageContainer.positionData = getScaledCorners(result.position, result.readWidth, result.readHeight, image.width, image.height) - - qrCodeComponent.smoothedPosition = imageContainer.positionData - - qrCodeComponent.updateLowPass(imageContainer.positionData); - - qrCodeComponent.updateOBBFromImage(imageContainer.positionData, image.scale, image.x, image.y); - - qrCodeComponent.lastValidResult = result - } else { - qrCodeComponent.lastValidResult = null - } - } - - function scanImage() { - image.grabToImage(function(result) { - if (result.image) { - var qrCodeResult = QRCodeHandler.scanImage(result.image); - - if (qrCodeResult.isValid) { - imageContainer.positionData = getScaledCorners(qrCodeResult.position, qrCodeResult.readWidth, qrCodeResult.readHeight, image.width, image.height) - - qrCodeComponent.smoothedPosition = imageContainer.positionData - - qrCodeComponent.updateLowPass(imageContainer.positionData); - - qrCodeComponent.updateOBBFromImage(imageContainer.positionData, image.scale, image.x, image.y); - - qrCodeComponent.lastValidResult = qrCodeResult - } else { - qrCodeComponent.lastValidResult = null - } - } else { - console.error("Failed to grab image for QR scanning."); - qrCodeComponent.lastValidResult = null; - } - }); - } - - PinchArea { - id: pinchArea - anchors.fill: parent - pinch.target: image - pinch.maximumScale: 4 - pinch.minimumScale: 1 - enabled: viewRect.visible - property real initialX: 0 - property real initialY: 0 - - onPinchUpdated: { - if (pinchArea.pinch.center !== undefined) { - image.scale = pinchArea.pinch.scale - } - } - - MouseArea { - id: galleryDragArea - anchors.fill: parent - hoverEnabled: true - enabled: deletePopUp === "closed" - property real startX: 0 - property real startY: 0 - property int swipeThreshold: 30 - - onPressed: { - startX = mouse.x - startY = mouse.y - } - - onPressAndHold: { - scanImageURL() - } - - onReleased: { - var deltaX = mouse.x - startX - var deltaY = mouse.y - startY - - if (mediaMenu.visible){ - scanImageTimer.start() - } - - swipeGesture(deltaX, deltaY, swipeThreshold) - } - } - } - - Timer { - id: updateObbFromImageScan - interval: 1000 / 120 - running: !!qrCodeComponent.lastValidResult && !qrCodeComponent.viewfinder - onTriggered: { - if (!qrCodeComponent.lastValidResult) return - - qrCodeComponent.updateOBBFromImage(imageContainer.positionData, image.scale, image.x, image.y) - updateObbFromImageScan.start() - } - } - - Timer { - id: scanImageTimer - interval: 1000 / 120 - running: false; - repeat: false - onTriggered: { - if (mediaDate.visible){ - scanImageURL() - } - } - } - - onVisibleChanged: { - if (visible) { - scanImage() - } - } - } - } - - MetadataView { - id: metadataDrawer - width: parent.width - height: parent.height / 2.6 - y: parent.height - visible: !viewRect.hideMediaInfo - - PropertyAnimation { - id: drawerAnimation - target: metadataDrawer - property: "y" - duration: 500 - easing.type: Easing.InOutQuad - } - - currentFileUrl: viewRect.currentFileUrl - textSize: viewRect.textSize - scalingRatio: viewRect.scalingRatio - } - - Component { - id: videoOutputComponent - - Item { - id: videoItem - anchors.fill: parent - property bool firstFramePlayed: false - - signal playbackStateChange() - - Connections { - target: viewRect - function onPlaybackRequest() { - playbackStateChangeHandler() - } - } - - MediaPlayer { - id: mediaPlayer - autoPlay: true - muted: viewRect.videoAudio - source: viewRect.visible ? viewRect.currentFileUrl : "" - - onSourceChanged: { - firstFramePlayed = false; - play(); - } - - onPositionChanged: { - if (position > 0 && !firstFramePlayed) { - pause(); - firstFramePlayed = true; - } - } - - onStopped: { - viewRect.mediaState = MediaPlayer.StoppedState - playVideoButtonFrame.visible = true - } - - onPaused: { - viewRect.mediaState = MediaPlayer.PausedState - playVideoButtonFrame.visible = true - } - } - - VideoOutput { - anchors.fill: parent - source: mediaPlayer - visible: viewRect.currentFileUrl && viewRect.currentFileUrl.endsWith(".mkv") - } - - function playbackStateChangeHandler() { - if (mediaPlayer.playbackState === MediaPlayer.PlayingState) { - mediaPlayer.pause(); - } else { - if (viewRect.visible == true) { - mediaPlayer.play(); - } - } - } - - MouseArea { - id: galleryDragArea - anchors.fill: parent - hoverEnabled: true - enabled: deletePopUp === "closed" - property real startX: 0 - property real startY: 0 - property int swipeThreshold: 30 - - onPressed: { - startX = mouse.x - startY = mouse.y - } - - onReleased: { - var deltaX = mouse.x - startX - var deltaY = mouse.y - startY - - swipeGesture(deltaX, deltaY, swipeThreshold) - } - } - - Rectangle { - id: playVideoButtonFrame - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: videoItem.horizontalCenter - width: 90 * viewRect.scalingRatio - height: 90 * viewRect.scalingRatio - radius: 150 * viewRect.scaleRatio - color: "#2b292a" - - Button { - id: playVideoButton - icon.source: "icons/playVideo.svg" - icon.color: "#f0f0f0" - anchors.centerIn: parent - anchors.horizontalCenterOffset: 2 * viewRect.scalingRatio - icon.width: 50 * viewRect.scalingRatio - icon.height: 50 * viewRect.scalingRatio - visible: true - flat: true - highlighted: false - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (viewRect.currentFileUrl.endsWith(".mkv")) { - viewRect.mediaState = MediaPlayer.PlayingState - parent.visible = parent.visible ? false : true - playbackRequest() - } - } - } - } - } - } - - Button { - id: btnPrev - implicitWidth: 60 * viewRect.scalingRatio - implicitHeight: 60 * viewRect.scalingRatio - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - icon.source: "icons/goPreviousSymbolic.svg" - icon.width: Math.round(btnPrev.width * 0.5) - icon.height: Math.round(btnPrev.height * 0.5) - icon.color: "white" - Layout.alignment : Qt.AlignHCenter - - visible: viewRect.index > 0 && !viewRect.hideMediaInfo - enabled: deletePopUp === "closed" - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - if ((viewRect.index - 1) >= 0 ) { - viewRect.videoAudio = true - viewRect.index = viewRect.index - 1 - } - } - } - - Button { - id: btnNext - implicitWidth: 60 * viewRect.scalingRatio - implicitHeight: 60 * viewRect.scalingRatio - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - icon.source: "icons/goNextSymbolic.svg" - icon.width: Math.round(btnNext.width * 0.5) - icon.height: Math.round(btnNext.height * 0.5) - icon.color: "white" - Layout.alignment : Qt.AlignHCenter - - visible: viewRect.index < (imgModel.count - 1) && !viewRect.hideMediaInfo - enabled: deletePopUp === "closed" - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - if ((viewRect.index + 1) <= (imgModel.count - 1)) { - viewRect.videoAudio = true - viewRect.index = viewRect.index + 1 - } - } - } - - Item { - id: mediaMenu - - anchors.bottom: parent.bottom - width: parent.width - height: 70 * viewRect.scalingRatio - - Rectangle { - anchors.fill: parent - color: "#2b292a" - } - - Loader { - id: mediaMenuLoader - anchors.fill: parent - sourceComponent: viewRect.mediaState === MediaPlayer.PlayingState ? videoPlayingMenuComponent : videoStoppedMenuComponent - } - - Component { - id: videoStoppedMenuComponent - - Item { - id: videoStoppedMenuItem - - Button { - id: btnClose - icon.source: "icons/cameraVideoSymbolic.svg" - icon.width: parent.width * 0.13 - icon.height: parent.height * 0.8 - icon.color: "white" - enabled: deletePopUp === "closed" && viewRect.visible - anchors.left: parent.left - anchors.leftMargin: 20 * viewRect.scalingRatio - anchors.verticalCenter: parent.verticalCenter - - visible: !viewRect.hideMediaInfo - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - viewRect.visible = false - playbackRequest(); - viewRect.index = imgModel.count - 1 - viewRect.closed(); - } - } - - Button { - id: btnDelete - anchors.right: parent.right - anchors.rightMargin: 20 * viewRect.scalingRatio - anchors.verticalCenter: parent.verticalCenter - icon.source: "icons/editDeleteSymbolic.svg" - icon.width: parent.width * 0.1 - icon.height: parent.width * 0.1 - icon.color: "white" - visible: viewRect.index >= 0 && !viewRect.hideMediaInfo - Layout.alignment: Qt.AlignHCenter - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - deletePopUp = "opened" - confirmationPopup.open() - } - } - - Popup { - id: confirmationPopup - width: 200 * viewRect.scalingRatio - height: 80 * viewRect.scalingRatio - - background: Rectangle { - border.color: "#444" - color: "#2b292a" - radius: 10 * viewRect.scalingRatio - } - - closePolicy: Popup.NoAutoClose - x: (parent.width - width) / 2 - y: (parent.height - height) - - Column { - anchors.centerIn: parent - spacing: 10 - - Text { - text: viewRect.currentFileUrl.endsWith(".mkv") ? " Delete Video?": " Delete Photo?" - horizontalAlignment: parent.AlignHCenter - - anchors.margins: 5 * viewRect.scalingRatio - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - color: "white" - font.bold: true - style: Text.Raised - styleColor: "black" - font.pixelSize: textSize - } - - Row { - spacing: 20 * viewRect.scalingRatio - - Button { - text: "Yes" - palette.buttonText: "white" - font.pixelSize: viewRect.textSize - width: 60 * viewRect.scalingRatio - height: confirmationPopup.height * 0.6 - onClicked: { - var tempCurrUrl = viewRect.currentFileUrl - fileManager.deleteImage(tempCurrUrl) - viewRect.index = imgModel.count - deletePopUp = "closed" - confirmationPopup.close() - } - - background: Rectangle { - anchors.fill: parent - color: "#3d3d3d" - radius: 10 * viewRect.scalingRatio - } - } - - Button { - text: "No" - palette.buttonText: "white" - font.pixelSize: viewRect.textSize - width: 60 * viewRect.scalingRatio - height: confirmationPopup.height * 0.6 - onClicked: { - deletePopUp = "closed" - confirmationPopup.close() - } - - background: Rectangle { - anchors.fill: parent - color: "#3d3d3d" - radius: 10 * viewRect.scalingRatio - } - } - } - } - } - - Rectangle { - id: mediaIndexView - anchors.centerIn: parent - width: parent.width * 0.2 - height: parent.height - color: "transparent" - visible: viewRect.index >= 0 && !viewRect.hideMediaInfo - Text { - text: (viewRect.index + 1) + " / " + imgModel.count - - anchors.fill: parent - anchors.margins: 5 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - color: "white" - font.bold: true - style: Text.Raised - styleColor: "black" - font.pixelSize: textSize - } - } - } - } - - Component { - id: videoPlayingMenuComponent - - Item { - id: videoPlayingMenuItem - anchors.fill: parent - - Button { - icon.source: "icons/cameraVideoSymbolic.svg" - icon.width: parent.width * 0.13 - icon.height: parent.height * 0.8 - icon.color: "white" - enabled: deletePopUp === "closed" && viewRect.visible - anchors.left: parent.left - anchors.leftMargin: 20 * viewRect.scalingRatio - anchors.verticalCenter: parent.verticalCenter - - visible: !viewRect.hideMediaInfo - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - viewRect.visible = false - playbackRequest(); - viewRect.index = imgModel.count - 1 - viewRect.closed(); - } - } - - Button { - id: stopVideo - icon.source: "icons/pauseVideo.svg" - icon.width: parent.width * 0.13 - icon.height: parent.height * 0.7 - icon.color: "white" - enabled: viewRect.visible - anchors.centerIn: parent - - visible: !viewRect.hideMediaInfo - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - playbackRequest(); - } - } - - Button { - id: muteSoundButton - icon.source: !viewRect.videoAudio ? "icons/audioOn.svg" : "icons/audioOff.svg" - icon.width: parent.width * 0.12 - icon.height: parent.height * 0.7 - icon.color: "white" - enabled: viewRect.visible - anchors.right: parent.right - anchors.rightMargin: 20 * viewRect.scalingRatio - anchors.verticalCenter: parent.verticalCenter - - visible: !viewRect.hideMediaInfo - - background: Rectangle { - anchors.fill: parent - color: "transparent" - } - - onClicked: { - viewRect.videoAudio = !viewRect.videoAudio - } - } - - } - } - } - - Rectangle { - id: mediaDate - anchors.top: parent.top - width: parent.width - height: 60 * viewRect.scalingRatio - color: "#2b292a" - visible: viewRect.index >= 0 && !viewRect.hideMediaInfo - - Text { - id: date - text: { - if (!viewRect.visible || viewRect.index === -1) { - return "None" - } else { - if (viewRect.currentFileUrl.endsWith(".mkv")) { - return fileManager.getVideoDate(viewRect.currentFileUrl) - } else { - return fileManager.getPictureDate(viewRect.currentFileUrl) - } - } - } - - anchors.fill: parent - anchors.margins: 5 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - color: "white" - font.bold: true - style: Text.Raised - styleColor: "black" - font.pixelSize: viewRect.textSize - } - } - - QrCode { - id: qrCodeComponent - viewfinder: null - openPopupFunction: openPopup - } -} diff --git a/src/qml/MediaThumbnail.qml b/src/qml/MediaThumbnail.qml new file mode 100644 index 0000000..e8c9ba9 --- /dev/null +++ b/src/qml/MediaThumbnail.qml @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2026 Furi Labs +// +// Authors: +// Joaquin Philco + +import QtQuick 2.15 +import Qt.labs.folderlistmodel 2.15 +import Qt.labs.platform 1.1 + +Item { + id: root + property bool videoMode: false + property string folder: videoMode + ? StandardPaths.writableLocation(StandardPaths.MoviesLocation) + "/furios-camera" + : StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/furios-camera" + + property string lastFileUrl: "" + property string thumbnailSource: "" + + FolderListModel { + id: model + folder: root.folder + showDirs: false + nameFilters: root.videoMode ? ["*.mkv"] : ["*.jpg"] + + onStatusChanged: if (status === FolderListModel.Ready) root.pickLatest() + onCountChanged: root.pickLatest() + } + + function pickLatest() { + if (model.count <= 0) { + lastFileUrl = "" + thumbnailSource = "" + return + } + + var bestUrl = "" + var bestT = -1 + + for (var i = 0; i < model.count; i++) { + var u = model.get(i, "fileUrl") + if (!u) continue + var urlStr = u.toString() + + var t = fileManager.getMediaEpochMs(urlStr) + if (t > bestT) { + bestT = t + bestUrl = urlStr + } + } + + lastFileUrl = bestUrl + + if (!lastFileUrl) { + thumbnailSource = "" + return + } + + if (!videoMode) { + thumbnailSource = lastFileUrl + } else { + thumbnailSource = "" + if (typeof thumbnailGenerator !== "undefined" && thumbnailGenerator.setVideoSource) + thumbnailGenerator.setVideoSource(lastFileUrl) + } + } + + Connections { + target: (typeof thumbnailGenerator !== "undefined") ? thumbnailGenerator : null + function onThumbnailGenerated(image) { + if (!root.videoMode || !root.lastFileUrl) return + root.thumbnailSource = thumbnailGenerator.toQmlImage ? thumbnailGenerator.toQmlImage(image) : image + } + } +} diff --git a/src/qml/MetadataView.qml b/src/qml/MetadataView.qml deleted file mode 100644 index f0b2fcb..0000000 --- a/src/qml/MetadataView.qml +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -// Copyright (C) 2024 Droidian Project -// -// Authors: -// Joaquin Philco - -import QtQuick 2.15 -import QtMultimedia 5.15 -import QtQuick.Layouts 1.15 -import QtQuick.Controls 2.15 -import Qt.labs.folderlistmodel 2.15 -import Qt.labs.platform 1.1 - -Item { - id: metadataViewComponent - - property alias metadataView: metadataRect - property string currentFileUrl: currentFileUrl - property var scalingRatio: scalingRatio - property var textSize: textSize - property var avgMetadataContainerHeight: 60 * scalingRatio - - Component.onCompleted: { - updateMetadata(currentFileUrl); - } - - function updateMetadata(url) { - metadataModel.clear(); - if (url !== "") { - if (url.endsWith(".mkv")) { - metadataModel.append({title: "File Type", value: fileManager.getDocumentType(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "File Size", value: fileManager.getFileSize(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "Video Dimensions", value: fileManager.getVideoDimensions(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "Codec ID", value: fileManager.getCodecId(url), dataHeight: avgMetadataContainerHeight}); - } else { - metadataModel.append({title: "Maker, Model", value: fileManager.getCameraHardware(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "Image Dimensions", value: fileManager.getDimensions(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "File Size", value: fileManager.getFileSize(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "Aperture", value: fileManager.getFStop(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "Exposure", value: fileManager.getExposure(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "ISO", value: fileManager.getISOSpeed(url), dataHeight: avgMetadataContainerHeight}); - metadataModel.append({title: "Focal Length", value: fileManager.focalLength(url), dataHeight: avgMetadataContainerHeight}); - if(fileManager.gpsMetadataAvailable(url)) { - metadataModel.append({title: "GPS Data", value: fileManager.getGpsMetadata(url), dataHeight: 80 * scalingRatio}); - } - } - } - } - - ListModel { - id: metadataModel - } - - onCurrentFileUrlChanged: { - updateMetadata(currentFileUrl); - } - - Rectangle { - id: metadataRect - color: "#2b292a" - width: parent.width - height: 408 * scalingRatio - - Loader { - id: contentLoader - anchors.fill: parent - sourceComponent: metadataComponent - - onLoaded: metadataViewComponent.updateMetadata(metadataViewComponent.currentFileUrl); - } - - Component { - id: metadataComponent - Item { - ScrollView { - anchors.fill: parent - clip: true - - ScrollBar.horizontal: ScrollBar { interactive: false } - - ListView { - header: Rectangle { - height: 10 * metadataViewComponent.scalingRatio - width: metadataRect.width - color: "transparent" - } - - footer: Rectangle { - height: 10 * metadataViewComponent.scalingRatio - width: metadataRect.width - color: "transparent" - } - - width: parent.width - height: parent.height - spacing: 5 - model: metadataModel - delegate: Rectangle { - anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - width: 370 * scalingRatio - height: dataHeight - color: "transparent" - radius: 10 * scalingRatio - - Column { - anchors.fill: parent - spacing: 3 * metadataViewComponent.scalingRatio - Rectangle { - width: 370 * metadataViewComponent.scalingRatio - height: dataHeight - color: "#3d3d3d" - radius: (10 * metadataViewComponent.scalingRatio) - Text { - text: title - color: "#8a8a8f" - font.bold: true - font.pixelSize: metadataViewComponent.textSize - 2 - style: Text.Raised - styleColor: "black" - elide: Text.ElideRight - anchors { - left: parent.left - top: parent.top - leftMargin: 10 * metadataViewComponent.scalingRatio - topMargin: 10 * metadataViewComponent.scalingRatio - } - } - - Text { - text: value - color: "white" - font.pixelSize: metadataViewComponent.textSize - style: Text.Raised - styleColor: "black" - elide: Text.ElideRight - anchors { - left: parent.left - bottom: parent.bottom - leftMargin: 10 * metadataViewComponent.scalingRatio - bottomMargin: 10 * metadataViewComponent.scalingRatio - } - } - } - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/src/qml/main.qml b/src/qml/main.qml index 50b50f2..7c79726 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2023 Droidian Project -// Copyright (C) 2024 Furi Labs +// Copyright (C) 2026 Furi Labs // // Authors: // Bardia Moshiri @@ -422,7 +422,6 @@ ApplicationWindow { anchors.bottomMargin: 43 * window.scalingRatio height: 150 * window.scalingRatio width: parent.width - visible: !mediaView.visible ToolTip { id: copiedTip @@ -748,7 +747,7 @@ ApplicationWindow { transformOrigin: Item.Center fillMode: Image.Stretch smooth: false - source: (cslate.state == "PhotoCapture") ? mediaView.lastImg : "" + source: mediaThumbnail.thumbnailSource scale: Math.min(parent.width / width, parent.height / height) } } @@ -761,7 +760,7 @@ ApplicationWindow { MouseArea { anchors.fill: parent onClicked: { - mediaView.visible = true; + galleryManager.onQmlRequestedScan(); } } } @@ -809,7 +808,7 @@ ApplicationWindow { anchors.centerIn: parent height: shutterBtnFrame.height width: height - enabled: cslate.state === "PhotoCapture" && !mediaView.visible + enabled: cslate.state === "PhotoCapture" background: Rectangle { id: camerabtn @@ -857,7 +856,7 @@ ApplicationWindow { id: shutterBtn anchors.fill: parent.fill anchors.centerIn: parent - enabled: cslate.state === "PhotoCapture" && !mediaView.visible + enabled: cslate.state === "PhotoCapture" icon.source: preCaptureTimer.running ? "" : configBar.currIndex === 0 ? "icons/windowCloseSymbolic.svg" : "icons/timer.svg" icon.color: "white" icon.width: shutterBtnFrame.width - 10 @@ -916,7 +915,6 @@ ApplicationWindow { Button { id: videoBtn anchors.fill: parent - enabled: !mediaView.visible Rectangle { id: redCircle @@ -1034,13 +1032,9 @@ ApplicationWindow { } } - MediaReview { - id: mediaView - anchors.fill: parent - onClosed: window.startCamera() - focus: visible - - scalingRatio: window.scalingRatio + MediaThumbnail { + id: mediaThumbnail + videoMode: (cslate.state === "VideoCapture") } Rectangle { @@ -1313,7 +1307,7 @@ ApplicationWindow { property var opened: 0; property var aspectRatioOpened: 0; property var currIndex: timerTumbler.currentIndex - visible: !mediaView.visible && !window.videoCaptured + visible: !window.videoCaptured RowLayout { anchors.horizontalCenter: parent.horizontalCenter @@ -1561,7 +1555,6 @@ ApplicationWindow { anchors.top: parent.top anchors.topMargin: 10 * window.scalingRatio - visible: !mediaView.visible flat: true down: false diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index 5e7202d..1ab6024 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -2,9 +2,8 @@ main.qml ZoomControl.qml - MediaReview.qml QrCode.qml - MetadataView.qml Camera.qml + MediaThumbnail.qml