diff --git a/platform/ios/Info.plist.in b/platform/ios/Info.plist.in
index 960877c135..4f9c60cb7a 100644
--- a/platform/ios/Info.plist.in
+++ b/platform/ios/Info.plist.in
@@ -131,7 +131,7 @@
LSSupportsOpeningDocumentsInPlace
-
+
LSApplicationQueriesSchemes
@@ -148,5 +148,197 @@
+
+
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeName
+ QGIS project
+ CFBundleTypeRole
+ Editor
+ LSHandlerRank
+ Owner
+ LSItemContentTypes
+
+ org.qgis.qgs
+ org.qgis.qgz
+
+
+
+ CFBundleTypeName
+ Geospatial dataset
+ CFBundleTypeRole
+ Editor
+ LSHandlerRank
+ Owner
+ LSItemContentTypes
+
+ org.opengeospatial.geopackage
+ com.mapbox.mbtiles
+ org.geojson.geojson
+ com.google.earth.kml
+ com.google.earth.kmz
+ com.topografix.gpx
+
+
+
+ CFBundleTypeName
+ Supported document
+ CFBundleTypeRole
+ Viewer
+ LSHandlerRank
+ Alternate
+ LSItemContentTypes
+
+ com.adobe.pdf
+ public.zip-archive
+ public.json
+ public.tiff
+ public.jpeg-2000
+
+
+
+
+
+ UTImportedTypeDeclarations
+
+
+ UTTypeIdentifier
+ org.qgis.qgs
+ UTTypeDescription
+ QGIS project
+ UTTypeConformsTo
+
+ public.xml
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ qgs
+
+
+
+
+ UTTypeIdentifier
+ org.qgis.qgz
+ UTTypeDescription
+ QGIS compressed project
+ UTTypeConformsTo
+
+ public.zip-archive
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ qgz
+
+
+
+
+ UTTypeIdentifier
+ org.opengeospatial.geopackage
+ UTTypeDescription
+ GeoPackage
+ UTTypeConformsTo
+
+ public.data
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ gpkg
+
+
+
+
+ UTTypeIdentifier
+ com.mapbox.mbtiles
+ UTTypeDescription
+ MBTiles
+ UTTypeConformsTo
+
+ public.data
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ mbtiles
+
+
+
+
+ UTTypeIdentifier
+ org.geojson.geojson
+ UTTypeDescription
+ GeoJSON
+ UTTypeConformsTo
+
+ public.json
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ geojson
+
+
+
+
+ UTTypeIdentifier
+ com.google.earth.kml
+ UTTypeDescription
+ KML
+ UTTypeConformsTo
+
+ public.xml
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ kml
+
+
+
+
+ UTTypeIdentifier
+ com.google.earth.kmz
+ UTTypeDescription
+ KMZ
+ UTTypeConformsTo
+
+ public.zip-archive
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ kmz
+
+
+
+
+ UTTypeIdentifier
+ com.topografix.gpx
+ UTTypeDescription
+ GPX
+ UTTypeConformsTo
+
+ public.xml
+
+ UTTypeTagSpecification
+
+ public.filename-extension
+
+ gpx
+
+
+
+
diff --git a/src/core/platforms/ios/iosplatformutilities.h b/src/core/platforms/ios/iosplatformutilities.h
index f3f8306b86..f9b4863bc3 100644
--- a/src/core/platforms/ios/iosplatformutilities.h
+++ b/src/core/platforms/ios/iosplatformutilities.h
@@ -39,6 +39,7 @@ class IosPlatformUtilities : public PlatformUtilities
void importProjectFolder() const override;
void importProjectArchive() const override;
void importDatasets() const override;
+ void importFile( const QString &path ) const override;
void exportDatasetTo( const QString &path ) const override;
void exportFolderTo( const QString &path ) const override;
void sendDatasetTo( const QString &path ) const override;
diff --git a/src/core/platforms/ios/iosplatformutilities.mm b/src/core/platforms/ios/iosplatformutilities.mm
index f98d9020e7..cb4178d318 100644
--- a/src/core/platforms/ios/iosplatformutilities.mm
+++ b/src/core/platforms/ios/iosplatformutilities.mm
@@ -75,7 +75,8 @@ - (void)documentInteractionControllerDidEndPreview:
PlatformUtilities::Capabilities IosPlatformUtilities::capabilities() const {
PlatformUtilities::Capabilities capabilities =
Capabilities() | NativeCamera | AdjustBrightness | FilePicker |
- CustomImport | CustomSend | CustomExport | UpdateProjectFromArchive;
+ CustomImport | CustomSend | CustomExport | UpdateProjectFromArchive |
+ FileImport;
#if WITH_SENTRY
capabilities |= SentryFramework;
#endif
@@ -513,6 +514,114 @@ - (void)documentPickerWasCancelled:
[root presentViewController:picker animated:YES completion:nil];
}
+static void removeInboxFile(const QString &path, const QString &appDir) {
+ const QString inboxDir = appDir + QStringLiteral("/Inbox/");
+ if (path.startsWith(inboxDir)) {
+ QFile::remove(path);
+ }
+}
+
+void IosPlatformUtilities::importFile(const QString &path) const {
+ NSLog(@"QField[importFile] called with path: %@", path.toNSString());
+
+ QFileInfo fileInfo(path);
+ if (!fileInfo.exists() || !fileInfo.isFile()) {
+ NSLog(@"QField[importFile] abort: not an existing regular file");
+ return;
+ }
+ if (!AppInterface::instance()) {
+ NSLog(@"QField[importFile] abort: AppInterface instance is null");
+ return;
+ }
+
+ const QString appDir = applicationDirectory();
+ const QString suffix = fileInfo.suffix().toLower();
+ const bool isProjectFile =
+ suffix == QLatin1String("qgs") || suffix == QLatin1String("qgz");
+ const bool isArchive = suffix == QLatin1String("zip");
+ NSLog(@"QField[importFile] suffix=%@ isProjectFile=%d isArchive=%d",
+ suffix.toNSString(), isProjectFile, isArchive);
+
+ if (isArchive) {
+ const QString importBase = appDir + QStringLiteral("/Imported Projects/");
+ QDir().mkpath(importBase);
+
+ QString destinationDir = importBase + fileInfo.completeBaseName();
+ int index = 1;
+ while (QFileInfo::exists(destinationDir)) {
+ destinationDir = importBase + fileInfo.completeBaseName() +
+ QStringLiteral("_%1").arg(index);
+ ++index;
+ }
+ QDir().mkpath(destinationDir);
+
+ QStringList extractedFiles;
+ if (!FileUtils::unzip(path, destinationDir, extractedFiles, false)) {
+ NSLog(@"QField[importFile] abort: unzip failed for %@",
+ path.toNSString());
+ return;
+ }
+ NSLog(@"QField[importFile] extracted %lu files into %@",
+ (unsigned long)extractedFiles.size(), destinationDir.toNSString());
+ removeInboxFile(path, appDir);
+
+ // Open the project bundled within the archive, or reveal its content
+ QString projectFile;
+ for (const QString &extractedFile : extractedFiles) {
+ const QString extractedSuffix =
+ QFileInfo(extractedFile).suffix().toLower();
+ if (extractedSuffix == QLatin1String("qgs") ||
+ extractedSuffix == QLatin1String("qgz")) {
+ projectFile = extractedFile;
+ break;
+ }
+ }
+
+ if (!projectFile.isEmpty()) {
+ NSLog(@"QField[importFile] loading bundled project: %@",
+ projectFile.toNSString());
+ AppInterface::instance()->loadFile(projectFile);
+ } else {
+ NSLog(@"QField[importFile] no project in archive, revealing: %@",
+ destinationDir.toNSString());
+ emit AppInterface::instance()->openPath(destinationDir);
+ }
+ return;
+ }
+
+ const QString importBase =
+ isProjectFile ? appDir + QStringLiteral("/Imported Projects/")
+ : appDir + QStringLiteral("/Imported Datasets/");
+ QDir().mkpath(importBase);
+
+ const QString suffixPart =
+ suffix.isEmpty() ? QString() : QStringLiteral(".") + suffix;
+ QString destinationFile = importBase + fileInfo.fileName();
+ int index = 1;
+ while (QFileInfo::exists(destinationFile)) {
+ destinationFile = importBase + fileInfo.completeBaseName() +
+ QStringLiteral("_%1").arg(index) + suffixPart;
+ ++index;
+ }
+
+ if (!QFile::copy(path, destinationFile)) {
+ NSLog(@"QField[importFile] abort: copy failed %@ -> %@", path.toNSString(),
+ destinationFile.toNSString());
+ return;
+ }
+ NSLog(@"QField[importFile] copied to %@", destinationFile.toNSString());
+ removeInboxFile(path, appDir);
+
+ const bool loaded = AppInterface::instance()->loadFile(destinationFile);
+ NSLog(@"QField[importFile] loadFile(%@) returned %d",
+ destinationFile.toNSString(), loaded);
+ if (!loaded) {
+ NSLog(@"QField[importFile] revealing in browser: %@",
+ importBase.toNSString());
+ emit AppInterface::instance()->openPath(importBase);
+ }
+}
+
void IosPlatformUtilities::sendDatasetTo(const QString &path) const {
NSMutableArray *items = [NSMutableArray array];
[items addObject:[NSURL fileURLWithPath:path.toNSString()]];
diff --git a/src/core/platforms/platformutilities.cpp b/src/core/platforms/platformutilities.cpp
index 56e00cda01..35e3c9af3f 100644
--- a/src/core/platforms/platformutilities.cpp
+++ b/src/core/platforms/platformutilities.cpp
@@ -279,6 +279,11 @@ void PlatformUtilities::importProjectArchive() const
void PlatformUtilities::importDatasets() const
{}
+void PlatformUtilities::importFile( const QString &path ) const
+{
+ Q_UNUSED( path )
+}
+
void PlatformUtilities::updateProjectFromArchive( const QString &projectPath ) const
{
const QString zipFilePath = QFileDialog::getOpenFileName( nullptr,
diff --git a/src/core/platforms/platformutilities.h b/src/core/platforms/platformutilities.h
index fb40e25e42..e0a137dc55 100644
--- a/src/core/platforms/platformutilities.h
+++ b/src/core/platforms/platformutilities.h
@@ -56,6 +56,7 @@ class QFIELD_CORE_EXPORT PlatformUtilities : public QObject
Vibrate = 1 << 9, //!< Haptic feedback / vibration support
UpdateProjectFromArchive = 1 << 10, //!< Update local project from a ZIP archive support
PositioningService = 1 << 11, //!< Positioning service support
+ FileImport = 1 << 12, //!< Importing files shared with the app from other applications
};
Q_DECLARE_FLAGS( Capabilities, Capability )
Q_FLAGS( Capabilities )
@@ -146,6 +147,8 @@ class QFIELD_CORE_EXPORT PlatformUtilities : public QObject
Q_INVOKABLE virtual void importProjectArchive() const;
//! Requests and imports one or more datasets into QField's application directory action
Q_INVOKABLE virtual void importDatasets() const;
+ //! Imports a file shared with QField by the operating system into its application directory and opens it
+ Q_INVOKABLE virtual void importFile( const QString &path ) const;
/**
* Update a local project content from a user-picked archive file action
diff --git a/src/core/qfieldurlhandler.cpp b/src/core/qfieldurlhandler.cpp
index 88bcf77550..32a7f2568b 100644
--- a/src/core/qfieldurlhandler.cpp
+++ b/src/core/qfieldurlhandler.cpp
@@ -16,8 +16,11 @@
***************************************************************************/
#include "appinterface.h"
+#include "platformutilities.h"
#include "qfieldurlhandler.h"
+#include
+
QFieldUrlHandler::QFieldUrlHandler( AppInterface *iface, QObject *parent )
: QObject( parent ), mIface( iface )
{
@@ -25,6 +28,15 @@ QFieldUrlHandler::QFieldUrlHandler( AppInterface *iface, QObject *parent )
void QFieldUrlHandler::handleUrl( const QUrl &url )
{
+ if ( url.isLocalFile() )
+ {
+ // Import on the next event loop cycle so the potentially heavy work (e.g.
+ // unzipping an archive) does not block the incoming-URL handling
+ const QString path = url.toLocalFile();
+ QTimer::singleShot( 0, [path]() { PlatformUtilities::instance()->importFile( path ); } );
+ return;
+ }
+
if ( mIface )
{
mIface->executeAction( url.toString() );
diff --git a/src/core/qgismobileapp.cpp b/src/core/qgismobileapp.cpp
index 416f6a9f7e..8bedac44c5 100644
--- a/src/core/qgismobileapp.cpp
+++ b/src/core/qgismobileapp.cpp
@@ -237,6 +237,10 @@ QgisMobileapp::QgisMobileapp( QgsApplication *app, QObject *parent )
mUrlHandler.reset( new QFieldUrlHandler( mIface, this ) );
QDesktopServices::setUrlHandler( QStringLiteral( "qfield" ), mUrlHandler.get(), "handleUrl" );
+ if ( PlatformUtilities::instance()->capabilities() & PlatformUtilities::FileImport )
+ {
+ QDesktopServices::setUrlHandler( QStringLiteral( "file" ), mUrlHandler.get(), "handleUrl" );
+ }
mMessageLogModel = new MessageLogModel( this );