mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Update project version to 19, add repeaters filter and theme support, and enhance contact list display
This commit is contained in:
@@ -489,7 +489,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -511,7 +511,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
|
||||
@@ -529,7 +529,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
|
||||
@@ -545,7 +545,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
|
||||
@@ -676,7 +676,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -699,7 +699,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 18;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>18</string>
|
||||
<string>19</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
||||
@@ -5,17 +5,22 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000203">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000227">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.287931">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.240778">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="31.633362">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="91.431808">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="328.922766">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1097,6 +1097,11 @@
|
||||
"description": "Filter option for contacts"
|
||||
},
|
||||
|
||||
"repeatersFilter": "Repeaters",
|
||||
"@repeatersFilter": {
|
||||
"description": "Filter option for repeaters"
|
||||
},
|
||||
|
||||
"sarMarkers": "SAR Markers",
|
||||
"@sarMarkers": {
|
||||
"description": "SAR markers section header"
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
|
||||
"contactsFilter": "Kontakti",
|
||||
|
||||
"repeatersFilter": "Repetitori",
|
||||
|
||||
"sarMarkers": "SAR markeri",
|
||||
|
||||
"foundPerson": "Pronađena osoba",
|
||||
|
||||
@@ -1215,6 +1215,12 @@ abstract class AppLocalizations {
|
||||
/// **'Contacts'**
|
||||
String get contactsFilter;
|
||||
|
||||
/// Filter option for repeaters
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Repeaters'**
|
||||
String get repeatersFilter;
|
||||
|
||||
/// SAR markers section header
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -643,6 +643,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get contactsFilter => 'Contacts';
|
||||
|
||||
@override
|
||||
String get repeatersFilter => 'Repeaters';
|
||||
|
||||
@override
|
||||
String get sarMarkers => 'SAR Markers';
|
||||
|
||||
|
||||
@@ -643,6 +643,9 @@ class AppLocalizationsHr extends AppLocalizations {
|
||||
@override
|
||||
String get contactsFilter => 'Kontakti';
|
||||
|
||||
@override
|
||||
String get repeatersFilter => 'Repetitori';
|
||||
|
||||
@override
|
||||
String get sarMarkers => 'SAR markeri';
|
||||
|
||||
|
||||
@@ -643,6 +643,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get contactsFilter => 'Stiki';
|
||||
|
||||
@override
|
||||
String get repeatersFilter => 'Ponavljalniki';
|
||||
|
||||
@override
|
||||
String get sarMarkers => 'SAR označevalci';
|
||||
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
|
||||
"contactsFilter": "Stiki",
|
||||
|
||||
"repeatersFilter": "Ponavljalniki",
|
||||
|
||||
"sarMarkers": "SAR označevalci",
|
||||
|
||||
"foundPerson": "Najdena oseba",
|
||||
|
||||
@@ -772,6 +772,30 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
RadioListTile<AppThemeMode>(
|
||||
title: Row(
|
||||
children: [
|
||||
const Text('SAR Navy Blue'),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF5C9FFF),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black26),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: const Text('Professional/Operations Mode'),
|
||||
value: AppThemeMode.sarNavyBlue,
|
||||
groupValue: _selectedTheme,
|
||||
onChanged: (value) {
|
||||
_handleThemeChange(value);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
RadioListTile<AppThemeMode>(
|
||||
title: Text(AppLocalizations.of(context)!.autoSystem),
|
||||
|
||||
@@ -5,6 +5,7 @@ enum AppThemeMode {
|
||||
dark,
|
||||
sarRed,
|
||||
sarGreen,
|
||||
sarNavyBlue,
|
||||
system,
|
||||
}
|
||||
|
||||
@@ -181,6 +182,65 @@ class AppTheme {
|
||||
);
|
||||
}
|
||||
|
||||
// SAR Navy Blue theme (Professional/Operations tones)
|
||||
static ThemeData get sarNavyBlueTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
brightness: Brightness.dark,
|
||||
primary: Color(0xFF5C9FFF), // Bright navy blue
|
||||
onPrimary: Color(0xFF000000),
|
||||
primaryContainer: Color(0xFF003366), // Dark navy
|
||||
onPrimaryContainer: Color(0xFFBBDEFF),
|
||||
secondary: Color(0xFF80B3FF),
|
||||
onSecondary: Color(0xFF000000),
|
||||
secondaryContainer: Color(0xFF002244),
|
||||
onSecondaryContainer: Color(0xFFBBDEFF),
|
||||
tertiary: Color(0xFF4DB8FF),
|
||||
onTertiary: Color(0xFF000000),
|
||||
error: Color(0xFFCF6679),
|
||||
onError: Color(0xFF000000),
|
||||
surface: Color(0xFF00111C), // Very dark blue-tinted
|
||||
onSurface: Color(0xFFE3F2FD),
|
||||
surfaceContainerHighest: Color(0xFF001A2D),
|
||||
onSurfaceVariant: Color(0xFFBBDEFF),
|
||||
outline: Color(0xFF5C9FFF),
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF00111C),
|
||||
appBarTheme: const AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
backgroundColor: Color(0xFF001A2D),
|
||||
foregroundColor: Color(0xFFE3F2FD),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: 2,
|
||||
color: const Color(0xFF001A2D),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: const BorderSide(
|
||||
color: Color(0xFF5C9FFF),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFF5C9FFF)),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF001A2D),
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF5C9FFF),
|
||||
foregroundColor: const Color(0xFF000000),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Get theme by mode
|
||||
static ThemeData getTheme(AppThemeMode mode, Brightness systemBrightness) {
|
||||
switch (mode) {
|
||||
@@ -192,6 +252,8 @@ class AppTheme {
|
||||
return sarRedTheme;
|
||||
case AppThemeMode.sarGreen:
|
||||
return sarGreenTheme;
|
||||
case AppThemeMode.sarNavyBlue:
|
||||
return sarNavyBlueTheme;
|
||||
case AppThemeMode.system:
|
||||
return systemBrightness == Brightness.dark ? darkTheme : lightTheme;
|
||||
}
|
||||
@@ -208,6 +270,8 @@ class AppTheme {
|
||||
return 'SAR Red (Alert)';
|
||||
case AppThemeMode.sarGreen:
|
||||
return 'SAR Green (Safe)';
|
||||
case AppThemeMode.sarNavyBlue:
|
||||
return 'SAR Navy Blue (Ops)';
|
||||
case AppThemeMode.system:
|
||||
return 'Auto (System)';
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ import '../../../models/contact.dart';
|
||||
|
||||
/// Contact list section for the compass dialog.
|
||||
/// Shows all contacts with location sorted by distance with bearing information.
|
||||
/// Splits contacts by type: Persons/Team, Repeaters, and Rooms.
|
||||
class CompassContactList extends StatelessWidget {
|
||||
final List<Contact> contacts;
|
||||
final Position? position;
|
||||
final double? heading;
|
||||
final Contact? selectedContact;
|
||||
final bool showContacts;
|
||||
final bool showRepeaters;
|
||||
final ValueChanged<Contact?> onContactTap;
|
||||
|
||||
const CompassContactList({
|
||||
@@ -19,6 +22,8 @@ class CompassContactList extends StatelessWidget {
|
||||
required this.position,
|
||||
this.heading,
|
||||
required this.selectedContact,
|
||||
required this.showContacts,
|
||||
required this.showRepeaters,
|
||||
required this.onContactTap,
|
||||
});
|
||||
|
||||
@@ -32,9 +37,16 @@ class CompassContactList extends StatelessWidget {
|
||||
return Text(AppLocalizations.of(context)!.locationUnavailable);
|
||||
}
|
||||
|
||||
// Calculate bearings and distances
|
||||
final contactsWithBearing = contacts.map((contact) {
|
||||
if (contact.displayLocation == null) return null;
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
// Split contacts by type
|
||||
final persons = <Map<String, dynamic>>[];
|
||||
final repeaters = <Map<String, dynamic>>[];
|
||||
final rooms = <Map<String, dynamic>>[];
|
||||
|
||||
// Calculate bearings and distances for each contact
|
||||
for (final contact in contacts) {
|
||||
if (contact.displayLocation == null) continue;
|
||||
|
||||
final bearing = _calculateBearing(
|
||||
position!.latitude,
|
||||
@@ -50,101 +62,160 @@ class CompassContactList extends StatelessWidget {
|
||||
contact.displayLocation!.longitude,
|
||||
);
|
||||
|
||||
return {
|
||||
final item = {
|
||||
'contact': contact,
|
||||
'bearing': bearing,
|
||||
'distance': distance,
|
||||
};
|
||||
}).whereType<Map<String, dynamic>>().toList();
|
||||
|
||||
// Sort by distance
|
||||
contactsWithBearing.sort((a, b) =>
|
||||
(a['distance'] as double).compareTo(b['distance'] as double));
|
||||
if (contact.isRepeater) {
|
||||
repeaters.add(item);
|
||||
} else if (contact.isRoom) {
|
||||
rooms.add(item);
|
||||
} else {
|
||||
persons.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort each list by distance
|
||||
persons.sort((a, b) => (a['distance'] as double).compareTo(b['distance'] as double));
|
||||
repeaters.sort((a, b) => (a['distance'] as double).compareTo(b['distance'] as double));
|
||||
rooms.sort((a, b) => (a['distance'] as double).compareTo(b['distance'] as double));
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8),
|
||||
child: Text(
|
||||
l10n.nearbyContacts,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
...contactsWithBearing.map((item) {
|
||||
final contact = item['contact'] as Contact;
|
||||
final bearing = item['bearing'] as double;
|
||||
final distance = item['distance'] as double;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: selectedContact == contact
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: selectedContact == contact
|
||||
? Border.all(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
leading: contact.roleEmoji != null
|
||||
? Text(
|
||||
contact.roleEmoji!,
|
||||
style: const TextStyle(fontSize: 24),
|
||||
)
|
||||
: Icon(
|
||||
Icons.person,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 24,
|
||||
),
|
||||
title: Text(contact.displayName),
|
||||
subtitle: Text(
|
||||
'${_bearingToCardinal(bearing)} • ${_formatDistance(distance)}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
trailing: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${bearing.round()}°',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
// Persons/Team section
|
||||
if (showContacts && persons.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 4),
|
||||
child: Text(
|
||||
l10n.teamMembers,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
if (heading != null)
|
||||
Text(
|
||||
_formatRelativeBearing(bearing, heading!, context),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (selectedContact == contact) {
|
||||
// Deselect if already selected
|
||||
onContactTap(null);
|
||||
} else {
|
||||
// Select this contact
|
||||
onContactTap(contact);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
...persons.map((item) => _buildContactTile(
|
||||
context,
|
||||
item,
|
||||
Icons.groups,
|
||||
Theme.of(context).colorScheme.primary,
|
||||
)),
|
||||
],
|
||||
// Repeaters section
|
||||
if (showRepeaters && repeaters.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 12),
|
||||
child: Text(
|
||||
l10n.repeaters,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
...repeaters.map((item) => _buildContactTile(
|
||||
context,
|
||||
item,
|
||||
Icons.router,
|
||||
Colors.purple,
|
||||
)),
|
||||
],
|
||||
// Rooms section
|
||||
if (rooms.isNotEmpty) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, bottom: 8, top: 12),
|
||||
child: Text(
|
||||
l10n.rooms,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
...rooms.map((item) => _buildContactTile(
|
||||
context,
|
||||
item,
|
||||
Icons.meeting_room,
|
||||
Colors.teal,
|
||||
)),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContactTile(
|
||||
BuildContext context,
|
||||
Map<String, dynamic> item,
|
||||
IconData defaultIcon,
|
||||
Color iconColor,
|
||||
) {
|
||||
final contact = item['contact'] as Contact;
|
||||
final bearing = item['bearing'] as double;
|
||||
final distance = item['distance'] as double;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: selectedContact == contact
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: selectedContact == contact
|
||||
? Border.all(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
leading: contact.roleEmoji != null
|
||||
? Text(
|
||||
contact.roleEmoji!,
|
||||
style: const TextStyle(fontSize: 24),
|
||||
)
|
||||
: Icon(
|
||||
defaultIcon,
|
||||
color: iconColor,
|
||||
size: 24,
|
||||
),
|
||||
title: Text(contact.displayName),
|
||||
subtitle: Text(
|
||||
'${_bearingToCardinal(bearing)} • ${_formatDistance(distance)}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
trailing: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${bearing.round()}°',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (heading != null)
|
||||
Text(
|
||||
_formatRelativeBearing(bearing, heading!, context),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (selectedContact == contact) {
|
||||
// Deselect if already selected
|
||||
onContactTap(null);
|
||||
} else {
|
||||
// Select this contact
|
||||
onContactTap(contact);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate bearing between two points (in degrees)
|
||||
double _calculateBearing(
|
||||
double lat1, double lon1, double lat2, double lon2) {
|
||||
|
||||
@@ -5,10 +5,12 @@ import '../../../l10n/app_localizations.dart';
|
||||
/// Allows filtering of contacts and SAR marker types.
|
||||
class CompassFilters extends StatefulWidget {
|
||||
final bool showContacts;
|
||||
final bool showRepeaters;
|
||||
final bool showFoundPerson;
|
||||
final bool showFire;
|
||||
final bool showStagingArea;
|
||||
final ValueChanged<bool> onShowContactsChanged;
|
||||
final ValueChanged<bool> onShowRepeatersChanged;
|
||||
final ValueChanged<bool> onShowFoundPersonChanged;
|
||||
final ValueChanged<bool> onShowFireChanged;
|
||||
final ValueChanged<bool> onShowStagingAreaChanged;
|
||||
@@ -17,10 +19,12 @@ class CompassFilters extends StatefulWidget {
|
||||
const CompassFilters({
|
||||
super.key,
|
||||
required this.showContacts,
|
||||
required this.showRepeaters,
|
||||
required this.showFoundPerson,
|
||||
required this.showFire,
|
||||
required this.showStagingArea,
|
||||
required this.onShowContactsChanged,
|
||||
required this.onShowRepeatersChanged,
|
||||
required this.onShowFoundPersonChanged,
|
||||
required this.onShowFireChanged,
|
||||
required this.onShowStagingAreaChanged,
|
||||
@@ -62,6 +66,18 @@ class _CompassFiltersState extends State<CompassFilters> {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Repeaters filter
|
||||
_CompactFilterItem(
|
||||
icon: Icons.router,
|
||||
color: Colors.purple,
|
||||
label: l10n.repeatersFilter,
|
||||
value: widget.showRepeaters,
|
||||
onChanged: (value) {
|
||||
widget.onShowRepeatersChanged(value);
|
||||
setDialogState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Divider(height: 8),
|
||||
const SizedBox(height: 4),
|
||||
// SAR Markers section
|
||||
|
||||
@@ -65,6 +65,7 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
|
||||
|
||||
// Visibility toggles
|
||||
bool _showContacts = true;
|
||||
bool _showRepeaters = false; // Hide repeaters by default
|
||||
bool _showFoundPerson = true;
|
||||
bool _showFire = true;
|
||||
bool _showStagingArea = true;
|
||||
@@ -308,6 +309,7 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
|
||||
),
|
||||
CompassFilters(
|
||||
showContacts: _showContacts,
|
||||
showRepeaters: _showRepeaters,
|
||||
showFoundPerson: _showFoundPerson,
|
||||
showFire: _showFire,
|
||||
showStagingArea: _showStagingArea,
|
||||
@@ -316,6 +318,11 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
|
||||
_showContacts = value;
|
||||
});
|
||||
},
|
||||
onShowRepeatersChanged: (value) {
|
||||
setState(() {
|
||||
_showRepeaters = value;
|
||||
});
|
||||
},
|
||||
onShowFoundPersonChanged: (value) {
|
||||
setState(() {
|
||||
_showFoundPerson = value;
|
||||
@@ -334,6 +341,7 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
|
||||
onShowAll: () {
|
||||
setState(() {
|
||||
_showContacts = true;
|
||||
_showRepeaters = true;
|
||||
_showFoundPerson = true;
|
||||
_showFire = true;
|
||||
_showStagingArea = true;
|
||||
@@ -363,7 +371,10 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
|
||||
? [_selectedContact!]
|
||||
: (_selectedSarMarker != null
|
||||
? []
|
||||
: (_showContacts ? widget.contacts : [])),
|
||||
: widget.contacts.where((c) =>
|
||||
(_showContacts && !c.isRepeater && !c.isRoom) ||
|
||||
(_showRepeaters && c.isRepeater)
|
||||
).toList()),
|
||||
sarMarkers: _selectedSarMarker != null
|
||||
? [_selectedSarMarker!]
|
||||
: (_selectedContact != null
|
||||
@@ -381,12 +392,14 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
|
||||
_buildSelectedItemDetail(context, heading, position),
|
||||
const SizedBox(height: 12),
|
||||
// Contacts list
|
||||
if (_showContacts && widget.contacts.isNotEmpty)
|
||||
if (widget.contacts.isNotEmpty)
|
||||
CompassContactList(
|
||||
contacts: widget.contacts,
|
||||
position: position,
|
||||
heading: heading,
|
||||
selectedContact: _selectedContact,
|
||||
showContacts: _showContacts,
|
||||
showRepeaters: _showRepeaters,
|
||||
onContactTap: (contact) {
|
||||
setState(() {
|
||||
_selectedContact = contact;
|
||||
|
||||
Reference in New Issue
Block a user