Configurable max height for node tags box, localizations for new UX strings

This commit is contained in:
stopflock
2025-11-16 18:16:50 -06:00
parent b2645f1341
commit 7c2b9ea087
12 changed files with 121 additions and 61 deletions
+19 -18
View File
@@ -21,12 +21,12 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Advanced Editing Options',
locService.t('advancedEdit.title'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
'These editors offer more advanced features for complex edits.',
locService.t('advancedEdit.subtitle'),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).textTheme.bodySmall?.color,
),
@@ -35,22 +35,22 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
// Web Editors Section
Text(
'Web Editors',
locService.t('advancedEdit.webEditors'),
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',
title: locService.t('advancedEdit.iDEditor'),
subtitle: locService.t('advancedEdit.iDEditorSubtitle'),
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',
title: locService.t('advancedEdit.rapidEditor'),
subtitle: locService.t('advancedEdit.rapidEditorSubtitle'),
onTap: () => _launchEditor(context, 'https://rapideditor.org/edit#map=19/0/0&nodes=${node.id}'),
),
@@ -58,7 +58,7 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
// Mobile Editors Section
Text(
'Mobile Editors',
locService.t('advancedEdit.mobileEditors'),
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
@@ -67,22 +67,22 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
_buildEditorTile(
context: context,
icon: Icons.android,
title: 'Vespucci',
subtitle: 'Advanced Android OSM editor',
title: locService.t('advancedEdit.vespucci'),
subtitle: locService.t('advancedEdit.vespucciSubtitle'),
onTap: () => _launchEditor(context, 'vespucci://edit?node=${node.id}'),
),
_buildEditorTile(
context: context,
icon: Icons.place,
title: 'StreetComplete',
subtitle: 'Survey-based mapping app',
title: locService.t('advancedEdit.streetComplete'),
subtitle: locService.t('advancedEdit.streetCompleteSubtitle'),
onTap: () => _launchEditor(context, 'streetcomplete://quest?node=${node.id}'),
),
_buildEditorTile(
context: context,
icon: Icons.map,
title: 'EveryDoor',
subtitle: 'Fast POI editing',
title: locService.t('advancedEdit.everyDoor'),
subtitle: locService.t('advancedEdit.everyDoorSubtitle'),
onTap: () => _launchEditor(context, 'everydoor://edit?node=${node.id}'),
),
],
@@ -91,8 +91,8 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
_buildEditorTile(
context: context,
icon: Icons.phone_iphone,
title: 'Go Map!!',
subtitle: 'iOS OSM editor',
title: locService.t('advancedEdit.goMap'),
subtitle: locService.t('advancedEdit.goMapSubtitle'),
onTap: () => _launchEditor(context, 'gomaposm://edit?node=${node.id}'),
),
],
@@ -131,6 +131,7 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
}
void _launchEditor(BuildContext context, String url) async {
final locService = LocalizationService.instance;
Navigator.pop(context); // Close the sheet first
try {
@@ -140,14 +141,14 @@ class AdvancedEditOptionsSheet extends StatelessWidget {
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not open $url - app may not be installed')),
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenEditor'))),
);
}
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not open editor - app may not be installed')),
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenEditor'))),
);
}
}
+1 -1
View File
@@ -236,7 +236,7 @@ class EditNodeSheet extends StatelessWidget {
OutlinedButton.icon(
onPressed: () => _openAdvancedEdit(context),
icon: const Icon(Icons.open_in_new, size: 16),
label: const Text('Use Advanced Editor'),
label: Text(locService.t('actions.useAdvancedEditor')),
style: OutlinedButton.styleFrom(
minimumSize: const Size(0, 32),
),
+40 -27
View File
@@ -5,6 +5,7 @@ import 'package:flutter_linkify/flutter_linkify.dart';
import '../models/osm_node.dart';
import '../app_state.dart';
import '../services/localization_service.dart';
import '../dev_config.dart';
import 'advanced_edit_options_sheet.dart';
class NodeTagSheet extends StatelessWidget {
@@ -79,14 +80,14 @@ class NodeTagSheet extends StatelessWidget {
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not open OSM website')),
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))),
);
}
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not open OSM website')),
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))),
);
}
}
@@ -113,21 +114,29 @@ class NodeTagSheet extends StatelessWidget {
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
...node.tags.entries.map(
(e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
// Constrain tag list height to keep buttons and map visible
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * kMaxTagListHeightRatio,
),
child: SingleChildScrollView(
child: Column(
children: [
Text(
e.key,
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(width: 8),
Expanded(
...node.tags.entries.map(
(e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
e.key,
style: TextStyle(
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(width: 8),
Expanded(
child: Linkify(
onOpen: (link) async {
final uri = Uri.parse(link.url);
@@ -135,19 +144,23 @@ class NodeTagSheet extends StatelessWidget {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not open URL: ${link.url}')),
SnackBar(content: Text('${LocalizationService.instance.t('advancedEdit.couldNotOpenURL')}: ${link.url}')),
);
}
},
text: e.value,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
text: e.value,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
linkStyle: TextStyle(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
options: const LinkifyOptions(humanize: false),
),
),
],
),
linkStyle: TextStyle(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
options: const LinkifyOptions(humanize: false),
),
),
],
@@ -162,14 +175,14 @@ class NodeTagSheet extends StatelessWidget {
TextButton.icon(
onPressed: () => _viewOnOSM(),
icon: const Icon(Icons.open_in_new, size: 16),
label: const Text('View on OSM'),
label: Text(locService.t('actions.viewOnOSM')),
),
const SizedBox(width: 8),
if (isEditable) ...[
OutlinedButton.icon(
onPressed: _openAdvancedEdit,
icon: const Icon(Icons.open_in_new, size: 18),
label: const Text('Advanced'),
label: Text(locService.t('actions.advanced')),
style: OutlinedButton.styleFrom(
minimumSize: const Size(0, 36),
),
+2 -1
View File
@@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../models/suspected_location.dart';
import '../app_state.dart';
import '../services/localization_service.dart';
import '../dev_config.dart';
class SuspectedLocationSheet extends StatelessWidget {
final SuspectedLocation location;
@@ -47,7 +48,7 @@ class SuspectedLocationSheet extends StatelessWidget {
// Constrain field list height to keep buttons visible
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4, // Max 40% of screen height
maxHeight: MediaQuery.of(context).size.height * kMaxTagListHeightRatio,
),
child: SingleChildScrollView(
child: Column(