Limit tag list size, make changelog use a list instead of \n, make links clickable in node tags

This commit is contained in:
stopflock
2025-11-16 17:30:24 -06:00
parent 05eedbb910
commit b2645f1341
5 changed files with 112 additions and 21 deletions

View File

@@ -1,29 +1,82 @@
{
"1.3.3": {
"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\""
"content": [
"• NEW: Added builtin surveillance device profiles for Rekor and Axis Communications ALPR cameras",
"• Both profiles include proper OSM tags for manufacturer identification and require direction setting",
"• NEW: Advanced editing options - access iD Editor, RapiD, Vespucci, StreetComplete, and other OSM editors",
"• NEW: 'View on OSM' links to see nodes directly on OpenStreetMap website",
"• UX: Constrained nodes (part of ways/relations) cannot be moved to prevent data corruption",
"• UX: Auto-clickable URLs in all tag values - any URL becomes a tappable link",
"• UX: Tag lists now scroll with max height to keep buttons and map visible",
"• UX: Improved button layout on mobile with two rows for better accessibility",
"• UX: Localized network status messages in all supported languages",
"• FIXED: Duplicate changelog service calls eliminated"
]
},
"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",
"• UX: Fixed Android navigation bar covering settings page content"
]
},
"1.3.1": {
"content": "• UX: Network status indicator always enabled\n• UX: Direction slider wider on small screens\n• UX: Fixed iOS keyboard missing 'Done' in settings\n• UX: Fixed multi-direction nodes in upload queue\n• UX: Improved suspected locations loading indicator; removed popup, fixed stuck spinner"
"content": [
"• UX: Network status indicator always enabled",
"• UX: Direction slider wider on small screens",
"• UX: Fixed iOS keyboard missing 'Done' in settings",
"• UX: Fixed multi-direction nodes in upload queue",
"• UX: Improved suspected locations loading indicator; removed popup, fixed stuck spinner"
]
},
"1.2.8": {
"content": "• UX: Profile selection is now a required step to prevent accidental submission of default profile.\n• NEW: Note in welcome message about not submitting data you cannot vouch for personally (no street view etc)\n• NEW: Added default operator profiles for the most common private operators nationwide (Lowe's, Home Depot, et al)\n• NEW: Support for cardinal directions in OSM data, multiple directions on a node."
"content": [
"• UX: Profile selection is now a required step to prevent accidental submission of default profile",
"• NEW: Note in welcome message about not submitting data you cannot vouch for personally (no street view etc)",
"• NEW: Added default operator profiles for the most common private operators nationwide (Lowe's, Home Depot, et al)",
"• NEW: Support for cardinal directions in OSM data, multiple directions on a node"
]
},
"1.2.7": {
"content": "• NEW: Compass indicator shows map orientation; tap to spin north-up\n• Smart area caching: Loads 3x larger areas and refreshes data every 60 seconds for much faster browsing\n• Enhanced tile loading: Increased retry attempts with faster delays - tiles load much more reliably\n• Better network status: Simplified loading indicator logic\n• Instant node display: Surveillance devices now appear immediately when data finishes loading\n• Node limit alerts: Get notified when some nodes are not drawn"
"content": [
"• NEW: Compass indicator shows map orientation; tap to spin north-up",
"• Smart area caching: Loads 3x larger areas and refreshes data every 60 seconds for much faster browsing",
"• Enhanced tile loading: Increased retry attempts with faster delays - tiles load much more reliably",
"• Better network status: Simplified loading indicator logic",
"• Instant node display: Surveillance devices now appear immediately when data finishes loading",
"• Node limit alerts: Get notified when some nodes are not drawn"
]
},
"1.2.4": {
"content": "• New welcome popup for first-time users with essential privacy information\n• Automatic changelog display when app updates (like this one!)\n• Added Release Notes viewer in Settings > About\n• Enhanced user onboarding and transparency about data handling\n• Improved documentation for contributors"
"content": [
"• New welcome popup for first-time users with essential privacy information",
"• Automatic changelog display when app updates (like this one!)",
"• Added Release Notes viewer in Settings > About",
"• Enhanced user onboarding and transparency about data handling",
"• Improved documentation for contributors"
]
},
"1.2.3": {
"content": "• Enhanced map performance and stability\n• Improved offline sync reliability\n• Added better error handling for uploads\n• Various bug fixes and improvements"
"content": [
"• Enhanced map performance and stability",
"• Improved offline sync reliability",
"• Added better error handling for uploads",
"• Various bug fixes and improvements"
]
},
"1.2.2": {
"content": "• New surveillance device profiles added\n• Improved tile loading performance\n• Fixed issue with GPS accuracy\n• Updated translations"
"content": [
"• New surveillance device profiles added",
"• Improved tile loading performance",
"• Fixed issue with GPS accuracy",
"• Updated translations"
]
},
"1.2.0": {
"content": "• Major UI improvements\n• Added proximity alerts\n• Enhanced offline capabilities\n• New suspected locations feature"
"content": [
"• Major UI improvements",
"• Added proximity alerts",
"• Enhanced offline capabilities",
"• New suspected locations feature"
]
}
}

