feat(#18): promote Radio Stats to top-level + rename Contact Settings to Contacts

Flatten two more redundant layers in the settings IA. Radio Stats: extract CompanionRadioStatsScreen's body into an embeddable CompanionRadioStatsBody (carrying the 1s-polling acquire/release lifecycle), add a top-level Radio Stats category that renders it directly, and remove the nested Radio Stats tile from the Radio Settings pane (the screen is kept; still pushed from radio_stats_entry.dart). Rename the Contact Settings category to Contacts (reusing contacts_title) with a contacts icon; rename _messagesContactsPane to _contactsPane.

Also fixes a pre-existing chart-freeze surfaced by review: _NoiseChartPainter compared only sample-list length, so the noise chart stopped repainting once the 120-sample buffer filled. Now compares contents via listEquals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/36/head
Strycher 4 months ago
parent 3fb798ca7f
commit 4ba4d22a79

@ -1,18 +1,37 @@
import 'package:flutter/foundation.dart' show listEquals;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart'; import 'package:meshcore_open/connector/meshcore_connector.dart';
import 'package:meshcore_open/models/companion_radio_stats.dart'; import 'package:meshcore_open/models/companion_radio_stats.dart';
import 'package:meshcore_open/l10n/l10n.dart'; import 'package:meshcore_open/l10n/l10n.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class CompanionRadioStatsScreen extends StatefulWidget { class CompanionRadioStatsScreen extends StatelessWidget {
const CompanionRadioStatsScreen({super.key}); const CompanionRadioStatsScreen({super.key});
@override @override
State<CompanionRadioStatsScreen> createState() => Widget build(BuildContext context) {
_CompanionRadioStatsScreenState(); return Scaffold(
appBar: AppBar(
title: Text(context.l10n.radioStats_screenTitle),
centerTitle: true,
),
body: const CompanionRadioStatsBody(),
);
}
} }
class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> { /// Embeddable Radio Stats content (no Scaffold) — used by both
/// [CompanionRadioStatsScreen] and the settings shell's Radio Stats pane.
/// Acquires 1 s radio-stats polling while mounted and releases it on dispose.
class CompanionRadioStatsBody extends StatefulWidget {
const CompanionRadioStatsBody({super.key});
@override
State<CompanionRadioStatsBody> createState() =>
_CompanionRadioStatsBodyState();
}
class _CompanionRadioStatsBodyState extends State<CompanionRadioStatsBody> {
final List<double> _noiseHistory = []; final List<double> _noiseHistory = [];
static const int _maxSamples = 120; static const int _maxSamples = 120;
MeshCoreConnector? _connector; MeshCoreConnector? _connector;
@ -52,16 +71,9 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
return Scaffold( return Selector<MeshCoreConnector, ({bool connected, bool supported})>(
appBar: AppBar( selector: (_, c) =>
title: Text(l10n.radioStats_screenTitle), (connected: c.isConnected, supported: c.supportsCompanionRadioStats),
centerTitle: true,
),
body: Selector<MeshCoreConnector, ({bool connected, bool supported})>(
selector: (_, c) => (
connected: c.isConnected,
supported: c.supportsCompanionRadioStats,
),
builder: (context, state, _) { builder: (context, state, _) {
if (!state.connected) { if (!state.connected) {
return Center(child: Text(l10n.radioStats_notConnected)); return Center(child: Text(l10n.radioStats_notConnected));
@ -95,9 +107,7 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
const SizedBox(height: 4), const SizedBox(height: 4),
Text(l10n.radioStats_lastRssi(stats.lastRssiDbm)), Text(l10n.radioStats_lastRssi(stats.lastRssiDbm)),
Text( Text(
l10n.radioStats_lastSnr( l10n.radioStats_lastSnr(stats.lastSnrDb.toStringAsFixed(1)),
stats.lastSnrDb.toStringAsFixed(1),
),
), ),
Text(l10n.radioStats_txAir(stats.txAirSecs)), Text(l10n.radioStats_txAir(stats.txAirSecs)),
Text(l10n.radioStats_rxAir(stats.rxAirSecs)), Text(l10n.radioStats_rxAir(stats.rxAirSecs)),
@ -119,16 +129,13 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
l10n.radioStats_chartCaption, l10n.radioStats_chartCaption,
style: tt.bodySmall?.copyWith( style: tt.bodySmall?.copyWith(color: scheme.onSurfaceVariant),
color: scheme.onSurfaceVariant,
),
), ),
], ],
); );
}, },
); );
}, },
),
); );
} }
} }
@ -233,8 +240,8 @@ class _NoiseChartPainter extends CustomPainter {
@override @override
bool shouldRepaint(covariant _NoiseChartPainter oldDelegate) { bool shouldRepaint(covariant _NoiseChartPainter oldDelegate) {
return oldDelegate.samples.length != samples.length || return oldDelegate.colorScheme != colorScheme ||
oldDelegate.colorScheme != colorScheme; !listEquals(oldDelegate.samples, samples);
} }
TextPainter _yAxisLabel(double v) { TextPainter _yAxisLabel(double v) {

@ -16,7 +16,7 @@ import 'settings/app_settings_view.dart';
import 'settings/message_settings_view.dart'; import 'settings/message_settings_view.dart';
import 'app_debug_log_screen.dart'; import 'app_debug_log_screen.dart';
import 'ble_debug_log_screen.dart'; import 'ble_debug_log_screen.dart';
import '../widgets/radio_stats_entry.dart'; import 'companion_radio_stats_screen.dart';
import '../widgets/sync_progress_overlay.dart'; import '../widgets/sync_progress_overlay.dart';
/// Convert device coding-rate value (1-4 on some firmware, 5-8 on others) /// Convert device coding-rate value (1-4 on some firmware, 5-8 on others)
@ -78,6 +78,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
subtitle: l10n.settings_radioSettingsSubtitle, subtitle: l10n.settings_radioSettingsSubtitle,
builder: _radioRangePane, builder: _radioRangePane,
), ),
SettingsCategory(
icon: Icons.sensors_outlined,
title: l10n.radioStats_settingsTile,
subtitle: l10n.radioStats_settingsSubtitle,
builder: _radioStatsPane,
),
SettingsCategory( SettingsCategory(
icon: Icons.badge_outlined, icon: Icons.badge_outlined,
title: l10n.settings_nodeSettings, title: l10n.settings_nodeSettings,
@ -90,10 +96,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: _privacyPane, builder: _privacyPane,
), ),
SettingsCategory( SettingsCategory(
icon: Icons.forum_outlined, icon: Icons.contacts_outlined,
title: l10n.settings_contactSettings, title: l10n.contacts_title,
subtitle: l10n.settings_contactSettingsSubtitle, subtitle: l10n.settings_contactSettingsSubtitle,
builder: _messagesContactsPane, builder: _contactsPane,
), ),
SettingsCategory( SettingsCategory(
icon: Icons.sms_outlined, icon: Icons.sms_outlined,
@ -144,17 +150,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
const Divider(height: 1), const Divider(height: 1),
_PathHashSizeTile(connector: connector), _PathHashSizeTile(connector: connector),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.sensors_outlined),
title: Text(l10n.radioStats_settingsTile),
subtitle: Text(l10n.radioStats_settingsSubtitle),
trailing: const Icon(Icons.chevron_right),
enabled:
connector.isConnected &&
connector.supportsCompanionRadioStats,
onTap: () => pushCompanionRadioStatsScreen(context),
),
], ],
), ),
), ),
@ -162,6 +157,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
} }
Widget _radioStatsPane(BuildContext context) =>
const CompanionRadioStatsBody();
Widget _identityPane(BuildContext context) { Widget _identityPane(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final connector = context.watch<MeshCoreConnector>(); final connector = context.watch<MeshCoreConnector>();
@ -237,7 +235,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
} }
Widget _messagesContactsPane(BuildContext context) { Widget _contactsPane(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final connector = context.watch<MeshCoreConnector>(); final connector = context.watch<MeshCoreConnector>();
return ListView( return ListView(

Loading…
Cancel
Save

Powered by TurnKey Linux.