mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
fix: Simplify device settings styling #123
This commit is contained in:
@@ -934,39 +934,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
context,
|
||||
ContactSection.repeaters,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (connectionProvider.deviceInfo.isConnected)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.radar, size: 20),
|
||||
tooltip: 'Discover repeaters',
|
||||
visualDensity: VisualDensity.compact,
|
||||
style: IconButton.styleFrom(
|
||||
foregroundColor: _sectionAccentColor(
|
||||
context,
|
||||
ContactSection.repeaters,
|
||||
),
|
||||
backgroundColor: _sectionAccentColor(
|
||||
context,
|
||||
ContactSection.repeaters,
|
||||
).withValues(alpha: 0.10),
|
||||
),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<ConnectionProvider>()
|
||||
.discoverNodeType(advertType: 2);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
l10n.repeaterDiscoverySent,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildSectionFilterField(
|
||||
context,
|
||||
@@ -1029,37 +996,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
||||
context,
|
||||
ContactSection.sensors,
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (connectionProvider.deviceInfo.isConnected)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.radar, size: 20),
|
||||
tooltip: 'Discover sensors',
|
||||
visualDensity: VisualDensity.compact,
|
||||
style: IconButton.styleFrom(
|
||||
foregroundColor: _sectionAccentColor(
|
||||
context,
|
||||
ContactSection.sensors,
|
||||
),
|
||||
backgroundColor: _sectionAccentColor(
|
||||
context,
|
||||
ContactSection.sensors,
|
||||
).withValues(alpha: 0.10),
|
||||
),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<ConnectionProvider>()
|
||||
.discoverNodeType(advertType: 4);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.sensorDiscoverySent),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildSectionFilterField(
|
||||
context,
|
||||
@@ -1718,15 +1654,7 @@ class _SectionCard extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.surface,
|
||||
accentColor.withValues(alpha: 0.04),
|
||||
colorScheme.surfaceContainerLow,
|
||||
],
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
||||
boxShadow: [
|
||||
@@ -1903,14 +1831,7 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.surfaceContainerLow,
|
||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
||||
],
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||
),
|
||||
|
||||
@@ -729,9 +729,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _gpsLoading = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to set GPS mode: $e')),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Failed to set GPS mode: $e')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,7 +893,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
.toList();
|
||||
if (contacts.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.noDeviceContactsToClear)),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.noDeviceContactsToClear),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -988,7 +990,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
.toList();
|
||||
if (channels.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.noCustomChannelsToClear)),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.noCustomChannelsToClear),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1095,19 +1099,8 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colorScheme.primaryContainer.withValues(alpha: 0.55),
|
||||
colorScheme.surface,
|
||||
colorScheme.surface,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [0.0, 0.22, 1.0],
|
||||
),
|
||||
),
|
||||
body: ColoredBox(
|
||||
color: colorScheme.surface,
|
||||
child: SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
@@ -1146,7 +1139,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
SizedBox(height: 20),
|
||||
_ConfigSectionCard(
|
||||
title: AppLocalizations.of(context)!.storage,
|
||||
subtitle: AppLocalizations.of(context)!.availableSpaceOnThisDevice,
|
||||
subtitle: AppLocalizations.of(
|
||||
context,
|
||||
)!.availableSpaceOnThisDevice,
|
||||
icon: Icons.storage_rounded,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1190,7 +1185,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
icon: _autoAddDiscoveredContactsEnabled
|
||||
? Icons.person_add_alt_1
|
||||
: Icons.person_add_disabled,
|
||||
title: AppLocalizations.of(context)!.enableAutomaticAdding,
|
||||
title: AppLocalizations.of(
|
||||
context,
|
||||
)!.enableAutomaticAdding,
|
||||
description:
|
||||
'Turn this off to keep discoveries manual-only on the radio.',
|
||||
accentColor: _autoAddDiscoveredContactsEnabled
|
||||
@@ -1293,7 +1290,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
SizedBox(height: 14),
|
||||
_SettingHighlightCard(
|
||||
icon: Icons.history_toggle_off_rounded,
|
||||
title: AppLocalizations.of(context)!.overwriteOldestWhenFull,
|
||||
title: AppLocalizations.of(
|
||||
context,
|
||||
)!.overwriteOldestWhenFull,
|
||||
description:
|
||||
'Allow the radio to replace the oldest contact when storage is full.',
|
||||
accentColor: _overwriteOldestAutoAddEnabled
|
||||
@@ -1329,7 +1328,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
: _saveAutoDiscoverySettings,
|
||||
isSaving: _isSavingAutoDiscoverySettings,
|
||||
isSaved: _autoDiscoverySettingsSaved,
|
||||
label: AppLocalizations.of(context)!.saveDiscoverySettings,
|
||||
label: AppLocalizations.of(
|
||||
context,
|
||||
)!.saveDiscoverySettings,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1338,7 +1339,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
SizedBox(height: 20),
|
||||
_ConfigSectionCard(
|
||||
title: AppLocalizations.of(context)!.publicInfo,
|
||||
subtitle: AppLocalizations.of(context)!.chooseTheNameAndLocationThisDeviceShares,
|
||||
subtitle: AppLocalizations.of(
|
||||
context,
|
||||
)!.chooseTheNameAndLocationThisDeviceShares,
|
||||
icon: Icons.public_rounded,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1368,9 +1371,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
if (_gpsEnabled != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
_SettingHighlightCard(
|
||||
icon: _gpsEnabled!
|
||||
? Icons.gps_fixed
|
||||
: Icons.gps_off,
|
||||
icon: _gpsEnabled! ? Icons.gps_fixed : Icons.gps_off,
|
||||
title: AppLocalizations.of(context)!.gpsModule,
|
||||
description:
|
||||
'Enable or disable the onboard GPS hardware.',
|
||||
@@ -1411,13 +1412,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface.withValues(alpha: 0.65),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(
|
||||
alpha: 0.7,
|
||||
),
|
||||
),
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1492,7 +1489,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
SizedBox(height: 20),
|
||||
_ConfigSectionCard(
|
||||
title: AppLocalizations.of(context)!.radioSettings,
|
||||
subtitle: AppLocalizations.of(context)!.chooseAPresetOrFinetuneCustomRadioSettings,
|
||||
subtitle: AppLocalizations.of(
|
||||
context,
|
||||
)!.chooseAPresetOrFinetuneCustomRadioSettings,
|
||||
icon: Icons.settings_input_antenna_rounded,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1716,7 +1715,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
SizedBox(height: 16),
|
||||
_SettingHighlightCard(
|
||||
icon: Icons.repeat_rounded,
|
||||
title: AppLocalizations.of(context)!.repeatNearbyTraffic,
|
||||
title: AppLocalizations.of(
|
||||
context,
|
||||
)!.repeatNearbyTraffic,
|
||||
description:
|
||||
deviceInfo.allowedRepeatFreqRanges != null &&
|
||||
deviceInfo.allowedRepeatFreqRanges!.isNotEmpty
|
||||
@@ -1762,7 +1763,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
SizedBox(height: 20),
|
||||
_ConfigSectionCard(
|
||||
title: AppLocalizations.of(context)!.dangerZone,
|
||||
subtitle: AppLocalizations.of(context)!.destructiveDeviceActions,
|
||||
subtitle: AppLocalizations.of(
|
||||
context,
|
||||
)!.destructiveDeviceActions,
|
||||
icon: Icons.warning_amber_rounded,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1770,13 +1773,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.errorContainer.withValues(
|
||||
alpha: 0.55,
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(
|
||||
color: colorScheme.error.withValues(alpha: 0.28),
|
||||
),
|
||||
border: Border.all(color: colorScheme.error),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1794,14 +1793,14 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
'Wipe data on device',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: colorScheme.onErrorContainer,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Erase contacts, keys, and radio settings from the connected MeshCore device and return it to factory defaults.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onErrorContainer,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1831,7 +1830,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
),
|
||||
)
|
||||
: Icon(Icons.people_alt_outlined),
|
||||
label: Text(AppLocalizations.of(context)!.clearAllContacts),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.clearAllContacts,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -1855,7 +1856,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
),
|
||||
)
|
||||
: Icon(Icons.forum_outlined),
|
||||
label: Text(AppLocalizations.of(context)!.clearAllChannels),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.clearAllChannels,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -1871,7 +1874,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
||||
minimumSize: const Size.fromHeight(52),
|
||||
),
|
||||
icon: Icon(Icons.delete_forever_rounded),
|
||||
label: Text(AppLocalizations.of(context)!.wipeDeviceData),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!.wipeDeviceData,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1930,26 +1935,9 @@ class _ConfigHeroCard extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(22),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colorScheme.primaryContainer,
|
||||
colorScheme.primary.withValues(alpha: 0.14),
|
||||
colorScheme.surfaceContainerHighest,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
border: Border.all(
|
||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.08),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.primary.withValues(alpha: 0.10),
|
||||
blurRadius: 26,
|
||||
offset: const Offset(0, 12),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1960,13 +1948,10 @@ class _ConfigHeroCard extends StatelessWidget {
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.12),
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.tune_rounded,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
child: Icon(Icons.tune_rounded, color: colorScheme.primary),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
@@ -1979,15 +1964,13 @@ class _ConfigHeroCard extends StatelessWidget {
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.onPrimaryContainer.withValues(
|
||||
alpha: 0.10,
|
||||
),
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
'Device settings',
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
@@ -1997,16 +1980,14 @@ class _ConfigHeroCard extends StatelessWidget {
|
||||
title,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer.withValues(
|
||||
alpha: 0.82,
|
||||
),
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
@@ -2050,23 +2031,24 @@ class _HeroStat extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final background = data.emphasized
|
||||
? colorScheme.primary.withValues(alpha: 0.18)
|
||||
: colorScheme.onPrimaryContainer.withValues(alpha: 0.10);
|
||||
final iconColor = data.emphasized
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant;
|
||||
final borderColor = data.emphasized
|
||||
? colorScheme.primary
|
||||
: colorScheme.outlineVariant;
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: background,
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.08),
|
||||
),
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(data.icon, size: 18, color: colorScheme.onPrimaryContainer),
|
||||
Icon(data.icon, size: 18, color: iconColor),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -2075,9 +2057,7 @@ class _HeroStat extends StatelessWidget {
|
||||
Text(
|
||||
data.label,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer.withValues(
|
||||
alpha: 0.76,
|
||||
),
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
@@ -2086,7 +2066,7 @@ class _HeroStat extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
color: colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
@@ -2118,7 +2098,9 @@ class _ConfigSectionCard extends StatelessWidget {
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Card(
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
elevation: 0,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
||||
child: Padding(
|
||||
@@ -2133,7 +2115,7 @@ class _ConfigSectionCard extends StatelessWidget {
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withValues(alpha: 0.10),
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(icon, color: colorScheme.primary),
|
||||
@@ -2259,14 +2241,7 @@ class _StorageUsageMeter extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.65),
|
||||
colorScheme.surfaceContainer,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
),
|
||||
@@ -2321,9 +2296,9 @@ class _SelectedPresetCard extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primaryContainer.withValues(alpha: 0.55),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: colorScheme.primary.withValues(alpha: 0.18)),
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -2418,16 +2393,9 @@ class _SettingHighlightCard extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
accentColor.withValues(alpha: 0.14),
|
||||
colorScheme.surfaceContainerHigh.withValues(alpha: 0.85),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
border: Border.all(color: accentColor.withValues(alpha: 0.18)),
|
||||
border: Border.all(color: colorScheme.outlineVariant),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -2436,7 +2404,7 @@ class _SettingHighlightCard extends StatelessWidget {
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
color: accentColor.withValues(alpha: 0.16),
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(icon, color: accentColor),
|
||||
|
||||
@@ -185,14 +185,7 @@ class ContactTile extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.surfaceContainerLow,
|
||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
||||
],
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||
),
|
||||
|
||||
@@ -106,7 +106,10 @@ class SpectrumScanPanel extends StatelessWidget {
|
||||
color: theme.colorScheme.primary,
|
||||
label: AppLocalizations.of(context)!.quiet,
|
||||
),
|
||||
_LegendChip(color: Colors.orange, label: AppLocalizations.of(context)!.moderate),
|
||||
_LegendChip(
|
||||
color: Colors.orange,
|
||||
label: AppLocalizations.of(context)!.moderate,
|
||||
),
|
||||
_LegendChip(
|
||||
color: theme.colorScheme.error,
|
||||
label: AppLocalizations.of(context)!.busy,
|
||||
@@ -147,7 +150,7 @@ class SpectrumScanPanel extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface.withValues(alpha: 0.7),
|
||||
color: theme.colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
),
|
||||
@@ -303,12 +306,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
||||
height: 108,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
scheme.surface,
|
||||
scheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
||||
],
|
||||
),
|
||||
color: scheme.surfaceContainerLow,
|
||||
border: Border.all(color: scheme.outlineVariant),
|
||||
),
|
||||
child: Stack(
|
||||
@@ -322,13 +320,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
scheme.primary.withValues(alpha: 0.12),
|
||||
scheme.tertiary.withValues(alpha: 0.08),
|
||||
scheme.primary.withValues(alpha: 0.12),
|
||||
],
|
||||
),
|
||||
color: scheme.surfaceContainerHigh,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -341,7 +333,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: scheme.primary.withValues(alpha: 0.20),
|
||||
color: scheme.primaryContainer,
|
||||
border: Border.all(color: scheme.primary),
|
||||
),
|
||||
),
|
||||
@@ -355,7 +347,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
color: scheme.tertiary.withValues(alpha: 0.32),
|
||||
color: scheme.tertiaryContainer,
|
||||
border: Border.all(color: scheme.tertiary),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,7 +4,7 @@ import '../../l10n/app_localizations.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../common/contact_avatar.dart';
|
||||
|
||||
enum _RecipientSortMode { activity, alphabetical }
|
||||
enum _RecipientSortMode { activity, favorites, alphabetical }
|
||||
|
||||
/// Bottom sheet for selecting message recipient (channel, contact, or room)
|
||||
class RecipientSelectorSheet extends StatefulWidget {
|
||||
@@ -74,11 +74,27 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
return a.isPublicChannel ? -1 : 1;
|
||||
}
|
||||
|
||||
if (_sortMode == _RecipientSortMode.activity) {
|
||||
if (_sortMode == _RecipientSortMode.favorites) {
|
||||
if (a.isFavourite != b.isFavourite) {
|
||||
return a.isFavourite ? -1 : 1;
|
||||
}
|
||||
final unreadCompare = _unreadFor(b).compareTo(_unreadFor(a));
|
||||
if (unreadCompare != 0) {
|
||||
return unreadCompare;
|
||||
}
|
||||
final lastSeenCompare = b.lastSeenTime.compareTo(a.lastSeenTime);
|
||||
if (lastSeenCompare != 0) {
|
||||
return lastSeenCompare;
|
||||
}
|
||||
} else if (_sortMode == _RecipientSortMode.activity) {
|
||||
final unreadCompare = _unreadFor(b).compareTo(_unreadFor(a));
|
||||
if (unreadCompare != 0) {
|
||||
return unreadCompare;
|
||||
}
|
||||
final lastSeenCompare = b.lastSeenTime.compareTo(a.lastSeenTime);
|
||||
if (lastSeenCompare != 0) {
|
||||
return lastSeenCompare;
|
||||
}
|
||||
}
|
||||
|
||||
return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase());
|
||||
@@ -165,7 +181,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 16),
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
@@ -176,64 +192,16 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.selectRecipient,
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
if (widget.showAllOption)
|
||||
_SummaryChip(
|
||||
icon: Icons.all_inbox_rounded,
|
||||
label: l10n.showAll,
|
||||
count: widget.unreadCount,
|
||||
accentColor: colorScheme.primary,
|
||||
),
|
||||
if (hasChannels)
|
||||
_SummaryChip(
|
||||
icon: Icons.broadcast_on_personal_rounded,
|
||||
label: l10n.channels,
|
||||
count: filteredChannels.length,
|
||||
accentColor: _sectionColor(
|
||||
context,
|
||||
'channel',
|
||||
),
|
||||
),
|
||||
if (hasContacts)
|
||||
_SummaryChip(
|
||||
icon: Icons.person_rounded,
|
||||
label: l10n.contacts,
|
||||
count: filteredContacts.length,
|
||||
accentColor: _sectionColor(
|
||||
context,
|
||||
'contact',
|
||||
),
|
||||
),
|
||||
if (hasRooms)
|
||||
_SummaryChip(
|
||||
icon: Icons.meeting_room_outlined,
|
||||
label: l10n.rooms,
|
||||
count: filteredRooms.length,
|
||||
accentColor: _sectionColor(context, 'room'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: Text(
|
||||
l10n.selectRecipient,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -244,69 +212,47 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.searchRecipients,
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() {
|
||||
_searchQuery = '';
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHigh,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 18,
|
||||
vertical: 16,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_SortChip(
|
||||
icon: Icons.flash_on_rounded,
|
||||
label: l10n.active,
|
||||
selected: _sortMode == _RecipientSortMode.activity,
|
||||
onTap: () {
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.searchRecipients,
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() {
|
||||
_searchQuery = '';
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
filled: true,
|
||||
fillColor: colorScheme.surfaceContainerHigh,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_sortMode = _RecipientSortMode.activity;
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
_SortChip(
|
||||
icon: Icons.sort_by_alpha_rounded,
|
||||
label: 'A-Z',
|
||||
selected: _sortMode == _RecipientSortMode.alphabetical,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_sortMode = _RecipientSortMode.alphabetical;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_buildSortMenuButton(context),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -422,6 +368,82 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSortMenuButton(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return PopupMenuButton<_RecipientSortMode>(
|
||||
tooltip: 'Sort',
|
||||
initialValue: _sortMode,
|
||||
onSelected: (sortMode) {
|
||||
setState(() {
|
||||
_sortMode = sortMode;
|
||||
});
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem<_RecipientSortMode>(
|
||||
value: _RecipientSortMode.activity,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.flash_on_rounded,
|
||||
size: 18,
|
||||
color: _sortMode == _RecipientSortMode.activity
|
||||
? colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(l10n.active),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<_RecipientSortMode>(
|
||||
value: _RecipientSortMode.favorites,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.star_rounded,
|
||||
size: 18,
|
||||
color: _sortMode == _RecipientSortMode.favorites
|
||||
? colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(l10n.favourites),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem<_RecipientSortMode>(
|
||||
value: _RecipientSortMode.alphabetical,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.sort_by_alpha_rounded, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text('A-Z'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
_sortMode == _RecipientSortMode.activity
|
||||
? Icons.flash_on_rounded
|
||||
: _sortMode == _RecipientSortMode.favorites
|
||||
? Icons.star_rounded
|
||||
: Icons.sort_by_alpha_rounded,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionCard(
|
||||
BuildContext context, {
|
||||
required String type,
|
||||
@@ -438,15 +460,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.surface,
|
||||
accentColor.withValues(alpha: 0.05),
|
||||
colorScheme.surfaceContainerLow,
|
||||
],
|
||||
),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
||||
),
|
||||
@@ -530,14 +544,9 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.primaryContainer.withValues(alpha: 0.4),
|
||||
colorScheme.surfaceContainerHigh,
|
||||
],
|
||||
),
|
||||
color: isSelected
|
||||
? colorScheme.primaryContainer.withValues(alpha: 0.42)
|
||||
: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
@@ -750,104 +759,3 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SummaryChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final int count;
|
||||
final Color accentColor;
|
||||
|
||||
const _SummaryChip({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.count,
|
||||
required this.accentColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 14, color: accentColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$count',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SortChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _SortChip({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return FilterChip(
|
||||
selected: selected,
|
||||
onSelected: (_) => onTap(),
|
||||
avatar: Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: selected
|
||||
? colorScheme.onSecondaryContainer
|
||||
: colorScheme.primary,
|
||||
),
|
||||
label: Text(
|
||||
label,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: selected
|
||||
? colorScheme.secondaryContainer
|
||||
: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||
),
|
||||
selectedColor: colorScheme.secondaryContainer,
|
||||
checkmarkColor: colorScheme.onSecondaryContainer,
|
||||
backgroundColor: colorScheme.surfaceContainerLow,
|
||||
labelStyle: TextStyle(
|
||||
color: selected
|
||||
? colorScheme.onSecondaryContainer
|
||||
: colorScheme.onSurface,
|
||||
),
|
||||
showCheckmark: false,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:meshcore_sar_app/l10n/app_localizations.dart';
|
||||
import 'package:meshcore_sar_app/models/channel.dart';
|
||||
import 'package:meshcore_sar_app/models/contact.dart';
|
||||
import 'package:meshcore_sar_app/models/contact_group.dart';
|
||||
import 'package:meshcore_sar_app/models/device_info.dart';
|
||||
import 'package:meshcore_sar_app/providers/connection_provider.dart';
|
||||
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
||||
import 'package:meshcore_sar_app/providers/map_provider.dart';
|
||||
@@ -16,6 +17,20 @@ import 'package:meshcore_sar_app/utils/contact_grouping.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class _FakeConnectionProvider extends ConnectionProvider {
|
||||
_FakeConnectionProvider({required bool isConnected})
|
||||
: _isConnected = isConnected;
|
||||
|
||||
final bool _isConnected;
|
||||
|
||||
@override
|
||||
DeviceInfo get deviceInfo => DeviceInfo(
|
||||
connectionState: _isConnected
|
||||
? ConnectionState.connected
|
||||
: ConnectionState.disconnected,
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
String? clipboardText;
|
||||
|
||||
@@ -102,8 +117,11 @@ void main() {
|
||||
WidgetTester tester, {
|
||||
List<Contact> contacts = const [],
|
||||
List<SavedContactGroup> savedGroups = const [],
|
||||
ConnectionProvider? connectionProvider,
|
||||
}) async {
|
||||
final contactsProvider = ContactsProvider();
|
||||
final resolvedConnectionProvider =
|
||||
connectionProvider ?? ConnectionProvider();
|
||||
for (final contact in contacts) {
|
||||
contactsProvider.addOrUpdateContact(contact);
|
||||
}
|
||||
@@ -118,7 +136,9 @@ void main() {
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: contactsProvider),
|
||||
ChangeNotifierProvider(create: (_) => ConnectionProvider()),
|
||||
ChangeNotifierProvider<ConnectionProvider>.value(
|
||||
value: resolvedConnectionProvider,
|
||||
),
|
||||
ChangeNotifierProvider(create: (_) => MessagesProvider()),
|
||||
ChangeNotifierProvider(create: (_) => MapProvider()),
|
||||
],
|
||||
@@ -264,4 +284,22 @@ void main() {
|
||||
expect(find.text('Sensors'), findsWidgets);
|
||||
expect(find.text('WX Station'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'contacts sections do not show sensor or repeater discovery actions',
|
||||
(tester) async {
|
||||
await pumpContactsTab(
|
||||
tester,
|
||||
contacts: [
|
||||
buildRepeater(seed: 70, name: 'Relay 1'),
|
||||
buildSensor(seed: 71, name: 'WX Station'),
|
||||
],
|
||||
connectionProvider: _FakeConnectionProvider(isConnected: true),
|
||||
);
|
||||
|
||||
expect(find.byTooltip('Discover repeaters'), findsNothing);
|
||||
expect(find.byTooltip('Discover sensors'), findsNothing);
|
||||
expect(find.byIcon(Icons.radar), findsNothing);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user