mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-09-27 11:31:44 +02:00
Link to OSM in node_details sheet. Add option to open node in other editors.
This commit is contained in:
@@ -107,10 +107,8 @@ cp lib/keys.dart.example lib/keys.dart
|
|||||||
- Persistent cache for MY submissions: clean up when we see that node appear in overpass results or when older than 24h
|
- Persistent cache for MY submissions: clean up when we see that node appear in overpass results or when older than 24h
|
||||||
- Dropdown on "refine tags" page to select acceptable options for camera:mount=
|
- Dropdown on "refine tags" page to select acceptable options for camera:mount=
|
||||||
- Tutorial / info guide before submitting first node
|
- Tutorial / info guide before submitting first node
|
||||||
- Link to OSM node in node_details_sheet
|
|
||||||
- Link to "my changes" on osm (username edit history)
|
- Link to "my changes" on osm (username edit history)
|
||||||
- Option to "extract node from way" for nodes attached to a way to allow moving
|
- Option to "extract node from way" for nodes attached to a way to allow moving
|
||||||
- Option to "open in other editor" for advanced edits: StreetComplete/EveryDoor/Vespucci/GO!! Map/OSM.org(iD)/Rapid/Level0/OSMand/OrganicMaps/CoMaps
|
|
||||||
|
|
||||||
### On Pause
|
### On Pause
|
||||||
- Suspected locations expansion to more regions
|
- Suspected locations expansion to more regions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"1.3.3": {
|
"1.3.3": {
|
||||||
"content": "• NEW: Added builtin surveillance device profiles for Rekor and Axis Communications ALPR cameras"
|
"content": "• NEW: Added builtin surveillance device profiles for Rekor and Axis Communications ALPR cameras\n• NEW: Smart constraint system prevents moving nodes that are part of OSM ways/relations to protect data integrity\n• NEW: \"View on OSM\" button in node details opens the node page on OpenStreetMap.org\n• NEW: \"Advanced Edit\" options with links to iD Editor, RapiD, Vespucci, StreetComplete, Go Map!!, and EveryDoor\n• IMPROVED: Network status messages are now fully localized in all supported languages\n• IMPROVED: Map interaction controls for constrained nodes - zoom/rotate allowed, movement prevented\n• IMPROVED: Debounced snap-back system keeps constrained nodes centered smoothly\n• FIXED: Eliminated duplicate changelog service calls on app updates\n• FIXED: Network status now correctly says \"Node data slow\" instead of \"Camera data slow\""
|
||||||
},
|
},
|
||||||
"1.3.2": {
|
"1.3.2": {
|
||||||
"content": "• HOTFIX: Temporarily disabled node editing to prevent OSM database issues while a bug is resolved\n• UX: Fixed Android navigation bar covering settings page content"
|
"content": "• HOTFIX: Temporarily disabled node editing to prevent OSM database issues while a bug is resolved\n• UX: Fixed Android navigation bar covering settings page content"
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import '../models/osm_node.dart';
|
||||||
|
import '../services/localization_service.dart';
|
||||||
|
|
||||||
|
class AdvancedEditOptionsSheet extends StatelessWidget {
|
||||||
|
final OsmNode node;
|
||||||
|
|
||||||
|
const AdvancedEditOptionsSheet({super.key, required this.node});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final locService = LocalizationService.instance;
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Advanced Editing Options',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'These editors offer more advanced features for complex edits.',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Web Editors Section
|
||||||
|
Text(
|
||||||
|
'Web Editors',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildEditorTile(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.public,
|
||||||
|
title: 'iD Editor',
|
||||||
|
subtitle: 'Full-featured web editor - always works',
|
||||||
|
onTap: () => _launchEditor(context, 'https://www.openstreetmap.org/edit?editor=id&node=${node.id}'),
|
||||||
|
),
|
||||||
|
_buildEditorTile(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.speed,
|
||||||
|
title: 'RapiD Editor',
|
||||||
|
subtitle: 'AI-assisted editing with Facebook data',
|
||||||
|
onTap: () => _launchEditor(context, 'https://rapideditor.org/edit#map=19/0/0&nodes=${node.id}'),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Mobile Editors Section
|
||||||
|
Text(
|
||||||
|
'Mobile Editors',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
if (Platform.isAndroid) ...[
|
||||||
|
_buildEditorTile(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.android,
|
||||||
|
title: 'Vespucci',
|
||||||
|
subtitle: 'Advanced Android OSM editor',
|
||||||
|
onTap: () => _launchEditor(context, 'vespucci://edit?node=${node.id}'),
|
||||||
|
),
|
||||||
|
_buildEditorTile(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.place,
|
||||||
|
title: 'StreetComplete',
|
||||||
|
subtitle: 'Survey-based mapping app',
|
||||||
|
onTap: () => _launchEditor(context, 'streetcomplete://quest?node=${node.id}'),
|
||||||
|
),
|
||||||
|
_buildEditorTile(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.map,
|
||||||
|
title: 'EveryDoor',
|
||||||
|
subtitle: 'Fast POI editing',
|
||||||
|
onTap: () => _launchEditor(context, 'everydoor://edit?node=${node.id}'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
if (Platform.isIOS) ...[
|
||||||
|
_buildEditorTile(
|
||||||
|
context: context,
|
||||||
|
icon: Icons.phone_iphone,
|
||||||
|
title: 'Go Map!!',
|
||||||
|
subtitle: 'iOS OSM editor',
|
||||||
|
onTap: () => _launchEditor(context, 'gomaposm://edit?node=${node.id}'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(locService.t('actions.close')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEditorTile({
|
||||||
|
required BuildContext context,
|
||||||
|
required IconData icon,
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(icon, size: 24),
|
||||||
|
title: Text(title),
|
||||||
|
subtitle: Text(subtitle),
|
||||||
|
trailing: const Icon(Icons.launch, size: 18),
|
||||||
|
onTap: onTap,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _launchEditor(BuildContext context, String url) async {
|
||||||
|
Navigator.pop(context); // Close the sheet first
|
||||||
|
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
} else {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Could not open $url - app may not be installed')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Could not open editor - app may not be installed')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import '../models/operator_profile.dart';
|
|||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
import '../state/settings_state.dart';
|
import '../state/settings_state.dart';
|
||||||
import 'refine_tags_sheet.dart';
|
import 'refine_tags_sheet.dart';
|
||||||
|
import 'advanced_edit_options_sheet.dart';
|
||||||
|
|
||||||
class EditNodeSheet extends StatelessWidget {
|
class EditNodeSheet extends StatelessWidget {
|
||||||
const EditNodeSheet({super.key, required this.session});
|
const EditNodeSheet({super.key, required this.session});
|
||||||
@@ -214,15 +215,33 @@ class EditNodeSheet extends StatelessWidget {
|
|||||||
if (session.originalNode.isConstrained)
|
if (session.originalNode.isConstrained)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
||||||
child: Row(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.info_outline, size: 20),
|
Row(
|
||||||
const SizedBox(width: 8),
|
children: [
|
||||||
Expanded(
|
const Icon(Icons.info_outline, size: 20),
|
||||||
child: Text(
|
const SizedBox(width: 8),
|
||||||
locService.t('editNode.cannotMoveConstrainedNode'),
|
Expanded(
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
child: Text(
|
||||||
),
|
locService.t('editNode.cannotMoveConstrainedNode'),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () => _openAdvancedEdit(context),
|
||||||
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
|
label: const Text('Use Advanced Editor'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
minimumSize: const Size(0, 32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -349,4 +368,12 @@ class EditNodeSheet extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _openAdvancedEdit(BuildContext context) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => AdvancedEditOptionsSheet(node: session.originalNode),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../models/osm_node.dart';
|
import '../models/osm_node.dart';
|
||||||
import '../app_state.dart';
|
import '../app_state.dart';
|
||||||
import '../services/localization_service.dart';
|
import '../services/localization_service.dart';
|
||||||
|
import 'advanced_edit_options_sheet.dart';
|
||||||
|
|
||||||
class NodeTagSheet extends StatelessWidget {
|
class NodeTagSheet extends StatelessWidget {
|
||||||
final OsmNode node;
|
final OsmNode node;
|
||||||
@@ -67,6 +69,36 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _viewOnOSM() async {
|
||||||
|
final url = 'https://www.openstreetmap.org/node/${node.id}';
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
} else {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Could not open OSM website')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Could not open OSM website')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openAdvancedEdit() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => AdvancedEditOptionsSheet(node: node),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
|
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
|
||||||
@@ -108,6 +140,30 @@ class NodeTagSheet extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
// First row: View and Advanced buttons
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () => _viewOnOSM(),
|
||||||
|
icon: const Icon(Icons.open_in_new, size: 16),
|
||||||
|
label: const Text('View on OSM'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
if (isEditable) ...[
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: _openAdvancedEdit,
|
||||||
|
icon: const Icon(Icons.open_in_new, size: 18),
|
||||||
|
label: const Text('Advanced'),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
minimumSize: const Size(0, 36),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// Second row: Edit, Delete, and Close buttons
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
Reference in New Issue
Block a user