mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-09 22:18:38 +02:00
Pass through all extraneous tags on an existing node, always. Strip out special case for "existing tags" profile, replace with an empty temp profile so all can be treated the same.
This commit is contained in:
@@ -154,28 +154,40 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
|
||||
/// Check if the edit session has any actual changes compared to the original node
|
||||
bool _hasActualChanges(EditNodeSession session) {
|
||||
debugPrint('EditNodeSheet: Checking for actual changes...');
|
||||
|
||||
// Extract operation is always a change
|
||||
if (session.extractFromWay) return true;
|
||||
if (session.extractFromWay) {
|
||||
debugPrint('EditNodeSheet: Extract operation detected - changes found');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check location change
|
||||
const double tolerance = 0.0000001; // ~1cm precision
|
||||
if ((session.target.latitude - session.originalNode.coord.latitude).abs() > tolerance ||
|
||||
(session.target.longitude - session.originalNode.coord.longitude).abs() > tolerance) {
|
||||
debugPrint('EditNodeSheet: Location change detected - changes found');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check direction changes
|
||||
if (!_directionsEqual(session.directions, session.originalNode.directionDeg)) {
|
||||
debugPrint('EditNodeSheet: Direction change detected - changes found');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check tag changes (including operator profile)
|
||||
final originalTags = session.originalNode.tags;
|
||||
final newTags = _getSessionCombinedTags(session);
|
||||
debugPrint('EditNodeSheet: Original tags: $originalTags');
|
||||
debugPrint('EditNodeSheet: New combined tags: $newTags');
|
||||
|
||||
if (!_tagsEqual(originalTags, newTags)) {
|
||||
debugPrint('EditNodeSheet: Tag changes detected - changes found');
|
||||
return true;
|
||||
}
|
||||
|
||||
debugPrint('EditNodeSheet: No changes detected');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -238,9 +250,11 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
profile: session.profile,
|
||||
operatorProfile: session.operatorProfile,
|
||||
refinedTags: session.refinedTags,
|
||||
additionalExistingTags: session.additionalExistingTags, // Include additional existing tags!
|
||||
changesetComment: session.changesetComment, // Required parameter
|
||||
uploadMode: UploadMode.production, // Mode doesn't matter for tag combination
|
||||
operation: UploadOperation.modify,
|
||||
originalNodeId: session.originalNode.id, // Required for modify operations
|
||||
);
|
||||
|
||||
return tempUpload.getCombinedTags();
|
||||
@@ -491,6 +505,7 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
selectedOperatorProfile: session.operatorProfile,
|
||||
selectedProfile: session.profile,
|
||||
currentRefinedTags: session.refinedTags,
|
||||
currentAdditionalExistingTags: session.additionalExistingTags,
|
||||
originalNodeTags: session.originalNode.tags,
|
||||
operation: session.extractFromWay ? UploadOperation.extract : UploadOperation.modify,
|
||||
),
|
||||
@@ -498,24 +513,17 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
),
|
||||
);
|
||||
if (result != null) {
|
||||
if (result.editedTags != null && session.profile?.isExistingTagsProfile == true) {
|
||||
// Update the existing tags profile with the edited tags
|
||||
final updatedProfile = session.profile!.copyWith(
|
||||
tags: result.editedTags,
|
||||
);
|
||||
appState.updateEditSession(
|
||||
profile: updatedProfile,
|
||||
operatorProfile: result.operatorProfile,
|
||||
refinedTags: result.refinedTags,
|
||||
changesetComment: result.changesetComment,
|
||||
);
|
||||
} else {
|
||||
appState.updateEditSession(
|
||||
operatorProfile: result.operatorProfile,
|
||||
refinedTags: result.refinedTags,
|
||||
changesetComment: result.changesetComment,
|
||||
);
|
||||
}
|
||||
debugPrint('EditNodeSheet: Updating session from refine tags result');
|
||||
debugPrint('EditNodeSheet: Profile: ${session.profile?.name}');
|
||||
debugPrint('EditNodeSheet: AdditionalExistingTags: ${result.additionalExistingTags}');
|
||||
debugPrint('EditNodeSheet: Current session additionalExistingTags: ${session.additionalExistingTags}');
|
||||
|
||||
appState.updateEditSession(
|
||||
operatorProfile: result.operatorProfile,
|
||||
refinedTags: result.refinedTags,
|
||||
additionalExistingTags: result.additionalExistingTags,
|
||||
changesetComment: result.changesetComment,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,7 +757,7 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
// Display name for the current profile - localize the existing tags profile
|
||||
String getDisplayName(NodeProfile? profile) {
|
||||
if (profile == null) return locService.t('editNode.selectProfile');
|
||||
if (profile.isExistingTagsProfile) {
|
||||
if (profile.id.startsWith('temp-empty-')) {
|
||||
return locService.t('editNode.existingTags');
|
||||
}
|
||||
return profile.name;
|
||||
|
||||
@@ -11,13 +11,13 @@ import 'nsi_tag_value_field.dart';
|
||||
class RefineTagsResult {
|
||||
final OperatorProfile? operatorProfile;
|
||||
final Map<String, String> refinedTags;
|
||||
final Map<String, String>? editedTags; // For existing tags profile mode
|
||||
final Map<String, String>? additionalExistingTags; // For tags that exist on node but not in selected profile
|
||||
final String changesetComment; // User-editable changeset comment
|
||||
|
||||
RefineTagsResult({
|
||||
required this.operatorProfile,
|
||||
required this.refinedTags,
|
||||
this.editedTags,
|
||||
this.additionalExistingTags,
|
||||
required this.changesetComment,
|
||||
});
|
||||
}
|
||||
@@ -28,6 +28,7 @@ class RefineTagsSheet extends StatefulWidget {
|
||||
this.selectedOperatorProfile,
|
||||
this.selectedProfile,
|
||||
this.currentRefinedTags,
|
||||
this.currentAdditionalExistingTags,
|
||||
this.originalNodeTags,
|
||||
required this.operation,
|
||||
});
|
||||
@@ -35,6 +36,7 @@ class RefineTagsSheet extends StatefulWidget {
|
||||
final OperatorProfile? selectedOperatorProfile;
|
||||
final NodeProfile? selectedProfile;
|
||||
final Map<String, String>? currentRefinedTags;
|
||||
final Map<String, String>? currentAdditionalExistingTags;
|
||||
final Map<String, String>? originalNodeTags;
|
||||
final UploadOperation operation;
|
||||
|
||||
@@ -46,8 +48,10 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
OperatorProfile? _selectedOperatorProfile;
|
||||
Map<String, String> _refinedTags = {};
|
||||
|
||||
// For existing tags profile: full tag editing
|
||||
late List<MapEntry<String, String>> _editableTags;
|
||||
|
||||
|
||||
// For additional existing tags (tags on node but not in profile)
|
||||
late List<MapEntry<String, String>> _additionalExistingTags;
|
||||
|
||||
// Changeset comment editing
|
||||
late final TextEditingController _commentController;
|
||||
@@ -58,15 +62,13 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
_selectedOperatorProfile = widget.selectedOperatorProfile;
|
||||
_refinedTags = Map<String, String>.from(widget.currentRefinedTags ?? {});
|
||||
|
||||
// Pre-populate refined tags with existing node values for empty profile tags
|
||||
_prePopulateWithExistingValues();
|
||||
// Note: Pre-population is now handled by SessionState when profile changes
|
||||
// _refinedTags is already initialized with the session's refinedTags above
|
||||
|
||||
// Initialize editable tags for existing tags profile
|
||||
if (widget.selectedProfile?.isExistingTagsProfile == true) {
|
||||
_editableTags = widget.selectedProfile!.tags.entries.toList();
|
||||
} else {
|
||||
_editableTags = [];
|
||||
}
|
||||
|
||||
|
||||
// Initialize additional existing tags (tags on node but not in profile)
|
||||
_initializeAdditionalExistingTags();
|
||||
|
||||
// Initialize changeset comment with default
|
||||
final defaultComment = AppState.generateDefaultChangesetComment(
|
||||
@@ -82,29 +84,11 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Pre-populate refined tags with existing values from the original node
|
||||
void _prePopulateWithExistingValues() {
|
||||
if (widget.selectedProfile == null || widget.originalNodeTags == null) return;
|
||||
|
||||
// Get refinable tags (empty values in profile)
|
||||
final refinableTags = _getRefinableTags();
|
||||
|
||||
// For each refinable tag, check if original node has a value
|
||||
for (final tagKey in refinableTags) {
|
||||
// Only pre-populate if we don't already have a refined value for this tag
|
||||
if (!_refinedTags.containsKey(tagKey)) {
|
||||
final existingValue = widget.originalNodeTags![tagKey];
|
||||
if (existingValue != null && existingValue.trim().isNotEmpty) {
|
||||
_refinedTags[tagKey] = existingValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get list of tag keys that have empty values and can be refined
|
||||
List<String> _getRefinableTags() {
|
||||
if (widget.selectedProfile == null) return [];
|
||||
if (widget.selectedProfile!.isExistingTagsProfile) return []; // Use full editing mode instead
|
||||
|
||||
return widget.selectedProfile!.tags.entries
|
||||
.where((entry) => entry.value.trim().isEmpty)
|
||||
@@ -112,8 +96,82 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Returns true if this is the existing tags profile requiring full editing
|
||||
bool get _isExistingTagsMode => widget.selectedProfile?.isExistingTagsProfile == true;
|
||||
/// Initialize additional existing tags (tags that exist on the node but not in the selected profile)
|
||||
void _initializeAdditionalExistingTags() {
|
||||
// Use the additional existing tags calculated by SessionState when profile changed
|
||||
if (widget.currentAdditionalExistingTags != null) {
|
||||
_additionalExistingTags = widget.currentAdditionalExistingTags!.entries.toList();
|
||||
debugPrint('RefineTagsSheet: Loaded ${_additionalExistingTags.length} additional existing tags from session');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: calculate them here if not provided (shouldn't normally happen)
|
||||
_additionalExistingTags = [];
|
||||
|
||||
// Skip if we don't have the required data
|
||||
if (widget.originalNodeTags == null || widget.selectedProfile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get tags from the original node that are not in the selected profile
|
||||
final profileTagKeys = widget.selectedProfile!.tags.keys.toSet();
|
||||
final originalTags = widget.originalNodeTags!;
|
||||
|
||||
for (final entry in originalTags.entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value;
|
||||
|
||||
// Skip tags that are handled elsewhere
|
||||
if (_shouldSkipTag(key)) continue;
|
||||
|
||||
// Skip tags that exist in the selected profile
|
||||
if (profileTagKeys.contains(key)) continue;
|
||||
|
||||
// Include this tag as an additional existing tag
|
||||
_additionalExistingTags.add(MapEntry(key, value));
|
||||
}
|
||||
|
||||
debugPrint('RefineTagsSheet: Fallback calculated ${_additionalExistingTags.length} additional existing tags');
|
||||
}
|
||||
|
||||
/// Check if a tag should be skipped from additional existing tags
|
||||
bool _shouldSkipTag(String key) {
|
||||
// Skip direction tags (handled separately)
|
||||
if (key == 'direction' || key == 'camera:direction') return true;
|
||||
|
||||
// Skip operator tags (handled by operator profile)
|
||||
if (key == 'operator' || key.startsWith('operator:')) return true;
|
||||
|
||||
// Skip internal cache tags
|
||||
if (key.startsWith('_')) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns true if we should show the additional existing tags section
|
||||
bool get _shouldShowAdditionalExistingTags => _hasAdditionalExistingTagsToManage;
|
||||
|
||||
/// Returns true if we have additional existing tags to manage (even if user deleted them all)
|
||||
bool get _hasAdditionalExistingTagsToManage {
|
||||
// Check if we originally had additional existing tags OR if user has added new ones
|
||||
if (widget.currentAdditionalExistingTags != null && widget.currentAdditionalExistingTags!.isNotEmpty) {
|
||||
return true; // We loaded some from the session
|
||||
}
|
||||
|
||||
// Fallback: check if we calculated any from the original node
|
||||
if (widget.originalNodeTags != null && widget.selectedProfile != null) {
|
||||
final profileTagKeys = widget.selectedProfile!.tags.keys.toSet();
|
||||
final originalTags = widget.originalNodeTags!;
|
||||
|
||||
for (final entry in originalTags.entries) {
|
||||
if (_shouldSkipTag(entry.key)) continue;
|
||||
if (profileTagKeys.contains(entry.key)) continue;
|
||||
return true; // Found at least one additional existing tag
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -132,21 +190,28 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
onPressed: () => Navigator.pop(context, RefineTagsResult(
|
||||
operatorProfile: widget.selectedOperatorProfile,
|
||||
refinedTags: widget.currentRefinedTags ?? {},
|
||||
editedTags: _isExistingTagsMode ? widget.selectedProfile?.tags : null,
|
||||
additionalExistingTags: _hasAdditionalExistingTagsToManage
|
||||
? Map<String, String>.fromEntries(_additionalExistingTags.where((e) => e.key.isNotEmpty))
|
||||
: null,
|
||||
changesetComment: _commentController.text,
|
||||
)),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final editedTags = _isExistingTagsMode
|
||||
? Map<String, String>.fromEntries(_editableTags.where((e) => e.key.isNotEmpty))
|
||||
final additionalTags = _hasAdditionalExistingTagsToManage
|
||||
? Map<String, String>.fromEntries(_additionalExistingTags.where((e) => e.key.isNotEmpty))
|
||||
: null;
|
||||
|
||||
debugPrint('RefineTagsSheet: Returning result');
|
||||
debugPrint('RefineTagsSheet: additionalTags: $additionalTags');
|
||||
debugPrint('RefineTagsSheet: _additionalExistingTags: $_additionalExistingTags');
|
||||
debugPrint('RefineTagsSheet: _shouldShowAdditionalExistingTags: $_shouldShowAdditionalExistingTags');
|
||||
|
||||
Navigator.pop(context, RefineTagsResult(
|
||||
operatorProfile: _selectedOperatorProfile,
|
||||
refinedTags: _refinedTags,
|
||||
editedTags: editedTags,
|
||||
additionalExistingTags: additionalTags,
|
||||
changesetComment: _commentController.text,
|
||||
));
|
||||
},
|
||||
@@ -276,10 +341,13 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
),
|
||||
],
|
||||
],
|
||||
// Add refineable tags section OR existing tags editing section
|
||||
...(_isExistingTagsMode
|
||||
? _buildExistingTagsEditingSection(locService)
|
||||
: _buildRefinableTagsSection(locService)),
|
||||
// Add refineable tags section
|
||||
..._buildRefinableTagsSection(locService),
|
||||
|
||||
// Add additional existing tags section
|
||||
...(_shouldShowAdditionalExistingTags
|
||||
? _buildAdditionalExistingTagsSection(locService)
|
||||
: []),
|
||||
|
||||
// Changeset comment section
|
||||
const SizedBox(height: 16),
|
||||
@@ -371,20 +439,22 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Build the section for full tag editing (existing tags profile mode)
|
||||
List<Widget> _buildExistingTagsEditingSection(LocalizationService locService) {
|
||||
|
||||
|
||||
/// Build the section for additional existing tags (tags on node but not in profile)
|
||||
List<Widget> _buildAdditionalExistingTagsSection(LocalizationService locService) {
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
locService.t('refineTagsSheet.existingTagsTitle'),
|
||||
'Additional Existing Tags',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
onPressed: _addNewTag,
|
||||
onPressed: _addNewAdditionalTag,
|
||||
tooltip: 'Add new tag',
|
||||
),
|
||||
],
|
||||
@@ -397,20 +467,20 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
locService.t('refineTagsSheet.existingTagsDescription'),
|
||||
'These tags exist on the original node but are not part of the selected profile. You can edit or remove them.',
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (_editableTags.isEmpty)
|
||||
if (_additionalExistingTags.isEmpty)
|
||||
Text(
|
||||
'No tags defined.',
|
||||
'No additional existing tags.',
|
||||
style: const TextStyle(color: Colors.grey, fontStyle: FontStyle.italic),
|
||||
)
|
||||
else
|
||||
..._editableTags.asMap().entries.map((entry) {
|
||||
..._additionalExistingTags.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final tag = entry.value;
|
||||
return _buildFullTagEditor(index, tag.key, tag.value, locService);
|
||||
return _buildAdditionalTagEditor(index, tag.key, tag.value, locService);
|
||||
}),
|
||||
],
|
||||
),
|
||||
@@ -419,8 +489,8 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
];
|
||||
}
|
||||
|
||||
/// Build a full tag editor row with key, value, and delete button
|
||||
Widget _buildFullTagEditor(int index, String key, String value, LocalizationService locService) {
|
||||
/// Build a tag editor row for additional existing tags
|
||||
Widget _buildAdditionalTagEditor(int index, String key, String value, LocalizationService locService) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: Row(
|
||||
@@ -438,7 +508,7 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
),
|
||||
onChanged: (newKey) {
|
||||
setState(() {
|
||||
_editableTags[index] = MapEntry(newKey, _editableTags[index].value);
|
||||
_additionalExistingTags[index] = MapEntry(newKey, _additionalExistingTags[index].value);
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -448,13 +518,13 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: NSITagValueField(
|
||||
key: ValueKey('${key}_${index}_edit'),
|
||||
key: ValueKey('${key}_${index}_additional'),
|
||||
tagKey: key,
|
||||
initialValue: value,
|
||||
hintText: 'Tag value',
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_editableTags[index] = MapEntry(_editableTags[index].key, newValue);
|
||||
_additionalExistingTags[index] = MapEntry(_additionalExistingTags[index].key, newValue);
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -463,7 +533,7 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
// Delete button
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove_circle_outline, color: Colors.red, size: 20),
|
||||
onPressed: () => _removeTag(index),
|
||||
onPressed: () => _removeAdditionalTag(index),
|
||||
tooltip: 'Remove tag',
|
||||
),
|
||||
],
|
||||
@@ -471,17 +541,17 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Add a new empty tag
|
||||
void _addNewTag() {
|
||||
/// Add a new empty additional tag
|
||||
void _addNewAdditionalTag() {
|
||||
setState(() {
|
||||
_editableTags.add(const MapEntry('', ''));
|
||||
_additionalExistingTags.add(const MapEntry('', ''));
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove a tag by index
|
||||
void _removeTag(int index) {
|
||||
/// Remove an additional tag by index
|
||||
void _removeAdditionalTag(int index) {
|
||||
setState(() {
|
||||
_editableTags.removeAt(index);
|
||||
_additionalExistingTags.removeAt(index);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user