Repository navigation
Conversation
Coverage Report for CI Build 37486743244Coverage increased (+0.1%) to 59.659%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions625 previously-covered lines in 8 files lost coverage.
Coverage Stats
💛 - Coveralls |
📦 Build Artifacts Ready
|
Withalion
left a comment
There was a problem hiding this comment.
Great job, it looks very promising! There are some trivial things, a bit more refactoring is needed, but mainly unit tests.
UI/UX
- It would be nice to navigate to the geometry you will be recording further after you open the draft
- After user is done recording geometry and doesn't fill out anything in the form yet, application crashes. We should open the form right away and not the geometry recording
- When a draft is available and an existing feature is clicked to edit the warning drawer opens and closes right away
- When a draft is available and "add" button is clicked, the drawer opens correctly, but discarding draft doesn't start recording mode, but stays in "view" mode of map
Withalion
left a comment
There was a problem hiding this comment.
Great job, it looks very promising! There are some trivial things, a bit more refactoring is needed, but mainly unit tests.
UI/UX
- It would be nice to navigate to the geometry you will be recording further after you open the draft
- After user is done recording geometry and doesn't fill out anything in the form yet, application crashes. We should open the form right away and not the geometry recording
- When a draft is available and an existing feature is clicked to edit the warning drawer opens and closes right away
- When a draft is available and "add" button is clicked, the drawer opens correctly, but discarding draft doesn't start recording mode, but stays in "view" mode of map
b242e2a to
d02dcfb
Compare
📦 Build Artifacts Ready
|
tomasMizera
left a comment
There was a problem hiding this comment.
Nice! :)
When testing, let's try it out on some low-end devices to see if the 1 second interval is not causing too much slowness in UI. If so, we might need to move the saving part to another thread.
In general, this is going in a very good direction, I believe we will have this ready for testing soon.
| connect( this, &RecordingMapTool::recordedGeometryChanged, this, [ this ]() | ||
| { | ||
| mDraftSaveTimer->start(); | ||
| } ); |
There was a problem hiding this comment.
The signal sounds good to me too for now.
|
|
||
| void FeatureDraftController::checkForDraft() | ||
| { | ||
| const QString projectId = QgsProject::instance()->homePath(); |
There was a problem hiding this comment.
This one's not resolved and I am +1 to use project IDs instead of homepath.
📦 Build Artifacts Ready
|
|
@Withalion everything should be resolved. |
📦 Build Artifacts Ready
|
📦 Build Artifacts Ready
|
Withalion
left a comment
There was a problem hiding this comment.
Few more comments after your changes. I'll let @tomasMizera have the final review
| bool isEmpty() const { return layerId.isEmpty(); } | ||
|
|
||
| // Whether this draft belongs to an existing feature being edited, rather than a new one being added | ||
| bool isExistingFeature() const { return !FID_IS_NULL( featureId ) && !FID_IS_NEW( featureId ); } |
There was a problem hiding this comment.
I would call here InputUtils::isFeatureIdValid()
| enum DraftStage | ||
| { | ||
| GeometryCapture, | ||
| AttributeForm | ||
| }; | ||
| Q_ENUM( DraftStage ) |
There was a problem hiding this comment.
Keep this enum instead of FeatureDraft::Stage and if necessary reuse it in FeatureDraft. Also define the enum as enum class DraftStage
| //! Name of the layer the pending draft belongs to (only meaningful when hasDraft is true) | ||
| Q_PROPERTY( QString draftLayerName READ draftLayerName NOTIFY hasDraftChanged ) |
There was a problem hiding this comment.
is it necessary to expose it separately to the layer object? draftLayer.name should return the same value. Or is there some QML limitation?
We could as well expose the property, but internally just keep the reference to the layer instead of another object property.
| QString mDraftLayerName; | ||
| QgsVectorLayer *mDraftLayer = nullptr; // not owned | ||
| DraftStage mDraftStage = AttributeForm; | ||
| bool mDraftIsExistingFeature = false; |
There was a problem hiding this comment.
i would remove mDraftIsExistingFeature as well just query FeatureDraft for this info
| FeatureDraftController::DraftStage FeatureDraftController::toQmlStage( FeatureDraft::Stage stage ) | ||
| { | ||
| return stage == FeatureDraft::GeometryCapture ? GeometryCapture : AttributeForm; | ||
| } |
There was a problem hiding this comment.
This smells you should not have to do it
| import QtQuick.Layouts | ||
|
|
||
| import "../../components" as MMComponents | ||
| import "." |
There was a problem hiding this comment.
"local" components should be available by default without this import
|
|
||
| property var project | ||
| property var featureLayerPair | ||
| property bool restoringDraft: false // see AttributeController.restoringDraft |
There was a problem hiding this comment.
this could be just alias then
| // action to run after the draft is discarded: "record" or "addFeature" | ||
| property string pendingAction: "" | ||
| property var pendingActionLayer: null // layer for "addFeature" | ||
|
|
There was a problem hiding this comment.
I don't like this here and ApplicationWindow doesn't look like the right place for it. Let's hide it at least to internal QtObject or think about some other way.
| const pair = controller.loadDraft() | ||
|
|
||
| // the draft is no longer valid and was discarded | ||
| if ( !pair.layer ) { |
There was a problem hiding this comment.
| if ( !pair.layer ) { | |
| if ( !pair.valid ) { |
| qRegisterMetaType< FeatureLayerPair * >( "FeatureLayerPair*" ); | ||
| qRegisterMetaType< AttributeController * >( "AttributeController*" ); | ||
| qmlRegisterUncreatableType<NotificationType>( "mm", 1, 0, "NotificationType", "NotificationType Enum" ); | ||
| qmlRegisterUncreatableType<FeatureDraftController>( "mm", 1, 0, "FeatureDraftController", "FeatureDraftController Enum" ); |
There was a problem hiding this comment.
Description
Adds automatic recovery of in-progress feature edits. If the app closes unexpectedly while you're editing a feature - a crash, an incoming call, a dead battery - your unsaved geometry and attribute changes are no longer lost. On the next launch you're offered the chance to resume exactly where you left off, or discard the changes.
Fixes: #4585
What changed
FeatureDraftStorage/FeatureDraftController(app/drafts/) persist an in-progress edit (geometry + attributes) as a JSON blob viaQSettings, debounced by ~1s so it isn't written on every keystroke/vertex.AttributeControllerandRecordingMapToolwrite a draft whenever attributes or geometry are edited, and clear it once the feature is saved, deleted, or the edit is cancelled.ActiveProjectexposes the draft controller to QML.Behaviour
Screen_Recording_20260818-082140_One.UI.Home.mp4
Screen_Recording_20260818-082018_One.UI.Home.mp4
Screen_Recording_20260818-082039.mp4
TLDR @Withalion
Feature edits are now drafted automatically so a crash, call, or dead battery doesn't cost you your unsaved work - surfaced via a notification and resume/discard drawers.