mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
fix: Simplify device settings styling #123
This commit is contained in:
@@ -934,39 +934,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
context,
|
context,
|
||||||
ContactSection.repeaters,
|
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(
|
_buildSectionFilterField(
|
||||||
context,
|
context,
|
||||||
@@ -1029,37 +996,6 @@ class _ContactsTabState extends State<ContactsTab> {
|
|||||||
context,
|
context,
|
||||||
ContactSection.sensors,
|
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(
|
_buildSectionFilterField(
|
||||||
context,
|
context,
|
||||||
@@ -1718,15 +1654,7 @@ class _SectionCard extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 16),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
colorScheme.surface,
|
|
||||||
accentColor.withValues(alpha: 0.04),
|
|
||||||
colorScheme.surfaceContainerLow,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(26),
|
borderRadius: BorderRadius.circular(26),
|
||||||
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
@@ -1903,14 +1831,7 @@ class _ChannelActivityCard extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(18),
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
colorScheme.surfaceContainerLow,
|
|
||||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -729,9 +729,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _gpsLoading = false);
|
setState(() => _gpsLoading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text('Failed to set GPS mode: $e')),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text('Failed to set GPS mode: $e')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,7 +893,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
.toList();
|
.toList();
|
||||||
if (contacts.isEmpty) {
|
if (contacts.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(AppLocalizations.of(context)!.noDeviceContactsToClear)),
|
SnackBar(
|
||||||
|
content: Text(AppLocalizations.of(context)!.noDeviceContactsToClear),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -988,7 +990,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
.toList();
|
.toList();
|
||||||
if (channels.isEmpty) {
|
if (channels.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(AppLocalizations.of(context)!.noCustomChannelsToClear)),
|
SnackBar(
|
||||||
|
content: Text(AppLocalizations.of(context)!.noCustomChannelsToClear),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1095,19 +1099,8 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
|
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
|
||||||
body: Container(
|
body: ColoredBox(
|
||||||
decoration: BoxDecoration(
|
color: colorScheme.surface,
|
||||||
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],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||||
@@ -1146,7 +1139,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
_ConfigSectionCard(
|
_ConfigSectionCard(
|
||||||
title: AppLocalizations.of(context)!.storage,
|
title: AppLocalizations.of(context)!.storage,
|
||||||
subtitle: AppLocalizations.of(context)!.availableSpaceOnThisDevice,
|
subtitle: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.availableSpaceOnThisDevice,
|
||||||
icon: Icons.storage_rounded,
|
icon: Icons.storage_rounded,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1190,7 +1185,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
icon: _autoAddDiscoveredContactsEnabled
|
icon: _autoAddDiscoveredContactsEnabled
|
||||||
? Icons.person_add_alt_1
|
? Icons.person_add_alt_1
|
||||||
: Icons.person_add_disabled,
|
: Icons.person_add_disabled,
|
||||||
title: AppLocalizations.of(context)!.enableAutomaticAdding,
|
title: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.enableAutomaticAdding,
|
||||||
description:
|
description:
|
||||||
'Turn this off to keep discoveries manual-only on the radio.',
|
'Turn this off to keep discoveries manual-only on the radio.',
|
||||||
accentColor: _autoAddDiscoveredContactsEnabled
|
accentColor: _autoAddDiscoveredContactsEnabled
|
||||||
@@ -1293,7 +1290,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
SizedBox(height: 14),
|
SizedBox(height: 14),
|
||||||
_SettingHighlightCard(
|
_SettingHighlightCard(
|
||||||
icon: Icons.history_toggle_off_rounded,
|
icon: Icons.history_toggle_off_rounded,
|
||||||
title: AppLocalizations.of(context)!.overwriteOldestWhenFull,
|
title: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.overwriteOldestWhenFull,
|
||||||
description:
|
description:
|
||||||
'Allow the radio to replace the oldest contact when storage is full.',
|
'Allow the radio to replace the oldest contact when storage is full.',
|
||||||
accentColor: _overwriteOldestAutoAddEnabled
|
accentColor: _overwriteOldestAutoAddEnabled
|
||||||
@@ -1329,7 +1328,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
: _saveAutoDiscoverySettings,
|
: _saveAutoDiscoverySettings,
|
||||||
isSaving: _isSavingAutoDiscoverySettings,
|
isSaving: _isSavingAutoDiscoverySettings,
|
||||||
isSaved: _autoDiscoverySettingsSaved,
|
isSaved: _autoDiscoverySettingsSaved,
|
||||||
label: AppLocalizations.of(context)!.saveDiscoverySettings,
|
label: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.saveDiscoverySettings,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1338,7 +1339,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
_ConfigSectionCard(
|
_ConfigSectionCard(
|
||||||
title: AppLocalizations.of(context)!.publicInfo,
|
title: AppLocalizations.of(context)!.publicInfo,
|
||||||
subtitle: AppLocalizations.of(context)!.chooseTheNameAndLocationThisDeviceShares,
|
subtitle: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.chooseTheNameAndLocationThisDeviceShares,
|
||||||
icon: Icons.public_rounded,
|
icon: Icons.public_rounded,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1368,9 +1371,7 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
if (_gpsEnabled != null) ...[
|
if (_gpsEnabled != null) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_SettingHighlightCard(
|
_SettingHighlightCard(
|
||||||
icon: _gpsEnabled!
|
icon: _gpsEnabled! ? Icons.gps_fixed : Icons.gps_off,
|
||||||
? Icons.gps_fixed
|
|
||||||
: Icons.gps_off,
|
|
||||||
title: AppLocalizations.of(context)!.gpsModule,
|
title: AppLocalizations.of(context)!.gpsModule,
|
||||||
description:
|
description:
|
||||||
'Enable or disable the onboard GPS hardware.',
|
'Enable or disable the onboard GPS hardware.',
|
||||||
@@ -1411,13 +1412,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.surface.withValues(alpha: 0.65),
|
color: colorScheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
border: Border.all(
|
border: Border.all(color: colorScheme.outlineVariant),
|
||||||
color: colorScheme.outlineVariant.withValues(
|
|
||||||
alpha: 0.7,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1492,7 +1489,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
_ConfigSectionCard(
|
_ConfigSectionCard(
|
||||||
title: AppLocalizations.of(context)!.radioSettings,
|
title: AppLocalizations.of(context)!.radioSettings,
|
||||||
subtitle: AppLocalizations.of(context)!.chooseAPresetOrFinetuneCustomRadioSettings,
|
subtitle: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.chooseAPresetOrFinetuneCustomRadioSettings,
|
||||||
icon: Icons.settings_input_antenna_rounded,
|
icon: Icons.settings_input_antenna_rounded,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1716,7 +1715,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
_SettingHighlightCard(
|
_SettingHighlightCard(
|
||||||
icon: Icons.repeat_rounded,
|
icon: Icons.repeat_rounded,
|
||||||
title: AppLocalizations.of(context)!.repeatNearbyTraffic,
|
title: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.repeatNearbyTraffic,
|
||||||
description:
|
description:
|
||||||
deviceInfo.allowedRepeatFreqRanges != null &&
|
deviceInfo.allowedRepeatFreqRanges != null &&
|
||||||
deviceInfo.allowedRepeatFreqRanges!.isNotEmpty
|
deviceInfo.allowedRepeatFreqRanges!.isNotEmpty
|
||||||
@@ -1762,7 +1763,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
_ConfigSectionCard(
|
_ConfigSectionCard(
|
||||||
title: AppLocalizations.of(context)!.dangerZone,
|
title: AppLocalizations.of(context)!.dangerZone,
|
||||||
subtitle: AppLocalizations.of(context)!.destructiveDeviceActions,
|
subtitle: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
)!.destructiveDeviceActions,
|
||||||
icon: Icons.warning_amber_rounded,
|
icon: Icons.warning_amber_rounded,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1770,13 +1773,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.errorContainer.withValues(
|
color: colorScheme.surfaceContainerLow,
|
||||||
alpha: 0.55,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
border: Border.all(
|
border: Border.all(color: colorScheme.error),
|
||||||
color: colorScheme.error.withValues(alpha: 0.28),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1794,14 +1793,14 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
'Wipe data on device',
|
'Wipe data on device',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
color: colorScheme.onErrorContainer,
|
color: colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Erase contacts, keys, and radio settings from the connected MeshCore device and return it to factory defaults.',
|
'Erase contacts, keys, and radio settings from the connected MeshCore device and return it to factory defaults.',
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
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),
|
: Icon(Icons.people_alt_outlined),
|
||||||
label: Text(AppLocalizations.of(context)!.clearAllContacts),
|
label: Text(
|
||||||
|
AppLocalizations.of(context)!.clearAllContacts,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -1855,7 +1856,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Icon(Icons.forum_outlined),
|
: Icon(Icons.forum_outlined),
|
||||||
label: Text(AppLocalizations.of(context)!.clearAllChannels),
|
label: Text(
|
||||||
|
AppLocalizations.of(context)!.clearAllChannels,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -1871,7 +1874,9 @@ class _DeviceConfigScreenState extends State<DeviceConfigScreen> {
|
|||||||
minimumSize: const Size.fromHeight(52),
|
minimumSize: const Size.fromHeight(52),
|
||||||
),
|
),
|
||||||
icon: Icon(Icons.delete_forever_rounded),
|
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(
|
return Container(
|
||||||
padding: const EdgeInsets.all(22),
|
padding: const EdgeInsets.all(22),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
colors: [
|
|
||||||
colorScheme.primaryContainer,
|
|
||||||
colorScheme.primary.withValues(alpha: 0.14),
|
|
||||||
colorScheme.surfaceContainerHighest,
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(28),
|
borderRadius: BorderRadius.circular(28),
|
||||||
border: Border.all(
|
border: Border.all(color: colorScheme.outlineVariant),
|
||||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.08),
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: colorScheme.primary.withValues(alpha: 0.10),
|
|
||||||
blurRadius: 26,
|
|
||||||
offset: const Offset(0, 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -1960,13 +1948,10 @@ class _ConfigHeroCard extends StatelessWidget {
|
|||||||
width: 56,
|
width: 56,
|
||||||
height: 56,
|
height: 56,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.12),
|
color: colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(18),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(Icons.tune_rounded, color: colorScheme.primary),
|
||||||
Icons.tune_rounded,
|
|
||||||
color: colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -1979,15 +1964,13 @@ class _ConfigHeroCard extends StatelessWidget {
|
|||||||
vertical: 6,
|
vertical: 6,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.onPrimaryContainer.withValues(
|
color: colorScheme.surfaceContainerHighest,
|
||||||
alpha: 0.10,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Device settings',
|
'Device settings',
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
color: colorScheme.onPrimaryContainer,
|
color: colorScheme.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1997,16 +1980,14 @@ class _ConfigHeroCard extends StatelessWidget {
|
|||||||
title,
|
title,
|
||||||
style: theme.textTheme.headlineSmall?.copyWith(
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
color: colorScheme.onPrimaryContainer,
|
color: colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
color: colorScheme.onPrimaryContainer.withValues(
|
color: colorScheme.onSurfaceVariant,
|
||||||
alpha: 0.82,
|
|
||||||
),
|
|
||||||
height: 1.35,
|
height: 1.35,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2050,23 +2031,24 @@ class _HeroStat extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
final background = data.emphasized
|
final iconColor = data.emphasized
|
||||||
? colorScheme.primary.withValues(alpha: 0.18)
|
? colorScheme.primary
|
||||||
: colorScheme.onPrimaryContainer.withValues(alpha: 0.10);
|
: colorScheme.onSurfaceVariant;
|
||||||
|
final borderColor = data.emphasized
|
||||||
|
? colorScheme.primary
|
||||||
|
: colorScheme.outlineVariant;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(minWidth: 140),
|
constraints: const BoxConstraints(minWidth: 140),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: background,
|
color: colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(color: borderColor),
|
||||||
color: colorScheme.onPrimaryContainer.withValues(alpha: 0.08),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(data.icon, size: 18, color: colorScheme.onPrimaryContainer),
|
Icon(data.icon, size: 18, color: iconColor),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -2075,9 +2057,7 @@ class _HeroStat extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
data.label,
|
data.label,
|
||||||
style: theme.textTheme.labelMedium?.copyWith(
|
style: theme.textTheme.labelMedium?.copyWith(
|
||||||
color: colorScheme.onPrimaryContainer.withValues(
|
color: colorScheme.onSurfaceVariant,
|
||||||
alpha: 0.76,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
@@ -2086,7 +2066,7 @@ class _HeroStat extends StatelessWidget {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
color: colorScheme.onPrimaryContainer,
|
color: colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2118,7 +2098,9 @@ class _ConfigSectionCard extends StatelessWidget {
|
|||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
|
color: colorScheme.surfaceContainerLow,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -2133,7 +2115,7 @@ class _ConfigSectionCard extends StatelessWidget {
|
|||||||
width: 42,
|
width: 42,
|
||||||
height: 42,
|
height: 42,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.primary.withValues(alpha: 0.10),
|
color: colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: colorScheme.primary),
|
child: Icon(icon, color: colorScheme.primary),
|
||||||
@@ -2259,14 +2241,7 @@ class _StorageUsageMeter extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
colors: [
|
|
||||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.65),
|
|
||||||
colorScheme.surfaceContainer,
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(18),
|
||||||
border: Border.all(color: colorScheme.outlineVariant),
|
border: Border.all(color: colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
@@ -2321,9 +2296,9 @@ class _SelectedPresetCard extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.primaryContainer.withValues(alpha: 0.55),
|
color: colorScheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(color: colorScheme.primary.withValues(alpha: 0.18)),
|
border: Border.all(color: colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -2418,16 +2393,9 @@ class _SettingHighlightCard extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
colors: [
|
|
||||||
accentColor.withValues(alpha: 0.14),
|
|
||||||
colorScheme.surfaceContainerHigh.withValues(alpha: 0.85),
|
|
||||||
],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
border: Border.all(color: accentColor.withValues(alpha: 0.18)),
|
border: Border.all(color: colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -2436,7 +2404,7 @@ class _SettingHighlightCard extends StatelessWidget {
|
|||||||
width: 42,
|
width: 42,
|
||||||
height: 42,
|
height: 42,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: accentColor.withValues(alpha: 0.16),
|
color: colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: accentColor),
|
child: Icon(icon, color: accentColor),
|
||||||
|
|||||||
@@ -185,14 +185,7 @@ class ContactTile extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(18),
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
colorScheme.surfaceContainerLow,
|
|
||||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -106,7 +106,10 @@ class SpectrumScanPanel extends StatelessWidget {
|
|||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
label: AppLocalizations.of(context)!.quiet,
|
label: AppLocalizations.of(context)!.quiet,
|
||||||
),
|
),
|
||||||
_LegendChip(color: Colors.orange, label: AppLocalizations.of(context)!.moderate),
|
_LegendChip(
|
||||||
|
color: Colors.orange,
|
||||||
|
label: AppLocalizations.of(context)!.moderate,
|
||||||
|
),
|
||||||
_LegendChip(
|
_LegendChip(
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
label: AppLocalizations.of(context)!.busy,
|
label: AppLocalizations.of(context)!.busy,
|
||||||
@@ -147,7 +150,7 @@ class SpectrumScanPanel extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.surface.withValues(alpha: 0.7),
|
color: theme.colorScheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
@@ -303,12 +306,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
|||||||
height: 108,
|
height: 108,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
gradient: LinearGradient(
|
color: scheme.surfaceContainerLow,
|
||||||
colors: [
|
|
||||||
scheme.surface,
|
|
||||||
scheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
border: Border.all(color: scheme.outlineVariant),
|
border: Border.all(color: scheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@@ -322,13 +320,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
|||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
gradient: LinearGradient(
|
color: scheme.surfaceContainerHigh,
|
||||||
colors: [
|
|
||||||
scheme.primary.withValues(alpha: 0.12),
|
|
||||||
scheme.tertiary.withValues(alpha: 0.08),
|
|
||||||
scheme.primary.withValues(alpha: 0.12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -341,7 +333,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
|||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
color: scheme.primary.withValues(alpha: 0.20),
|
color: scheme.primaryContainer,
|
||||||
border: Border.all(color: scheme.primary),
|
border: Border.all(color: scheme.primary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -355,7 +347,7 @@ class _FrequencyRangePreview extends StatelessWidget {
|
|||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
color: scheme.tertiary.withValues(alpha: 0.32),
|
color: scheme.tertiaryContainer,
|
||||||
border: Border.all(color: scheme.tertiary),
|
border: Border.all(color: scheme.tertiary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import '../../l10n/app_localizations.dart';
|
|||||||
import '../../models/contact.dart';
|
import '../../models/contact.dart';
|
||||||
import '../common/contact_avatar.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)
|
/// Bottom sheet for selecting message recipient (channel, contact, or room)
|
||||||
class RecipientSelectorSheet extends StatefulWidget {
|
class RecipientSelectorSheet extends StatefulWidget {
|
||||||
@@ -74,11 +74,27 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
return a.isPublicChannel ? -1 : 1;
|
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));
|
final unreadCompare = _unreadFor(b).compareTo(_unreadFor(a));
|
||||||
if (unreadCompare != 0) {
|
if (unreadCompare != 0) {
|
||||||
return unreadCompare;
|
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());
|
return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase());
|
||||||
@@ -165,7 +181,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 16),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
@@ -176,65 +192,17 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
borderRadius: BorderRadius.circular(999),
|
borderRadius: BorderRadius.circular(999),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
l10n.selectRecipient,
|
l10n.selectRecipient,
|
||||||
style: Theme.of(context).textTheme.titleLarge
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
?.copyWith(
|
|
||||||
fontWeight: FontWeight.w900,
|
fontWeight: FontWeight.w900,
|
||||||
letterSpacing: -0.5,
|
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'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
IconButton.filledTonal(
|
IconButton.filledTonal(
|
||||||
@@ -244,8 +212,11 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 12),
|
||||||
TextField(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: l10n.searchRecipients,
|
hintText: l10n.searchRecipients,
|
||||||
@@ -264,12 +235,12 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
filled: true,
|
filled: true,
|
||||||
fillColor: colorScheme.surfaceContainerHigh,
|
fillColor: colorScheme.surfaceContainerHigh,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(18),
|
borderRadius: BorderRadius.circular(16),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 18,
|
horizontal: 16,
|
||||||
vertical: 16,
|
vertical: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -278,36 +249,11 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
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: () {
|
|
||||||
setState(() {
|
|
||||||
_sortMode = _RecipientSortMode.activity;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_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(
|
Widget _buildSectionCard(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String type,
|
required String type,
|
||||||
@@ -438,15 +460,7 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: colorScheme.surfaceContainerLow,
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
colorScheme.surface,
|
|
||||||
accentColor.withValues(alpha: 0.05),
|
|
||||||
colorScheme.surfaceContainerLow,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
border: Border.all(color: accentColor.withValues(alpha: 0.14)),
|
||||||
),
|
),
|
||||||
@@ -530,14 +544,9 @@ class _RecipientSelectorSheetState extends State<RecipientSelectorSheet> {
|
|||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
color: isSelected
|
||||||
begin: Alignment.topLeft,
|
? colorScheme.primaryContainer.withValues(alpha: 0.42)
|
||||||
end: Alignment.bottomRight,
|
: colorScheme.surfaceContainerHigh,
|
||||||
colors: [
|
|
||||||
colorScheme.primaryContainer.withValues(alpha: 0.4),
|
|
||||||
colorScheme.surfaceContainerHigh,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected
|
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/channel.dart';
|
||||||
import 'package:meshcore_sar_app/models/contact.dart';
|
import 'package:meshcore_sar_app/models/contact.dart';
|
||||||
import 'package:meshcore_sar_app/models/contact_group.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/connection_provider.dart';
|
||||||
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
|
||||||
import 'package:meshcore_sar_app/providers/map_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:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.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() {
|
void main() {
|
||||||
String? clipboardText;
|
String? clipboardText;
|
||||||
|
|
||||||
@@ -102,8 +117,11 @@ void main() {
|
|||||||
WidgetTester tester, {
|
WidgetTester tester, {
|
||||||
List<Contact> contacts = const [],
|
List<Contact> contacts = const [],
|
||||||
List<SavedContactGroup> savedGroups = const [],
|
List<SavedContactGroup> savedGroups = const [],
|
||||||
|
ConnectionProvider? connectionProvider,
|
||||||
}) async {
|
}) async {
|
||||||
final contactsProvider = ContactsProvider();
|
final contactsProvider = ContactsProvider();
|
||||||
|
final resolvedConnectionProvider =
|
||||||
|
connectionProvider ?? ConnectionProvider();
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
contactsProvider.addOrUpdateContact(contact);
|
contactsProvider.addOrUpdateContact(contact);
|
||||||
}
|
}
|
||||||
@@ -118,7 +136,9 @@ void main() {
|
|||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider.value(value: contactsProvider),
|
ChangeNotifierProvider.value(value: contactsProvider),
|
||||||
ChangeNotifierProvider(create: (_) => ConnectionProvider()),
|
ChangeNotifierProvider<ConnectionProvider>.value(
|
||||||
|
value: resolvedConnectionProvider,
|
||||||
|
),
|
||||||
ChangeNotifierProvider(create: (_) => MessagesProvider()),
|
ChangeNotifierProvider(create: (_) => MessagesProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => MapProvider()),
|
ChangeNotifierProvider(create: (_) => MapProvider()),
|
||||||
],
|
],
|
||||||
@@ -264,4 +284,22 @@ void main() {
|
|||||||
expect(find.text('Sensors'), findsWidgets);
|
expect(find.text('Sensors'), findsWidgets);
|
||||||
expect(find.text('WX Station'), findsOneWidget);
|
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