mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 00:40:28 +00:00
Refactor SAR marker handling and add template management
- Updated CompassSarList and DetailedCompassDialog to use marker.displayName instead of marker.type.displayName. - Enhanced DrawingLayer and DrawingMarkersLayer to support simple mode for drawing visibility and interaction. - Added toggle switches in DrawingToolbar for showing/hiding received drawings and SAR markers. - Modified MapMarkers to utilize custom emojis and display names for markers. - Introduced RecipientSelectorSheet for selecting message recipients with search functionality. - Refactored SarUpdateSheet to use SAR templates instead of marker types, allowing for emoji and name customization. - Created SarTemplateEditDialog for adding and editing SAR templates with color selection and preview.
This commit is contained in:
344
lib/widgets/messages/recipient_selector_sheet.dart
Normal file
344
lib/widgets/messages/recipient_selector_sheet.dart
Normal file
@@ -0,0 +1,344 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
/// Bottom sheet for selecting message recipient (channel, contact, or room)
|
||||
class RecipientSelectorSheet extends StatefulWidget {
|
||||
final List<Contact> contacts;
|
||||
final List<Contact> rooms;
|
||||
final String? currentDestinationType;
|
||||
final String? currentRecipientPublicKey;
|
||||
final Function(String type, Contact? recipient) onSelect;
|
||||
|
||||
const RecipientSelectorSheet({
|
||||
super.key,
|
||||
required this.contacts,
|
||||
required this.rooms,
|
||||
this.currentDestinationType,
|
||||
this.currentRecipientPublicKey,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecipientSelectorSheet> createState() => _RecipientSelectorSheetState();
|
||||
}
|
||||
|
||||
class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
String _searchQuery = '';
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<Contact> _filterContacts(List<Contact> contacts) {
|
||||
if (_searchQuery.isEmpty) return contacts;
|
||||
final query = _searchQuery.toLowerCase();
|
||||
return contacts.where((contact) {
|
||||
final name = contact.displayName?.toLowerCase() ?? contact.advName.toLowerCase();
|
||||
return name.contains(query);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
bool _isSelected(String type, Contact? contact) {
|
||||
if (widget.currentDestinationType != type) return false;
|
||||
if (type == 'channel') return true;
|
||||
if (contact == null) return false;
|
||||
return contact.publicKeyHex == widget.currentRecipientPublicKey;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final filteredContacts = _filterContacts(widget.contacts);
|
||||
final filteredRooms = _filterContacts(widget.rooms);
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.selectRecipient,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
tooltip: l10n.close,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Search field
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.searchRecipients,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() {
|
||||
_searchQuery = '';
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Recipients list
|
||||
Flexible(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
// Public Channel option
|
||||
_buildRecipientTile(
|
||||
context: context,
|
||||
icon: Icons.public,
|
||||
title: l10n.publicChannel,
|
||||
subtitle: l10n.broadcastToAllNearby,
|
||||
isSelected: _isSelected('channel', null),
|
||||
onTap: () {
|
||||
widget.onSelect('channel', null);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
|
||||
const Divider(),
|
||||
|
||||
// Contacts section
|
||||
if (widget.contacts.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
l10n.contacts,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (filteredContacts.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
l10n.noContactsFound,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).disabledColor,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
else
|
||||
...filteredContacts.map((contact) {
|
||||
return _buildRecipientTile(
|
||||
context: context,
|
||||
icon: Icons.person,
|
||||
title: contact.displayName ?? contact.advName,
|
||||
subtitle: contact.publicKeyShort,
|
||||
emoji: contact.roleEmoji,
|
||||
isSelected: _isSelected('contact', contact),
|
||||
onTap: () {
|
||||
widget.onSelect('contact', contact);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
||||
const Divider(),
|
||||
|
||||
// Rooms section
|
||||
if (widget.rooms.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
l10n.rooms,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (filteredRooms.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
l10n.noRoomsFound,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).disabledColor,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
else
|
||||
...filteredRooms.map((room) {
|
||||
return _buildRecipientTile(
|
||||
context: context,
|
||||
icon: Icons.meeting_room,
|
||||
title: room.displayName ?? room.advName,
|
||||
subtitle: room.publicKeyShort,
|
||||
emoji: room.roleEmoji,
|
||||
isSelected: _isSelected('room', room),
|
||||
onTap: () {
|
||||
widget.onSelect('room', room);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
||||
// Empty state
|
||||
if (widget.contacts.isEmpty && widget.rooms.isEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.people_outline,
|
||||
size: 64,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.noContactsOrRoomsAvailable,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.messagesWillBeSentToPublicChannel,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecipientTile({
|
||||
required BuildContext context,
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
String? emoji,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
if (emoji != null && emoji.isNotEmpty) ...[
|
||||
Text(emoji, style: const TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
).copyWith(
|
||||
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
trailing: isSelected
|
||||
? Icon(
|
||||
Icons.check_circle,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: null,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,15 @@ import 'package:provider/provider.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import '../../providers/contacts_provider.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../../models/sar_marker.dart';
|
||||
import '../../models/sar_template.dart';
|
||||
import '../../services/validation_service.dart';
|
||||
import '../../services/sar_template_service.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
/// SAR Update Sheet - Modal bottom sheet for creating and sending SAR markers
|
||||
/// This widget is public so it can be used from both messages_tab.dart and map_tab.dart
|
||||
class SarUpdateSheet extends StatefulWidget {
|
||||
final Future<void> Function(SarMarkerType, Position, String?, Uint8List?, bool) onSend;
|
||||
final Future<void> Function(String emoji, String name, Position, Uint8List?, bool) onSend;
|
||||
final Position? prePopulatedPosition;
|
||||
final bool allowLocationUpdate;
|
||||
|
||||
@@ -27,7 +28,9 @@ class SarUpdateSheet extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
SarMarkerType _selectedType = SarMarkerType.foundPerson;
|
||||
SarTemplate? _selectedTemplate;
|
||||
List<SarTemplate> _templates = [];
|
||||
final SarTemplateService _templateService = SarTemplateService();
|
||||
Position? _currentPosition;
|
||||
bool _loadingLocation = false;
|
||||
String? _locationError;
|
||||
@@ -37,6 +40,7 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeTemplates();
|
||||
// Use pre-populated position if provided, otherwise get current location
|
||||
if (widget.prePopulatedPosition != null) {
|
||||
_currentPosition = widget.prePopulatedPosition;
|
||||
@@ -46,6 +50,21 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
_setDefaultDestination();
|
||||
}
|
||||
|
||||
Future<void> _initializeTemplates() async {
|
||||
if (!_templateService.isInitialized) {
|
||||
await _templateService.initialize();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_templates = _templateService.templates;
|
||||
// Select first template by default
|
||||
if (_templates.isNotEmpty) {
|
||||
_selectedTemplate = _templates.first;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _setDefaultDestination() {
|
||||
// Set default to first room, or first channel if no rooms exist
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -140,19 +159,23 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
Widget build(BuildContext context) {
|
||||
// Get keyboard height to adjust padding
|
||||
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
final bottomSafeArea = MediaQuery.of(context).padding.bottom;
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.9,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
return AnimatedPadding(
|
||||
padding: EdgeInsets.only(bottom: keyboardHeight),
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.9,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
@@ -196,7 +219,9 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16,
|
||||
bottom: keyboardHeight > 0 ? keyboardHeight + 16 : 16,
|
||||
// Add bottom padding for button area (button + padding + safe area)
|
||||
// Button height ~48px + container padding 32px + safe area
|
||||
bottom: 80 + bottomSafeArea,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -211,30 +236,17 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
MarkerTypeChip(
|
||||
type: SarMarkerType.foundPerson,
|
||||
isSelected: _selectedType == SarMarkerType.foundPerson,
|
||||
onTap: () => setState(() => _selectedType = SarMarkerType.foundPerson),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
MarkerTypeChip(
|
||||
type: SarMarkerType.fire,
|
||||
isSelected: _selectedType == SarMarkerType.fire,
|
||||
onTap: () => setState(() => _selectedType = SarMarkerType.fire),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
MarkerTypeChip(
|
||||
type: SarMarkerType.stagingArea,
|
||||
isSelected: _selectedType == SarMarkerType.stagingArea,
|
||||
onTap: () => setState(() => _selectedType = SarMarkerType.stagingArea),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
MarkerTypeChip(
|
||||
type: SarMarkerType.object,
|
||||
isSelected: _selectedType == SarMarkerType.object,
|
||||
onTap: () => setState(() => _selectedType = SarMarkerType.object),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
..._templates.map((template) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: TemplateChip(
|
||||
template: template,
|
||||
isSelected: _selectedTemplate?.id == template.id,
|
||||
onTap: () => setState(() => _selectedTemplate = template),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Destination selection (compact dropdown with rooms and channel)
|
||||
Text(
|
||||
@@ -595,35 +607,28 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
// Bottom action button
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _currentPosition == null || _selectedContact == null
|
||||
onPressed: _currentPosition == null || _selectedContact == null || _selectedTemplate == null
|
||||
? null
|
||||
: () async {
|
||||
final validator = ValidationService();
|
||||
|
||||
// Validate coordinates
|
||||
final coordResult = validator.validateCoordinates(
|
||||
_currentPosition!.latitude,
|
||||
_currentPosition!.longitude,
|
||||
);
|
||||
if (!coordResult.isValid) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(coordResult.errorMessage!),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final notes = _notesController.text.trim();
|
||||
|
||||
// Validate notes length if provided
|
||||
final notes = _notesController.text.trim();
|
||||
if (notes.isNotEmpty) {
|
||||
final notesResult = validator.validateName(
|
||||
notes,
|
||||
@@ -642,6 +647,23 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate coordinates
|
||||
final coordResult = validator.validateCoordinates(
|
||||
_currentPosition!.latitude,
|
||||
_currentPosition!.longitude,
|
||||
);
|
||||
if (!coordResult.isValid) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(coordResult.errorMessage!),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate location accuracy (warn if >50m)
|
||||
if (_currentPosition!.accuracy != null &&
|
||||
_currentPosition!.accuracy! > 50.0) {
|
||||
@@ -669,10 +691,21 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
if (shouldContinue != true) return;
|
||||
}
|
||||
|
||||
// Combine template name with optional notes
|
||||
String displayText;
|
||||
if (notes.isNotEmpty) {
|
||||
// Include both template name and custom notes
|
||||
displayText = '${_selectedTemplate!.name} - $notes';
|
||||
} else {
|
||||
// Just the template name
|
||||
displayText = _selectedTemplate!.name;
|
||||
}
|
||||
|
||||
// Send SAR marker with emoji and display text
|
||||
await widget.onSend(
|
||||
_selectedType,
|
||||
_selectedTemplate!.emoji,
|
||||
displayText,
|
||||
_currentPosition!,
|
||||
notes.isEmpty ? null : notes,
|
||||
_selectedContact!.isChannel
|
||||
? null
|
||||
: _selectedContact!.publicKey,
|
||||
@@ -701,43 +734,29 @@ class _SarUpdateSheetState extends State<SarUpdateSheet> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker Type Chip widget - Displays a selectable SAR marker type
|
||||
class MarkerTypeChip extends StatelessWidget {
|
||||
final SarMarkerType type;
|
||||
/// Template Chip widget - Displays a selectable SAR template
|
||||
class TemplateChip extends StatelessWidget {
|
||||
final SarTemplate template;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const MarkerTypeChip({
|
||||
const TemplateChip({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.template,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
Color _getMarkerColor() {
|
||||
switch (type) {
|
||||
case SarMarkerType.foundPerson:
|
||||
return Colors.green;
|
||||
case SarMarkerType.fire:
|
||||
return Colors.red;
|
||||
case SarMarkerType.stagingArea:
|
||||
return Colors.orange;
|
||||
case SarMarkerType.object:
|
||||
return Colors.purple;
|
||||
case SarMarkerType.unknown:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = _getMarkerColor();
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final color = template.color;
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
@@ -758,18 +777,35 @@ class MarkerTypeChip extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
type.emoji,
|
||||
template.emoji,
|
||||
style: const TextStyle(fontSize: 32),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
type.getLocalizedName(context),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isSelected ? color : colorScheme.onSurface,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
template.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected ? color : colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (template.description.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
template.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
|
||||
Reference in New Issue
Block a user