diff --git a/platform/android/res/menu/project_folder_menu.xml b/platform/android/res/menu/project_folder_menu.xml
deleted file mode 100644
index 92b3c7be09..0000000000
--- a/platform/android/res/menu/project_folder_menu.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
diff --git a/platform/android/res/menu/project_item_menu.xml b/platform/android/res/menu/project_item_menu.xml
deleted file mode 100644
index 1a13b02d05..0000000000
--- a/platform/android/res/menu/project_item_menu.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/platform/android/res/menu/project_menu.xml b/platform/android/res/menu/project_menu.xml
deleted file mode 100644
index 0dc338d644..0000000000
--- a/platform/android/res/menu/project_menu.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
diff --git a/platform/android/res/values/strings.xml b/platform/android/res/values/strings.xml
index 8607da0e69..de89829e40 100644
--- a/platform/android/res/values/strings.xml
+++ b/platform/android/res/values/strings.xml
@@ -36,10 +36,6 @@
Allow
Deny Permanently
Deny Once
- Import Project from Folder
- Import Project from ZIP
- Import Dataset(s)
- Storage Management Help
Import Error
The selected dataset(s) could not be imported properly.
The selected project archive was not imported properly.
@@ -52,13 +48,6 @@
Cancel
Export Error
The selected project folder was not exported properly.
- Send to...
- Send Compressed Folder to...
- Export to Folder...
- Add to Favorites
- Remove from Favorites
- Remove Dataset
- Remove Project Folder
Removal Confirmation
The dataset will be permamently deleted, proceed with removal?
The project folder will be permamently deleted, proceed with removal?
@@ -70,6 +59,7 @@
I\'ll read later
Please wait while QField is importing the project
Please wait while QField is importing the dataset(s)
+ Please wait while QField is updating the local project
Currently uploading attachments to QFieldCloud
Operation Unsupported
Your device does not support this import operation.
diff --git a/platform/android/src/ch/opengis/qfield/QFieldActivity.java b/platform/android/src/ch/opengis/qfield/QFieldActivity.java
index c6e2c35deb..79f4d2fee8 100644
--- a/platform/android/src/ch/opengis/qfield/QFieldActivity.java
+++ b/platform/android/src/ch/opengis/qfield/QFieldActivity.java
@@ -99,6 +99,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
public class QFieldActivity extends QtActivity {
+ private static final int IMPORT_DATASET = 300;
+ private static final int IMPORT_PROJECT_FOLDER = 301;
+ private static final int IMPORT_PROJECT_ARCHIVE = 302;
+
+ private static final int UPDATE_PROJECT_FROM_ARCHIVE = 400;
+
+ private static final int EXPORT_TO_FOLDER = 500;
+
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor sharedPreferenceEditor;
private ProgressDialog progressDialog;
@@ -112,6 +120,7 @@ public class QFieldActivity extends QtActivity {
private float originalBrightness;
private boolean handleVolumeKeys = false;
private String pathsToExport;
+ private String projectPath;
private double sceneTopMargin = 0;
private double sceneBottomMargin = 0;
@@ -608,7 +617,7 @@ private void triggerImportDatasets() {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setType("*/*");
try {
- startActivityForResult(intent, R.id.import_dataset);
+ startActivityForResult(intent, IMPORT_DATASET);
} catch (ActivityNotFoundException e) {
displayAlertDialog(
getString(R.string.operation_unsupported),
@@ -625,7 +634,7 @@ private void triggerImportProjectFolder() {
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
try {
- startActivityForResult(intent, R.id.import_project_folder);
+ startActivityForResult(intent, IMPORT_PROJECT_FOLDER);
} catch (ActivityNotFoundException e) {
displayAlertDialog(
getString(R.string.operation_unsupported),
@@ -643,7 +652,26 @@ private void triggerImportProjectArchive() {
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType("application/zip");
try {
- startActivityForResult(intent, R.id.import_project_archive);
+ startActivityForResult(intent, IMPORT_PROJECT_ARCHIVE);
+ } catch (ActivityNotFoundException e) {
+ displayAlertDialog(
+ getString(R.string.operation_unsupported),
+ getString(R.string.import_operation_unsupported));
+ Log.w("QField", "No activity found for ACTION_OPEN_DOCUMENT.");
+ }
+ return;
+ }
+
+ private void triggerUpdateProjectFromArchive(String path) {
+ projectPath = path;
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ intent.setType("application/zip");
+ try {
+ startActivityForResult(intent, UPDATE_PROJECT_FROM_ARCHIVE);
} catch (ActivityNotFoundException e) {
displayAlertDialog(
getString(R.string.operation_unsupported),
@@ -695,7 +723,7 @@ private void exportToFolder(String paths) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
try {
- startActivityForResult(intent, R.id.export_to_folder);
+ startActivityForResult(intent, EXPORT_TO_FOLDER);
} catch (ActivityNotFoundException e) {
displayAlertDialog(
getString(R.string.operation_unsupported),
@@ -948,6 +976,54 @@ public void run() {
});
}
+ void updateProjectFromArchive(Uri archiveUri) {
+ File externalFilesDir = getExternalFilesDir(null);
+ if (externalFilesDir == null) {
+ return;
+ }
+
+ ProgressDialog progressDialog =
+ new ProgressDialog(this, R.style.DialogTheme);
+ progressDialog.setMessage(getString(R.string.update_project_wait));
+ progressDialog.setIndeterminate(true);
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+
+ Context context = getApplication().getApplicationContext();
+ ContentResolver resolver = getContentResolver();
+
+ executorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ DocumentFile documentFile =
+ DocumentFile.fromSingleUri(context, archiveUri);
+
+ String projectFolder =
+ new File(projectPath).getParentFile().getAbsolutePath() +
+ "/";
+ boolean imported = false;
+ try {
+ InputStream input = resolver.openInputStream(archiveUri);
+ imported = QFieldUtils.zipToFolder(input, projectFolder);
+ } catch (Exception e) {
+ e.printStackTrace();
+
+ if (!isFinishing()) {
+ displayAlertDialog(
+ getString(R.string.import_error),
+ getString(R.string.import_project_archive_error));
+ }
+ }
+
+ progressDialog.dismiss();
+ if (imported) {
+ // Trigger a project re-load
+ openProject(projectPath);
+ }
+ }
+ });
+ }
+
private void checkPermissions() {
List permissionsList = new ArrayList();
if (ContextCompat.checkSelfPermission(
@@ -1093,8 +1169,7 @@ public void onClick(
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
- if (requestCode == R.id.import_dataset &&
- resultCode == Activity.RESULT_OK) {
+ if (requestCode == IMPORT_DATASET && resultCode == Activity.RESULT_OK) {
Log.d("QField", "handling import dataset(s)");
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir == null || data == null) {
@@ -1159,7 +1234,7 @@ public void onClick(DialogInterface dialog, int id) {
} else {
importDatasets(datasetUris);
}
- } else if (requestCode == R.id.import_project_folder &&
+ } else if (requestCode == IMPORT_PROJECT_FOLDER &&
resultCode == Activity.RESULT_OK) {
Log.d("QField", "handling import project folder");
File externalFilesDir = getExternalFilesDir(null);
@@ -1199,7 +1274,7 @@ public void onClick(DialogInterface dialog, int id) {
} else {
importProjectFolder(uri);
}
- } else if (requestCode == R.id.import_project_archive &&
+ } else if (requestCode == IMPORT_PROJECT_ARCHIVE &&
resultCode == Activity.RESULT_OK) {
Log.d("QField", "handling import project archive");
File externalFilesDir = getExternalFilesDir(null);
@@ -1248,7 +1323,23 @@ public void onClick(DialogInterface dialog, int id) {
} else {
importProjectArchive(uri);
}
- } else if (requestCode == R.id.export_to_folder &&
+ } else if (requestCode == UPDATE_PROJECT_FROM_ARCHIVE &&
+ resultCode == Activity.RESULT_OK) {
+ Log.d("QField", "handling updating project from archive");
+ File externalFilesDir = getExternalFilesDir(null);
+ if (externalFilesDir == null || data == null) {
+ return;
+ }
+
+ Uri uri = data.getData();
+ Context context = getApplication().getApplicationContext();
+ ContentResolver resolver = getContentResolver();
+
+ DocumentFile documentFile =
+ DocumentFile.fromSingleUri(context, uri);
+
+ updateProjectFromArchive(uri);
+ } else if (requestCode == EXPORT_TO_FOLDER &&
resultCode == Activity.RESULT_OK) {
Log.d("QField", "handling export to folder");
diff --git a/src/core/platforms/android/androidplatformutilities.cpp b/src/core/platforms/android/androidplatformutilities.cpp
index e2aad1d454..04ec5c424f 100644
--- a/src/core/platforms/android/androidplatformutilities.cpp
+++ b/src/core/platforms/android/androidplatformutilities.cpp
@@ -98,7 +98,7 @@ AndroidPlatformUtilities::AndroidPlatformUtilities()
PlatformUtilities::Capabilities AndroidPlatformUtilities::capabilities() const
{
- PlatformUtilities::Capabilities capabilities = Capabilities() | NativeCamera | AdjustBrightness | CustomLocalDataPicker | CustomImport | CustomExport | CustomSend | FilePicker | VolumeKeys;
+ PlatformUtilities::Capabilities capabilities = Capabilities() | NativeCamera | AdjustBrightness | CustomLocalDataPicker | CustomImport | CustomExport | CustomSend | FilePicker | VolumeKeys | UpdateProjectFromArchive;
#ifdef WITH_SENTRY
capabilities |= SentryFramework;
#endif
@@ -251,6 +251,25 @@ void AndroidPlatformUtilities::importDatasets() const
}
}
+void AndroidPlatformUtilities::updateProjectFromArchive( const QString &projectPath ) const
+{
+ if ( mActivity.isValid() )
+ {
+ runOnAndroidMainThread( [projectPath] {
+ auto activity = qtAndroidContext();
+ if ( activity.isValid() )
+ {
+#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
+ QAndroidJniObject projectPathJni = QAndroidJniObject::fromString( projectPath );
+#else
+ QJniObject projectPathJni = QJniObject::fromString( projectPath );
+#endif
+ activity.callMethod( "triggerUpdateProjectFromArchive", "(Ljava/lang/String;)V", projectPathJni.object() );
+ }
+ } );
+ }
+}
+
void AndroidPlatformUtilities::sendDatasetTo( const QString &path ) const
{
if ( mActivity.isValid() )
diff --git a/src/core/platforms/android/androidplatformutilities.h b/src/core/platforms/android/androidplatformutilities.h
index 658db30705..271dd696d1 100644
--- a/src/core/platforms/android/androidplatformutilities.h
+++ b/src/core/platforms/android/androidplatformutilities.h
@@ -46,6 +46,8 @@ class AndroidPlatformUtilities : public PlatformUtilities
void importProjectArchive() const override;
void importDatasets() const override;
+ void updateProjectFromArchive( const QString &projectPath ) const override;
+
void sendDatasetTo( const QString &path ) const override;
void exportDatasetTo( const QString &path ) const override;
void removeDataset( const QString &path ) const override;
diff --git a/src/core/platforms/platformutilities.cpp b/src/core/platforms/platformutilities.cpp
index b180b66f7b..9bf567ddb4 100644
--- a/src/core/platforms/platformutilities.cpp
+++ b/src/core/platforms/platformutilities.cpp
@@ -206,6 +206,11 @@ void PlatformUtilities::importProjectArchive() const
void PlatformUtilities::importDatasets() const
{}
+void PlatformUtilities::updateProjectFromArchive( const QString &projectPath ) const
+{
+ Q_UNUSED( projectPath )
+}
+
void PlatformUtilities::exportFolderTo( const QString &path ) const
{
Q_UNUSED( path )
diff --git a/src/core/platforms/platformutilities.h b/src/core/platforms/platformutilities.h
index 21b3c9ea06..f5bc966cc3 100644
--- a/src/core/platforms/platformutilities.h
+++ b/src/core/platforms/platformutilities.h
@@ -41,16 +41,17 @@ class QFIELD_CORE_EXPORT PlatformUtilities : public QObject
public:
enum Capability
{
- NoCapabilities = 0, //!< No capabilities
- NativeCamera = 1, //!< Native camera handling support
- AdjustBrightness = 1 << 1, //!< Screen brightness adjustment support
- SentryFramework = 1 << 2, //!< Sentry framework support
- CustomLocalDataPicker = 1 << 3, //!< Custom QML local data picker support
- CustomImport = 1 << 4, //!< Import project and dataset support
- CustomExport = 1 << 5, //!< Export project and dataset support
- CustomSend = 1 << 6, //!< Send/share files support
- FilePicker = 1 << 7, //!< File picker support
- VolumeKeys = 1 << 8, //!< Volume keys handling support
+ NoCapabilities = 0, //!< No capabilities
+ NativeCamera = 1, //!< Native camera handling support
+ AdjustBrightness = 1 << 1, //!< Screen brightness adjustment support
+ SentryFramework = 1 << 2, //!< Sentry framework support
+ CustomLocalDataPicker = 1 << 3, //!< Custom QML local data picker support
+ CustomImport = 1 << 4, //!< Import project and dataset support
+ CustomExport = 1 << 5, //!< Export project and dataset support
+ CustomSend = 1 << 6, //!< Send/share files support
+ FilePicker = 1 << 7, //!< File picker support
+ VolumeKeys = 1 << 8, //!< Volume keys handling support
+ UpdateProjectFromArchive = 1 << 9, //!< Update local project from a ZIP archive support
};
Q_DECLARE_FLAGS( Capabilities, Capability )
Q_FLAGS( Capabilities )
@@ -130,6 +131,12 @@ class QFIELD_CORE_EXPORT PlatformUtilities : public QObject
//! Requests and imports one or more datasets into QField's application directory action
Q_INVOKABLE virtual void importDatasets() const;
+ /**
+ * Update a local project content from a user-picked archive file action
+ * \param projectPath the project file path
+ */
+ Q_INVOKABLE virtual void updateProjectFromArchive( const QString &projectPath ) const;
+
//! Exports a folder \a path to a user-specified location
Q_INVOKABLE virtual void exportFolderTo( const QString &path ) const;
//! Exports a dataset \a path to a user-specified location
diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml
index f95852f29f..6ea9c714f3 100644
--- a/src/qml/QFieldLocalDataPickerScreen.qml
+++ b/src/qml/QFieldLocalDataPickerScreen.qml
@@ -325,9 +325,14 @@ Page {
}
QfToolButton {
- id: importButton
+ id: actionButton
round: true
- visible: table.model.currentPath === 'root'
+
+ // Since the project menu only has one action for now, hide if PlatformUtilities.UpdateProjectFromArchive is missing
+ property bool isLocalProject: QFieldCloudUtils.getProjectId(qgisProject.fileName) === '' &&
+ (projectInfo.filePath.endsWith('.qgs') || projectInfo.filePath.endsWith('.qgz')) &&
+ platformUtilities.capabilities & PlatformUtilities.UpdateProjectFromArchive
+ visible: (projectFolderView && isLocalProject && table.model.currentDepth === 1) || table.model.currentPath === 'root'
anchors.bottom: parent.bottom
anchors.right: parent.right
@@ -338,8 +343,12 @@ Page {
iconSource: Theme.getThemeIcon( "ic_add_white_24dp" )
onClicked: {
- importMenu.popup(importButton.x + importButton.width - importMenu.width + 10,
- importButton.y - importButton.height - header.height - 10)
+ var xy = mapToItem(mainWindow.contentItem, actionButton.width, actionButton.height)
+ if (projectFolderView) {
+ projectMenu.popup(xy.x - projectMenu.width, xy.y - projectMenu.height - header.height)
+ } else {
+ importMenu.popup(xy.x - importMenu.width, xy.y - importMenu.height - header.height)
+ }
}
}
}
@@ -558,6 +567,40 @@ Page {
onTriggered: { Qt.openUrlExternally("https://docs.qfield.org/get-started/storage/") }
}
}
+
+ Menu {
+ id: projectMenu
+
+ title: qsTr('Project Actions')
+
+ width: {
+ var result = 0;
+ var padding = 0;
+ for (var i = 0; i < count; ++i) {
+ var item = itemAt(i);
+ result = Math.max(item.contentItem.implicitWidth, result);
+ padding = Math.max(item.padding, padding);
+ }
+ return Math.min( result + padding * 2,mainWindow.width - 20);
+ }
+
+ topMargin: sceneTopMargin
+ bottomMargin: sceneBottomMargin
+
+ MenuItem {
+ id: updateProjectFromArchive
+
+ enabled: platformUtilities.capabilities & PlatformUtilities.UpdateProjectFromArchive
+ visible: enabled
+ font: Theme.defaultFont
+ width: parent.width
+ height: enabled ? undefined : 0
+ leftPadding: 10
+
+ text: qsTr( "Update project from ZIP" )
+ onTriggered: { platformUtilities.updateProjectFromArchive(projectInfo.filePath); }
+ }
+ }
}
Dialog {