Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 193 additions & 1 deletion platform/ios/Info.plist.in
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
<true/>

<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<false/>

<key>LSApplicationQueriesSchemes</key>
<array>
Expand All @@ -148,5 +148,197 @@
</array>
</dict>
</array>

<!-- Document types QField can open when shared from other applications -->
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>QGIS project</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>org.qgis.qgs</string>
<string>org.qgis.qgz</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Geospatial dataset</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>org.opengeospatial.geopackage</string>
<string>com.mapbox.mbtiles</string>
<string>org.geojson.geojson</string>
<string>com.google.earth.kml</string>
<string>com.google.earth.kmz</string>
<string>com.topografix.gpx</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Supported document</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
<string>public.zip-archive</string>
<string>public.json</string>
<string>public.tiff</string>
<string>public.jpeg-2000</string>
</array>
</dict>
</array>

<!-- Type declarations for geospatial extensions iOS does not know natively -->
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.qgis.qgs</string>
<key>UTTypeDescription</key>
<string>QGIS project</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>qgs</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.qgis.qgz</string>
<key>UTTypeDescription</key>
<string>QGIS compressed project</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.zip-archive</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>qgz</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.opengeospatial.geopackage</string>
<key>UTTypeDescription</key>
<string>GeoPackage</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gpkg</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.mapbox.mbtiles</string>
<key>UTTypeDescription</key>
<string>MBTiles</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mbtiles</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.geojson.geojson</string>
<key>UTTypeDescription</key>
<string>GeoJSON</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.json</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>geojson</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.google.earth.kml</string>
<key>UTTypeDescription</key>
<string>KML</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>kml</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.google.earth.kmz</string>
<key>UTTypeDescription</key>
<string>KMZ</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.zip-archive</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>kmz</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.topografix.gpx</string>
<key>UTTypeDescription</key>
<string>GPX</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>gpx</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions src/core/platforms/ios/iosplatformutilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
111 changes: 110 additions & 1 deletion src/core/platforms/ios/iosplatformutilities.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()]];
Expand Down
5 changes: 5 additions & 0 deletions src/core/platforms/platformutilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/core/platforms/platformutilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/core/qfieldurlhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,27 @@
***************************************************************************/

#include "appinterface.h"
#include "platformutilities.h"
#include "qfieldurlhandler.h"

#include <QTimer>

QFieldUrlHandler::QFieldUrlHandler( AppInterface *iface, QObject *parent )
: QObject( parent ), mIface( iface )
{
}

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() );
Expand Down
Loading
Loading