diff --git a/src/core/deltafilewrapper.cpp b/src/core/deltafilewrapper.cpp index 63f0640049..a88a64f99c 100644 --- a/src/core/deltafilewrapper.cpp +++ b/src/core/deltafilewrapper.cpp @@ -647,103 +647,7 @@ void DeltaFileWrapper::addPatch( const QString &localLayerId, const QString &sou delta.insert( QStringLiteral( "old" ), oldData ); delta.insert( QStringLiteral( "new" ), newData ); - - mIsDirty = true; - - QMap layerPkDeltaIdx = mLocalPkDeltaIdx.value( localLayerId ); - QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); - - qInfo() << "DeltaFileWrapper::addPatch: localPk=" << localPk << " layerPkDeltaIdx=" << layerPkDeltaIdx; - - if ( layerPkDeltaIdx.contains( localPk ) ) - { - int deltaIdx = layerPkDeltaIdx.take( localPk ); - QJsonObject deltaCreate = mDeltas.at( deltaIdx ).toObject(); - - Q_ASSERT( deltaCreate.value( QStringLiteral( "method" ) ).toString() == QStringLiteral( "create" ) ); - - QJsonObject newCreate = deltaCreate.value( QStringLiteral( "new" ) ).toObject(); - QJsonObject attributesCreate = newCreate.value( QStringLiteral( "attributes" ) ).toObject(); - - qInfo() << "DeltaFileWrapper::addPatch: replacing an existing create delta: " << deltaCreate << "at" << deltaIdx; - - if ( !newData.value( QStringLiteral( "geometry" ) ).isUndefined() ) - { - newCreate.insert( QStringLiteral( "geometry" ), newData.value( QStringLiteral( "geometry" ) ) ); - } - - const QStringList attributeNames = tmpNewAttrs.keys(); - for ( const QString &attributeName : attributeNames ) - { - attributesCreate.insert( attributeName, tmpNewAttrs.value( attributeName ) ); - } - - newCreate.insert( QStringLiteral( "attributes" ), geometryToJsonValue( newGeom ) ); - newCreate.insert( QStringLiteral( "attributes" ), attributesCreate ); - deltaCreate.insert( QStringLiteral( "new" ), newCreate ); - deltaCreate.insert( QStringLiteral( "sourcePk" ), delta.value( QStringLiteral( "sourcePk" ) ) ); - - mDeltas.replace( deltaIdx, deltaCreate ); - - qInfo() << "DeltaFileWrapper::addPatch: replaced an existing create delta: " << deltaCreate; - - return; - } - else - { - for ( qsizetype i = mDeltas.size() - 1; i >= 0; i-- ) - { - QJsonObject existingDelta = mDeltas[i].toObject(); - const QString existingLayerId = existingDelta.value( QStringLiteral( "localLayerId" ) ).toString(); - const QString existingLocalPk = existingDelta.value( QStringLiteral( "localPk" ) ).toString(); - const QString existingMethod = existingDelta.value( QStringLiteral( "method" ) ).toString(); - if ( existingLayerId == localLayerId && existingLocalPk == newFeature.attribute( localPkAttrName ) && existingMethod == "patch" ) - { - QJsonObject existingOldData = existingDelta.value( QStringLiteral( "old" ) ).toObject(); - QJsonObject existingNewData = existingDelta.value( QStringLiteral( "new" ) ).toObject(); - if ( newData.contains( "geometry" ) ) - { - existingNewData.insert( "geometry", newData.value( QStringLiteral( "geometry" ) ).toString() ); - if ( !existingOldData.contains( "geometry" ) ) - { - // Previous patch did not contain a geometry change, add old geometry data - existingOldData.insert( "geometry", oldData.value( QStringLiteral( "geometry" ) ) ); - } - } - const QStringList attributeNames = tmpNewAttrs.keys(); - if ( !attributeNames.isEmpty() ) - { - QJsonObject existingOldAttributes = existingOldData.value( QStringLiteral( "attributes" ) ).toObject(); - QJsonObject existingNewAttributes = existingNewData.value( QStringLiteral( "attributes" ) ).toObject(); - for ( const QString &attributeName : attributeNames ) - { - existingNewAttributes.insert( attributeName, tmpNewAttrs.value( attributeName ) ); - if ( !existingOldAttributes.contains( attributeName ) ) - { - // Previous patch did not contain this attribute change, add old attribute value - existingOldAttributes.insert( attributeName, tmpOldAttrs.value( attributeName ) ); - } - } - existingOldData.insert( "attributes", existingOldAttributes ); - existingNewData.insert( "attributes", existingNewAttributes ); - } - existingDelta.insert( "old", existingOldData ); - existingDelta.insert( "new", existingNewData ); - - mDeltas.replace( i, existingDelta ); - - qInfo() << "DeltaFileWrapper::addPatch: replaced an existing patch delta: " << existingDelta; - - return; - } - } - - mDeltas.append( delta ); - - qInfo() << "DeltaFileWrapper::addPatch: Added a new patch delta: " << delta; - - emit countChanged(); - } + appendDelta( delta ); } @@ -763,18 +667,6 @@ void DeltaFileWrapper::addDelete( const QString &localLayerId, const QString &so { "clientId", QFieldCloudUtils::projectSetting( mCloudProjectId, QStringLiteral( "lastLocalExportId" ) ).toString() }, } ); - QMap layerPkDeltaIdx = mLocalPkDeltaIdx.value( localLayerId ); - QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); - - if ( layerPkDeltaIdx.contains( localPk ) ) - { - mDeltas.removeAt( layerPkDeltaIdx.take( localPk ) ); - - emit countChanged(); - - return; - } - const QStringList attachmentFieldsList = attachmentFieldNames( mProject, localLayerId ); const QgsAttributes oldAttrs = oldFeature.attributes(); QJsonObject oldData( { { "geometry", geometryToJsonValue( oldFeature.geometry() ) } } ); @@ -813,13 +705,7 @@ void DeltaFileWrapper::addDelete( const QString &localLayerId, const QString &so } delta.insert( QStringLiteral( "old" ), oldData ); - - mDeltas.append( delta ); - mIsDirty = true; - - qInfo() << "DeltaFileWrapper::addDelete: Added a new delete delta: " << delta; - - emit countChanged(); + appendDelta( delta ); } @@ -892,17 +778,219 @@ void DeltaFileWrapper::addCreate( const QString &localLayerId, const QString &so delta.insert( QStringLiteral( "new" ), newData ); - QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); + appendDelta( delta ); +} + +void DeltaFileWrapper::appendDelta( const QJsonObject &delta ) +{ + if ( mIsPushing ) + { + mPendingDeltas << delta; + } + else + { + mergeDelta( delta ); + } +} + +void DeltaFileWrapper::mergeCreateDelta( const QJsonObject &delta ) +{ + Q_ASSERT( delta.value( QStringLiteral( "method" ) ) == "create" ); + + const QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); + const QString localLayerId = delta.value( QStringLiteral( "localLayerId" ) ).toString(); mLocalPkDeltaIdx[localLayerId][localPk] = static_cast( mDeltas.count() ); mDeltas.append( delta ); mIsDirty = true; qInfo() << "DeltaFileWrapper::addCreate: Added a new create delta: " << delta; + emit countChanged(); +} +void DeltaFileWrapper::mergeDeleteDelta( const QJsonObject &delta ) +{ + Q_ASSERT( delta.value( QStringLiteral( "method" ) ) == "delete" ); + + const QString localLayerId = delta.value( QStringLiteral( "localLayerId" ) ).toString(); + QMap layerPkDeltaIdx = mLocalPkDeltaIdx.value( localLayerId ); + QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); + if ( layerPkDeltaIdx.contains( localPk ) ) + { + // Feature creation/deletion occured in the same delta session, just remove as if nothing had ever occured + mDeltas.removeAt( layerPkDeltaIdx.take( localPk ) ); + emit countChanged(); + return; + } + + mDeltas.append( delta ); + mIsDirty = true; + + qInfo() << "DeltaFileWrapper::addDelete: Added a new delete delta: " << delta; emit countChanged(); } +void DeltaFileWrapper::mergePatchDelta( const QJsonObject &delta ) +{ + Q_ASSERT( delta.value( QStringLiteral( "method" ) ) == "patch" ); + + QJsonObject oldData = delta.value( QStringLiteral( "old" ) ).toObject(); + QJsonObject newData = delta.value( QStringLiteral( "new" ) ).toObject(); + + QJsonObject tmpOldAttrs; + if ( oldData.contains( QStringLiteral( "attributes" ) ) ) + { + tmpOldAttrs = oldData.value( QStringLiteral( "attributes" ) ).toObject(); + } + + QString newGeomString; + QJsonObject tmpNewAttrs; + if ( newData.contains( QStringLiteral( "geometry" ) ) ) + { + newGeomString = oldData.value( QStringLiteral( "geometry" ) ).toString(); + } + if ( newData.contains( QStringLiteral( "attributes" ) ) ) + { + tmpNewAttrs = newData.value( QStringLiteral( "attributes" ) ).toObject(); + } + + mIsDirty = true; + + const QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); + const QString localLayerId = delta.value( QStringLiteral( "localLayerId" ) ).toString(); + QMap layerPkDeltaIdx = mLocalPkDeltaIdx.value( localLayerId ); + + qInfo() << "DeltaFileWrapper::addPatch: localPk=" << localPk << " layerPkDeltaIdx=" << layerPkDeltaIdx; + + if ( layerPkDeltaIdx.contains( localPk ) ) + { + int deltaIdx = layerPkDeltaIdx.take( localPk ); + QJsonObject deltaCreate = mDeltas.at( deltaIdx ).toObject(); + + Q_ASSERT( deltaCreate.value( QStringLiteral( "method" ) ).toString() == QStringLiteral( "create" ) ); + + QJsonObject newCreate = deltaCreate.value( QStringLiteral( "new" ) ).toObject(); + QJsonObject attributesCreate = newCreate.value( QStringLiteral( "attributes" ) ).toObject(); + + qInfo() << "DeltaFileWrapper::addPatch: replacing an existing create delta: " << deltaCreate << "at" << deltaIdx; + + if ( !newData.value( QStringLiteral( "geometry" ) ).isUndefined() ) + { + newCreate.insert( QStringLiteral( "geometry" ), newData.value( QStringLiteral( "geometry" ) ) ); + } + + const QStringList attributeNames = tmpNewAttrs.keys(); + for ( const QString &attributeName : attributeNames ) + { + attributesCreate.insert( attributeName, tmpNewAttrs.value( attributeName ) ); + } + + newCreate.insert( QStringLiteral( "attributes" ), newGeomString ); + newCreate.insert( QStringLiteral( "attributes" ), attributesCreate ); + deltaCreate.insert( QStringLiteral( "new" ), newCreate ); + deltaCreate.insert( QStringLiteral( "sourcePk" ), delta.value( QStringLiteral( "sourcePk" ) ) ); + + mDeltas.replace( deltaIdx, deltaCreate ); + + qInfo() << "DeltaFileWrapper::addPatch: replaced an existing create delta: " << deltaCreate; + + return; + } + else + { + for ( qsizetype i = mDeltas.size() - 1; i >= 0; i-- ) + { + QJsonObject existingDelta = mDeltas[i].toObject(); + const QString existingLayerId = existingDelta.value( QStringLiteral( "localLayerId" ) ).toString(); + const QString existingLocalPk = existingDelta.value( QStringLiteral( "localPk" ) ).toString(); + const QString existingMethod = existingDelta.value( QStringLiteral( "method" ) ).toString(); + if ( existingLayerId == localLayerId && existingLocalPk == localPk && existingMethod == "patch" ) + { + QJsonObject existingOldData = existingDelta.value( QStringLiteral( "old" ) ).toObject(); + QJsonObject existingNewData = existingDelta.value( QStringLiteral( "new" ) ).toObject(); + if ( newData.contains( "geometry" ) ) + { + existingNewData.insert( "geometry", newData.value( QStringLiteral( "geometry" ) ).toString() ); + if ( !existingOldData.contains( "geometry" ) ) + { + // Previous patch did not contain a geometry change, add old geometry data + existingOldData.insert( "geometry", oldData.value( QStringLiteral( "geometry" ) ) ); + } + } + const QStringList attributeNames = tmpNewAttrs.keys(); + if ( !attributeNames.isEmpty() ) + { + QJsonObject existingOldAttributes = existingOldData.value( QStringLiteral( "attributes" ) ).toObject(); + QJsonObject existingNewAttributes = existingNewData.value( QStringLiteral( "attributes" ) ).toObject(); + for ( const QString &attributeName : attributeNames ) + { + existingNewAttributes.insert( attributeName, tmpNewAttrs.value( attributeName ) ); + if ( !existingOldAttributes.contains( attributeName ) ) + { + // Previous patch did not contain this attribute change, add old attribute value + existingOldAttributes.insert( attributeName, tmpOldAttrs.value( attributeName ) ); + } + } + existingOldData.insert( "attributes", existingOldAttributes ); + existingNewData.insert( "attributes", existingNewAttributes ); + } + existingDelta.insert( "old", existingOldData ); + existingDelta.insert( "new", existingNewData ); + + mDeltas.replace( i, existingDelta ); + + qInfo() << "DeltaFileWrapper::addPatch: replaced an existing patch delta: " << existingDelta; + + return; + } + } + + mDeltas.append( delta ); + + qInfo() << "DeltaFileWrapper::addPatch: Added a new patch delta: " << delta; + + emit countChanged(); + } +} + +void DeltaFileWrapper::mergeDelta( const QJsonObject &delta ) +{ + const QString deltaMethod = delta.value( "method" ).toString(); + if ( deltaMethod == "create" ) + { + mergeCreateDelta( delta ); + } + else if ( deltaMethod == "delete" ) + { + mergeDeleteDelta( delta ); + } + else if ( deltaMethod == "patch" ) + { + mergePatchDelta( delta ); + } + else + { + Q_ASSERT( 0 ); + } +} + +void DeltaFileWrapper::setIsPushing( bool isPushing ) +{ + if ( mIsPushing == isPushing ) + return; + + mIsPushing = isPushing; + emit isPushingChanged(); + + if ( !mIsPushing && !mPendingDeltas.isEmpty() ) + { + for ( const QJsonObject delta : std::as_const( mPendingDeltas ) ) + { + mergeDelta( delta ); + } + mPendingDeltas.clear(); + } +} QJsonValue DeltaFileWrapper::geometryToJsonValue( const QgsGeometry &geom ) const { diff --git a/src/core/deltafilewrapper.h b/src/core/deltafilewrapper.h index fe2ac1afac..26738c54bf 100644 --- a/src/core/deltafilewrapper.h +++ b/src/core/deltafilewrapper.h @@ -36,6 +36,7 @@ class DeltaFileWrapper : public QObject Q_OBJECT Q_PROPERTY( int count READ count NOTIFY countChanged ) + Q_PROPERTY( bool isPushing READ isPushing NOTIFY isPushingChanged ) public: /** @@ -321,6 +322,19 @@ class DeltaFileWrapper : public QObject */ Q_INVOKABLE bool applyReversed(); + /** + * Returns TRUE if the pushing state is active. + */ + bool isPushing() const { return mIsPushing; } + + + /** + * Sets the pushing state. + * + * @param isPushing set to TRUE to reflect an ongoing pushing state. + */ + void setIsPushing( bool isPushing ); + signals: /** * Emitted when the `deltas` list has changed. @@ -330,6 +344,12 @@ class DeltaFileWrapper : public QObject void countChanged(); + /** + * Emmitted when the pushing state has changed. + */ + void isPushingChanged(); + + /** * * @todo TEST @@ -364,6 +384,37 @@ class DeltaFileWrapper : public QObject */ QJsonValue attributeToJsonValue( const QVariant &value ); + + /** + * Append generated \a delta. + */ + void appendDelta( const QJsonObject &delta ); + + + /** + * Merge the generated \a delta into stored deltas. + */ + void mergeDelta( const QJsonObject &delta ); + + + /** + * Merge the generated create \a delta into stored deltas. Should only be called from `mergeDelta` method. + */ + void mergeCreateDelta( const QJsonObject &delta ); + + + /** + * Merge the generated delete \a delta into stored deltas. Should only be called from `mergeDelta` method. + */ + void mergeDeleteDelta( const QJsonObject &delta ); + + + /** + * Merge the generated patch \a delta into stored deltas. Should only be called from `mergeDelta` method. + */ + void mergePatchDelta( const QJsonObject &delta ); + + /** * The current project instance */ @@ -379,6 +430,10 @@ class DeltaFileWrapper : public QObject */ QJsonArray mDeltas; + /** + * The list of pending JSON deltas. + */ + QList mPendingDeltas; /** * The root deltas JSON object. @@ -416,6 +471,12 @@ class DeltaFileWrapper : public QObject bool mIsDirty = false; + /** + * Holds whether the pushing state has been activated. + */ + bool mIsPushing = false; + + /** * Whether the delta file is currently being applied. */ diff --git a/src/core/qfieldcloudprojectsmodel.cpp b/src/core/qfieldcloudprojectsmodel.cpp index 0a084af8d6..d4f4f382f8 100644 --- a/src/core/qfieldcloudprojectsmodel.cpp +++ b/src/core/qfieldcloudprojectsmodel.cpp @@ -1278,6 +1278,8 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo return; } + deltaFileWrapper->setIsPushing( true ); + project->status = ProjectStatus::Uploading; project->deltaFileId = deltaFileWrapper->id(); project->deltaFileUploadStatus = DeltaLocalStatus; @@ -1324,6 +1326,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo if ( deltaFileToUpload.isEmpty() ) { + deltaFileWrapper->setIsPushing( false ); project->status = ProjectStatus::Idle; emit dataChanged( projectIndex, projectIndex, QVector() << StatusRole ); return; @@ -1358,13 +1361,15 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo // TODO check why exactly we failed // maybe the project does not exist, then create it? QgsMessageLog::logMessage( QStringLiteral( "Failed to upload delta file, reason:\n%1\n%2" ).arg( deltasReply->errorString(), project->deltaFileUploadStatusString ) ); + + mLayerObserver->deltaFileWrapper()->setIsPushing( false ); + projectCancelUpload( projectId ); return; } project->uploadDeltaProgress = 1; project->deltaFileUploadStatus = DeltaPendingStatus; - project->deltaLayersToDownload = mLayerObserver->deltaFileWrapper()->deltaLayerIds(); emit dataChanged( projectIndex, projectIndex, QVector() << UploadDeltaProgressRole << UploadDeltaStatusRole ); @@ -1394,6 +1399,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo DeltaFileWrapper *deltaFileWrapper = mLayerObserver->deltaFileWrapper(); deltaFileWrapper->reset(); deltaFileWrapper->resetId(); + deltaFileWrapper->setIsPushing( false ); if ( !deltaFileWrapper->toFile() ) QgsMessageLog::logMessage( QStringLiteral( "Failed to reset delta file after delta push. %1" ).arg( deltaFileWrapper->errorString() ) ); @@ -1430,6 +1436,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo case DeltaErrorStatus: delete networkDeltaStatusCheckedParent; deltaFileWrapper->resetId(); + deltaFileWrapper->setIsPushing( false ); if ( !deltaFileWrapper->toFile() ) QgsMessageLog::logMessage( QStringLiteral( "Failed update committed delta file." ) ); @@ -1443,6 +1450,7 @@ void QFieldCloudProjectsModel::projectUpload( const QString &projectId, const bo deltaFileWrapper->reset(); deltaFileWrapper->resetId(); + deltaFileWrapper->setIsPushing( false ); if ( !deltaFileWrapper->toFile() ) QgsMessageLog::logMessage( QStringLiteral( "Failed to reset delta file. %1" ).arg( deltaFileWrapper->errorString() ) ); @@ -1940,10 +1948,25 @@ QHash QFieldCloudProjectsModel::roleNames() const roles[UserRoleRole] = "UserRole"; roles[UserRoleOriginRole] = "UserRoleOrigin"; roles[DeltaListRole] = "DeltaList"; + roles[AutoPushEnabledRole] = "AutoPushEnabled"; + roles[AutoPushIntervalMinsRole] = "AutoPushIntervalMins"; return roles; } +void QFieldCloudProjectsModel::projectSetAutoPushEnabled( const QString &projectId, bool enabled ) +{ + const QModelIndex projectIndex = findProjectIndex( projectId ); + + if ( projectIndex.isValid() ) + { + CloudProject *project = mProjects[projectIndex.row()]; + project->autoPushEnabled = !project->autoPushEnabled; + QFieldCloudUtils::setProjectSetting( project->id, QStringLiteral( "autoPushEnabled" ), project->autoPushEnabled ); + emit dataChanged( projectIndex, projectIndex, QVector() << AutoPushEnabledRole ); + } +} + void QFieldCloudProjectsModel::reload( const QJsonArray &remoteProjects ) { beginResetModel(); @@ -1961,6 +1984,8 @@ void QFieldCloudProjectsModel::reload( const QJsonArray &remoteProjects ) cloudProject->lastLocalDataLastUpdatedAt = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "lastLocalDataLastUpdatedAt" ) ).toDateTime(); cloudProject->isOutdated = cloudProject->dataLastUpdatedAt > cloudProject->lastLocalDataLastUpdatedAt; cloudProject->projectFileIsOutdated = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "projectFileOudated" ), false ).toBool(); + cloudProject->autoPushEnabled = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "autoPushEnabled" ), false ).toBool(); + cloudProject->autoPushIntervalMins = QFieldCloudUtils::projectSetting( cloudProject->id, QStringLiteral( "autoPushIntervalMins" ), 30 ).toInt(); // generate local export id if not present. Possible reasons for missing localExportId are: // - just upgraded QField that introduced the field @@ -2136,6 +2161,10 @@ QVariant QFieldCloudProjectsModel::data( const QModelIndex &index, int role ) co return mProjects.at( index.row() )->userRoleOrigin; case DeltaListRole: return QVariant::fromValue( mProjects.at( index.row() )->deltaListModel ); + case AutoPushEnabledRole: + return mProjects.at( index.row() )->autoPushEnabled; + case AutoPushIntervalMinsRole: + return mProjects.at( index.row() )->autoPushIntervalMins; } return QVariant(); diff --git a/src/core/qfieldcloudprojectsmodel.h b/src/core/qfieldcloudprojectsmodel.h index 6ab8ffacd7..ce36a57b78 100644 --- a/src/core/qfieldcloudprojectsmodel.h +++ b/src/core/qfieldcloudprojectsmodel.h @@ -68,6 +68,8 @@ class QFieldCloudProjectsModel : public QAbstractListModel UserRoleRole, UserRoleOriginRole, DeltaListRole, + AutoPushEnabledRole, + AutoPushIntervalMinsRole, }; Q_ENUM( ColumnRole ) @@ -274,6 +276,9 @@ class QFieldCloudProjectsModel : public QAbstractListModel //! Cancels ongoing cloud project download with \a projectId. Q_INVOKABLE void projectCancelDownload( const QString &projectId ); + //! Toggles the cloud project auto-push enabled state + Q_INVOKABLE void projectSetAutoPushEnabled( const QString &projectId, bool enabled ); + signals: void cloudConnectionChanged(); void layerObserverChanged(); @@ -290,7 +295,6 @@ class QFieldCloudProjectsModel : public QAbstractListModel void projectDownloadFinished( const QString &projectId, const QString &errorString = QString() ); void deltaListModelChanged(); - // void networkDeltaUploaded( const QString &projectId ); void networkDeltaStatusChecked( const QString &projectId ); void networkAttachmentsUploaded( const QString &projectId ); @@ -429,6 +433,10 @@ class QFieldCloudProjectsModel : public QAbstractListModel QDateTime lastLocalDataLastUpdatedAt; QDateTime lastRefreshedAt; + + bool autoPushEnabled = false; + int autoPushIntervalMins = 30; + QMap jobs; }; diff --git a/src/qml/QFieldCloudPopup.qml b/src/qml/QFieldCloudPopup.qml index 532c1d0414..ff83283ce3 100644 --- a/src/qml/QFieldCloudPopup.qml +++ b/src/qml/QFieldCloudPopup.qml @@ -30,7 +30,6 @@ Popup { onFinished: { if (connectionSettings.visible) { connectionSettings.visible = false; - projects.visible = true; } else { popup.close() } @@ -466,6 +465,67 @@ Popup { Layout.bottomMargin: 5 } + RowLayout { + Layout.leftMargin: 10 + Layout.rightMargin: 10 + + Label { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + topPadding: 10 + bottomPadding: 10 + font: Theme.tipFont + wrapMode: Text.WordWrap + color: autoPush.checked ? Theme.mainTextColor : Theme.secondaryTextColor + + text: qsTr('Automatically push changes every %n minute(s)','',0 + cloudProjectsModel.currentProjectData.AutoPushIntervalMins) + + MouseArea { + anchors.fill: parent + onClicked: cloudProjectsModel.projectSetAutoPushEnabled(cloudProjectsModel.currentProjectId, !autoPush.checked) + } + } + + QfSwitch { + id: autoPush + Layout.preferredWidth: implicitContentWidth + Layout.alignment: Qt.AlignVCenter + width: implicitContentWidth + small: true + + checked: !!cloudProjectsModel.currentProjectData.AutoPushEnabled + onClicked: { + cloudProjectsModel.projectSetAutoPushEnabled(cloudProjectsModel.currentProjectId, checked) + } + } + + Timer { + id: autoPushTimer + running: !!cloudProjectsModel.currentProjectData.AutoPushEnabled + interval: (!cloudProjectsModel.currentProjectData.AutoPushIntervalMins - 0) * 60 * 1000 + repeat: true + + onRunningChanged: { + if (running && pushButton.enabled) { + const dtStr = cloudProjectsModel.currentProjectData.LastLocalPushDeltas + if (dtStr) { + const dt = new Date(dtStr) + const now = new Date() + if ((now - dt) >= interval) { + projectUpload(false) + } + } + } + } + + onTriggered: { + if (pushButton.enabled) { + projectUpload(false) + } + } + } + } + Text { id: lastExportPushText font: Theme.tipFont @@ -642,21 +702,19 @@ Popup { function projectUpload(shouldDownloadUpdates) { if (cloudProjectsModel.currentProjectData && cloudProjectsModel.currentProjectData.CanSync) { cloudProjectsModel.projectUpload(cloudProjectsModel.currentProjectId, shouldDownloadUpdates) - return } } function revertLocalChangesFromCurrentProject() { if (cloudProjectsModel.currentProjectData && cloudProjectsModel.currentProjectData.CanSync) { - if ( cloudProjectsModel.revertLocalChangesFromCurrentProject(cloudProjectsModel.currentProjectId) ) + if (cloudProjectsModel.revertLocalChangesFromCurrentProject(cloudProjectsModel.currentProjectId)) { displayToast(qsTr('Local changes reverted')) - else + } else { displayToast(qsTr('Failed to revert changes'), 'error') - - return + } + } else { + displayToast(qsTr('No changes to revert')) } - - displayToast(qsTr('No changes to revert')) } function resetCurrentProject() { diff --git a/src/qml/qgismobileapp.qml b/src/qml/qgismobileapp.qml index b0874a22c5..8791e4d329 100644 --- a/src/qml/qgismobileapp.qml +++ b/src/qml/qgismobileapp.qml @@ -3372,8 +3372,8 @@ ApplicationWindow { property bool hasInsertRights: true property bool hasEditRights: true - property bool insertRights: hasInsertRights && (cloudProjectsModel.currentProjectId == '' || cloudProjectsModel.currentProjectData.Status === QFieldCloudProjectsModel.Idle) - property bool editRights: hasEditRights && (cloudProjectsModel.currentProjectId == '' || cloudProjectsModel.currentProjectData.Status === QFieldCloudProjectsModel.Idle) + property bool insertRights: hasInsertRights + property bool editRights: hasEditRights } BusyIndicator {