View File

@@ -17,6 +17,22 @@ class ChangelogService {
Map<String, dynamic>? _changelogData;
bool _initialized = false;
/// Parse changelog content from either string or array format
String? _parseChangelogContent(dynamic content) {
if (content == null) return null;
if (content is String) {
// Legacy format: single string with \n
return content.isEmpty ? null : content;
} else if (content is List) {
// New format: array of strings
final lines = content.whereType<String>().where((line) => line.isNotEmpty).toList();
return lines.isEmpty ? null : lines.join('\n');
}
return null;
}
/// Initialize the service by loading changelog data
Future<void> init() async {
if (_initialized) return;
@@ -89,8 +105,7 @@ class ChangelogService {
return null;
}
final content = versionData['content'] as String?;
return (content?.isEmpty == true) ? null : content;
return _parseChangelogContent(versionData['content']);
}
/// Get the changelog content that should be displayed (may be combined from multiple versions)
@@ -112,8 +127,7 @@ class ChangelogService {
final versionData = _changelogData![version] as Map<String, dynamic>?;
if (versionData == null) return null;
final content = versionData['content'] as String?;
return (content?.isEmpty == true) ? null : content;
return _parseChangelogContent(versionData['content']);
}
/// Get all changelog entries (for settings page)
@@ -125,7 +139,7 @@ class ChangelogService {
for (final entry in _changelogData!.entries) {
final version = entry.key;
final versionData = entry.value as Map<String, dynamic>?;
final content = versionData?['content'] as String?;
final content = _parseChangelogContent(versionData?['content']);
// Only include versions with non-empty content
if (content != null && content.isNotEmpty) {
@@ -203,7 +217,7 @@ class ChangelogService {
for (final entry in _changelogData!.entries) {
final version = entry.key;
final versionData = entry.value as Map<String, dynamic>?;
final content = versionData?['content'] as String?;
final content = _parseChangelogContent(versionData?['content']);
// Skip versions with empty content
if (content == null || content.isEmpty) continue;
@@ -220,7 +234,7 @@ class ChangelogService {
// Build changelog content
final intermediateChangelogs = intermediateVersions.map((version) {
final versionData = _changelogData![version] as Map<String, dynamic>;
final content = versionData['content'] as String;
final content = _parseChangelogContent(versionData['content'])!; // Safe to use ! here since we filtered empty content above
return '**Version $version:**\n$content';
}).toList();

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import '../models/osm_node.dart';
import '../app_state.dart';
import '../services/localization_service.dart';
@@ -127,12 +128,26 @@ class NodeTagSheet extends StatelessWidget {
),
const SizedBox(width: 8),
Expanded(
child: Text(
e.value,
child: Linkify(
onOpen: (link) async {
final uri = Uri.parse(link.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: ${link.url}')),
);
}
},
text: e.value,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
),
softWrap: true,
linkStyle: TextStyle(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
options: const LinkifyOptions(humanize: false),
),
),
],

View File

@@ -44,8 +44,16 @@ class SuspectedLocationSheet extends StatelessWidget {
),
const SizedBox(height: 12),
// Display all fields
...displayData.entries.map(
// Constrain field list height to keep buttons visible
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.4, // Max 40% of screen height
),
child: SingleChildScrollView(
child: Column(
children: [
// Display all fields
...displayData.entries.map(
(e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(

View File

@@ -21,6 +21,7 @@ dependencies:
xml: ^6.4.2
flutter_local_notifications: ^17.2.2
url_launcher: ^6.3.0
flutter_linkify: ^6.0.0
# Auth, storage, prefs
oauth2_client: ^4.2.0