mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
fix: Count RX data and speed up Flutter builds #123
This commit is contained in:
18
.github/workflows/build-artifacts.yml
vendored
18
.github/workflows/build-artifacts.yml
vendored
@@ -40,6 +40,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: stable
|
channel: stable
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Cache Android NDK
|
- name: Cache Android NDK
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -95,6 +98,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: stable
|
channel: stable
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Enable Linux desktop
|
- name: Enable Linux desktop
|
||||||
run: flutter config --enable-linux-desktop
|
run: flutter config --enable-linux-desktop
|
||||||
@@ -132,6 +138,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: stable
|
channel: stable
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Enable Windows desktop
|
- name: Enable Windows desktop
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -174,6 +183,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: stable
|
channel: stable
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Enable macOS desktop
|
- name: Enable macOS desktop
|
||||||
run: flutter config --enable-macos-desktop
|
run: flutter config --enable-macos-desktop
|
||||||
@@ -216,6 +228,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: stable
|
channel: stable
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
@@ -272,6 +287,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: stable
|
channel: stable
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Enable web
|
- name: Enable web
|
||||||
run: flutter config --enable-web
|
run: flutter config --enable-web
|
||||||
|
|||||||
3
.github/workflows/build-multiplatform.yml
vendored
3
.github/workflows/build-multiplatform.yml
vendored
@@ -27,6 +27,9 @@ jobs:
|
|||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
cache: true
|
cache: true
|
||||||
|
pub-cache: true
|
||||||
|
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||||
|
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ T? _maybeProvider<T>(BuildContext context) {
|
|||||||
|
|
||||||
class LiveTrafficScreen extends StatefulWidget {
|
class LiveTrafficScreen extends StatefulWidget {
|
||||||
final List<BlePacketLog> Function() logReader;
|
final List<BlePacketLog> Function() logReader;
|
||||||
final int Function()? rxCountReader;
|
|
||||||
final Listenable? refreshListenable;
|
final Listenable? refreshListenable;
|
||||||
final DateTime Function() now;
|
final DateTime Function() now;
|
||||||
final VoidCallback? openPacketLogs;
|
final VoidCallback? openPacketLogs;
|
||||||
@@ -35,7 +34,6 @@ class LiveTrafficScreen extends StatefulWidget {
|
|||||||
const LiveTrafficScreen({
|
const LiveTrafficScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.logReader,
|
required this.logReader,
|
||||||
this.rxCountReader,
|
|
||||||
this.refreshListenable,
|
this.refreshListenable,
|
||||||
DateTime Function()? now,
|
DateTime Function()? now,
|
||||||
this.openPacketLogs,
|
this.openPacketLogs,
|
||||||
@@ -49,7 +47,6 @@ class LiveTrafficScreen extends StatefulWidget {
|
|||||||
return LiveTrafficScreen(
|
return LiveTrafficScreen(
|
||||||
key: key,
|
key: key,
|
||||||
logReader: () => provider.bleService.packetLogs,
|
logReader: () => provider.bleService.packetLogs,
|
||||||
rxCountReader: () => provider.rxPacketCount,
|
|
||||||
refreshListenable: provider,
|
refreshListenable: provider,
|
||||||
openPacketLogs: openPacketLogs,
|
openPacketLogs: openPacketLogs,
|
||||||
);
|
);
|
||||||
@@ -118,15 +115,17 @@ class _LiveTrafficScreenState extends State<LiveTrafficScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final logs = widget.logReader();
|
||||||
|
final totalRxDataCount = LiveTrafficSummary.countRxDataLogs(logs);
|
||||||
final unfilteredSnapshot = LiveTrafficSummary.fromLogs(
|
final unfilteredSnapshot = LiveTrafficSummary.fromLogs(
|
||||||
widget.logReader(),
|
logs,
|
||||||
now: widget.now(),
|
now: widget.now(),
|
||||||
clearedAt: _clearedAt,
|
clearedAt: _clearedAt,
|
||||||
preferredHashSize: _preferredHashSize,
|
preferredHashSize: _preferredHashSize,
|
||||||
window: _selectedWindow,
|
window: _selectedWindow,
|
||||||
);
|
);
|
||||||
final snapshot = LiveTrafficSummary.fromLogs(
|
final snapshot = LiveTrafficSummary.fromLogs(
|
||||||
widget.logReader(),
|
logs,
|
||||||
now: widget.now(),
|
now: widget.now(),
|
||||||
clearedAt: _clearedAt,
|
clearedAt: _clearedAt,
|
||||||
preferredHashSize: _preferredHashSize,
|
preferredHashSize: _preferredHashSize,
|
||||||
@@ -177,7 +176,7 @@ class _LiveTrafficScreenState extends State<LiveTrafficScreen> {
|
|||||||
children: [
|
children: [
|
||||||
_SummaryPanel(
|
_SummaryPanel(
|
||||||
snapshot: snapshot,
|
snapshot: snapshot,
|
||||||
totalRxCount: widget.rxCountReader?.call(),
|
totalRxCount: totalRxDataCount,
|
||||||
routeHashCounts: routeHashCounts,
|
routeHashCounts: routeHashCounts,
|
||||||
onWindowTap: () => _showWindowPicker(context),
|
onWindowTap: () => _showWindowPicker(context),
|
||||||
),
|
),
|
||||||
@@ -306,8 +305,48 @@ class _SummaryPanel extends StatelessWidget {
|
|||||||
LiveTrafficBusyness.active => 'Active',
|
LiveTrafficBusyness.active => 'Active',
|
||||||
LiveTrafficBusyness.busy => 'Busy',
|
LiveTrafficBusyness.busy => 'Busy',
|
||||||
};
|
};
|
||||||
|
final metrics = [
|
||||||
|
_MetricTile(
|
||||||
|
tileKey: const ValueKey('liveTrafficMetric:rxPackets'),
|
||||||
|
label: AppLocalizations.of(context)!.rxPackets,
|
||||||
|
value: '${snapshot.rxCount}',
|
||||||
|
subtitle: totalRxCount == null
|
||||||
|
? _windowSummaryLabel(snapshot.windowDuration)
|
||||||
|
: 'Device total $totalRxCount',
|
||||||
|
),
|
||||||
|
_MetricTile(
|
||||||
|
tileKey: const ValueKey('liveTrafficMetric:rssi'),
|
||||||
|
label: AppLocalizations.of(context)!.rssi,
|
||||||
|
value: snapshot.latestRssiDbm == null
|
||||||
|
? 'No RX data'
|
||||||
|
: '${snapshot.latestRssiDbm} dBm',
|
||||||
|
subtitle: snapshot.avgRssiDbm == null
|
||||||
|
? 'No average yet'
|
||||||
|
: 'Avg ${snapshot.avgRssiDbm!.toStringAsFixed(1)} dBm',
|
||||||
|
),
|
||||||
|
_MetricTile(
|
||||||
|
tileKey: const ValueKey('liveTrafficMetric:snr'),
|
||||||
|
label: AppLocalizations.of(context)!.snr,
|
||||||
|
value: snapshot.latestSnrDb == null
|
||||||
|
? 'No RX data'
|
||||||
|
: '${snapshot.latestSnrDb!.toStringAsFixed(1)} dB',
|
||||||
|
subtitle: snapshot.avgSnrDb == null
|
||||||
|
? 'No average yet'
|
||||||
|
: 'Avg ${snapshot.avgSnrDb!.toStringAsFixed(1)} dB',
|
||||||
|
),
|
||||||
|
_MetricTile(
|
||||||
|
tileKey: const ValueKey('liveTrafficMetric:multihop'),
|
||||||
|
label: AppLocalizations.of(context)!.multihop,
|
||||||
|
value: '${snapshot.multiHopCount}',
|
||||||
|
subtitle: routeHashCounts.summaryLabel,
|
||||||
|
footer: snapshot.avgHopCount == null
|
||||||
|
? 'No routes yet'
|
||||||
|
: 'Avg ${snapshot.avgHopCount!.toStringAsFixed(1)} hops',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
@@ -346,44 +385,26 @@ class _SummaryPanel extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
Wrap(
|
LayoutBuilder(
|
||||||
spacing: 10,
|
builder: (context, constraints) {
|
||||||
runSpacing: 10,
|
const spacing = 10.0;
|
||||||
children: [
|
const minTileWidth = 150.0;
|
||||||
_MetricTile(
|
final columns =
|
||||||
label: AppLocalizations.of(context)!.rxPackets,
|
((constraints.maxWidth + spacing) / (minTileWidth + spacing))
|
||||||
value: '${snapshot.rxCount}',
|
.floor()
|
||||||
subtitle: totalRxCount == null
|
.clamp(1, metrics.length);
|
||||||
? _windowSummaryLabel(snapshot.windowDuration)
|
final tileWidth =
|
||||||
: 'Device total $totalRxCount',
|
(constraints.maxWidth - (spacing * (columns - 1))) / columns;
|
||||||
),
|
|
||||||
_MetricTile(
|
return Wrap(
|
||||||
label: AppLocalizations.of(context)!.rssi,
|
spacing: spacing,
|
||||||
value: snapshot.latestRssiDbm == null
|
runSpacing: spacing,
|
||||||
? 'No RX data'
|
children: [
|
||||||
: '${snapshot.latestRssiDbm} dBm',
|
for (final metric in metrics)
|
||||||
subtitle: snapshot.avgRssiDbm == null
|
SizedBox(width: tileWidth, child: metric),
|
||||||
? 'No average yet'
|
],
|
||||||
: 'Avg ${snapshot.avgRssiDbm!.toStringAsFixed(1)} dBm',
|
);
|
||||||
),
|
},
|
||||||
_MetricTile(
|
|
||||||
label: AppLocalizations.of(context)!.snr,
|
|
||||||
value: snapshot.latestSnrDb == null
|
|
||||||
? 'No RX data'
|
|
||||||
: '${snapshot.latestSnrDb!.toStringAsFixed(1)} dB',
|
|
||||||
subtitle: snapshot.avgSnrDb == null
|
|
||||||
? 'No average yet'
|
|
||||||
: 'Avg ${snapshot.avgSnrDb!.toStringAsFixed(1)} dB',
|
|
||||||
),
|
|
||||||
_MetricTile(
|
|
||||||
label: AppLocalizations.of(context)!.multihop,
|
|
||||||
value: '${snapshot.multiHopCount}',
|
|
||||||
subtitle: routeHashCounts.summaryLabel,
|
|
||||||
footer: snapshot.avgHopCount == null
|
|
||||||
? 'No routes yet'
|
|
||||||
: 'Avg ${snapshot.avgHopCount!.toStringAsFixed(1)} hops',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -525,12 +546,14 @@ String _windowSummaryLabel(Duration duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MetricTile extends StatelessWidget {
|
class _MetricTile extends StatelessWidget {
|
||||||
|
final Key? tileKey;
|
||||||
final String label;
|
final String label;
|
||||||
final String value;
|
final String value;
|
||||||
final String subtitle;
|
final String subtitle;
|
||||||
final String? footer;
|
final String? footer;
|
||||||
|
|
||||||
const _MetricTile({
|
const _MetricTile({
|
||||||
|
this.tileKey,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
@@ -540,7 +563,7 @@ class _MetricTile extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
width: 160,
|
key: tileKey,
|
||||||
height: 136,
|
height: 136,
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -949,7 +972,6 @@ class _LiveTrafficCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LiveTrafficPacketDetails {
|
class _LiveTrafficPacketDetails {
|
||||||
@@ -1105,7 +1127,6 @@ class _PacketInfoLine extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _GeoPoint {
|
class _GeoPoint {
|
||||||
final double latitude;
|
final double latitude;
|
||||||
final double longitude;
|
final double longitude;
|
||||||
|
|||||||
@@ -141,6 +141,18 @@ class LiveTrafficSummary {
|
|||||||
|
|
||||||
const LiveTrafficSummary._();
|
const LiveTrafficSummary._();
|
||||||
|
|
||||||
|
static bool isRxDataLog(BlePacketLog log) {
|
||||||
|
return log.direction == PacketDirection.rx &&
|
||||||
|
log.responseCode == logRxDataResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int countRxDataLogs(Iterable<BlePacketLog> logs, {DateTime? since}) {
|
||||||
|
return logs.where((log) {
|
||||||
|
return isRxDataLog(log) &&
|
||||||
|
(since == null || !log.timestamp.isBefore(since));
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
static LiveTrafficSnapshot fromLogs(
|
static LiveTrafficSnapshot fromLogs(
|
||||||
Iterable<BlePacketLog> logs, {
|
Iterable<BlePacketLog> logs, {
|
||||||
required DateTime now,
|
required DateTime now,
|
||||||
@@ -154,15 +166,14 @@ class LiveTrafficSummary {
|
|||||||
? clearedAt
|
? clearedAt
|
||||||
: windowStart;
|
: windowStart;
|
||||||
|
|
||||||
final recentLogs = logs
|
final recentLogs =
|
||||||
.where(
|
logs
|
||||||
(log) =>
|
.where(
|
||||||
log.direction == PacketDirection.rx &&
|
(log) =>
|
||||||
log.responseCode == logRxDataResponseCode &&
|
isRxDataLog(log) && !log.timestamp.isBefore(effectiveStart),
|
||||||
!log.timestamp.isBefore(effectiveStart),
|
)
|
||||||
)
|
.toList()
|
||||||
.toList()
|
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||||
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
|
||||||
|
|
||||||
final entries = <LiveTrafficEntry>[];
|
final entries = <LiveTrafficEntry>[];
|
||||||
for (final log in recentLogs) {
|
for (final log in recentLogs) {
|
||||||
@@ -215,7 +226,9 @@ class LiveTrafficSummary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final visibleEntries = filteredEntries.reversed.take(maxVisibleEntries).toList();
|
final visibleEntries = filteredEntries.reversed
|
||||||
|
.take(maxVisibleEntries)
|
||||||
|
.toList();
|
||||||
const txCount = 0;
|
const txCount = 0;
|
||||||
final totalCount = rxCount;
|
final totalCount = rxCount;
|
||||||
final packetsPerMinute =
|
final packetsPerMinute =
|
||||||
@@ -233,7 +246,9 @@ class LiveTrafficSummary {
|
|||||||
avgRssiDbm: rssiCount == 0 ? null : rssiSum / rssiCount,
|
avgRssiDbm: rssiCount == 0 ? null : rssiSum / rssiCount,
|
||||||
latestRssiDbm: latestRssiDbm,
|
latestRssiDbm: latestRssiDbm,
|
||||||
multiHopCount: multiHopCount,
|
multiHopCount: multiHopCount,
|
||||||
avgHopCount: hopCountSamples == 0 ? null : hopCountTotal / hopCountSamples,
|
avgHopCount: hopCountSamples == 0
|
||||||
|
? null
|
||||||
|
: hopCountTotal / hopCountSamples,
|
||||||
visibleEntries: visibleEntries,
|
visibleEntries: visibleEntries,
|
||||||
busyness: _busynessForPacketsPerMinute(packetsPerMinute),
|
busyness: _busynessForPacketsPerMinute(packetsPerMinute),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:meshcore_sar_app/models/ble_packet_log.dart';
|
import 'package:meshcore_sar_app/models/ble_packet_log.dart';
|
||||||
|
import 'package:meshcore_sar_app/l10n/app_localizations.dart';
|
||||||
import 'package:meshcore_sar_app/screens/live_traffic_screen.dart';
|
import 'package:meshcore_sar_app/screens/live_traffic_screen.dart';
|
||||||
|
|
||||||
BlePacketLog _log({
|
BlePacketLog _log({
|
||||||
@@ -50,6 +51,14 @@ List<int> _multiHopRaw({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _testApp(Widget child) {
|
||||||
|
return MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('shows empty state before traffic arrives', (tester) async {
|
testWidgets('shows empty state before traffic arrives', (tester) async {
|
||||||
final logs = <BlePacketLog>[];
|
final logs = <BlePacketLog>[];
|
||||||
@@ -57,8 +66,8 @@ void main() {
|
|||||||
DateTime now = DateTime(2026, 3, 12, 12, 0, 0);
|
DateTime now = DateTime(2026, 3, 12, 12, 0, 0);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
_testApp(
|
||||||
home: LiveTrafficScreen(
|
LiveTrafficScreen(
|
||||||
logReader: () => logs,
|
logReader: () => logs,
|
||||||
refreshListenable: refresh,
|
refreshListenable: refresh,
|
||||||
now: () => now,
|
now: () => now,
|
||||||
@@ -78,10 +87,9 @@ void main() {
|
|||||||
DateTime now = DateTime(2026, 3, 12, 12, 0, 0);
|
DateTime now = DateTime(2026, 3, 12, 12, 0, 0);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
_testApp(
|
||||||
home: LiveTrafficScreen(
|
LiveTrafficScreen(
|
||||||
logReader: () => logs,
|
logReader: () => logs,
|
||||||
rxCountReader: () => 7,
|
|
||||||
refreshListenable: refresh,
|
refreshListenable: refresh,
|
||||||
now: () => now,
|
now: () => now,
|
||||||
),
|
),
|
||||||
@@ -114,10 +122,9 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
expect(find.text('1 pkt/min'), findsOneWidget);
|
expect(find.text('1 pkt/min'), findsOneWidget);
|
||||||
expect(find.text('Device total 7'), findsOneWidget);
|
expect(find.text('Device total 1'), findsOneWidget);
|
||||||
expect(find.text('FLOOD RESPONSE'), findsOneWidget);
|
expect(find.text('FLOOD RESPONSE'), findsOneWidget);
|
||||||
expect(find.text('MULTI-HOP'), findsOneWidget);
|
expect(find.text('-84 dBm'), findsOneWidget);
|
||||||
expect(find.textContaining('RSSI -84 dBm'), findsOneWidget);
|
|
||||||
expect(find.textContaining('Hash:'), findsOneWidget);
|
expect(find.textContaining('Hash:'), findsOneWidget);
|
||||||
expect(
|
expect(
|
||||||
find.textContaining('Path: 3 hops [c010,6301,68d9]'),
|
find.textContaining('Path: 3 hops [c010,6301,68d9]'),
|
||||||
@@ -143,8 +150,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
_testApp(
|
||||||
home: LiveTrafficScreen(
|
LiveTrafficScreen(
|
||||||
logReader: () => logs,
|
logReader: () => logs,
|
||||||
refreshListenable: refresh,
|
refreshListenable: refresh,
|
||||||
now: () => now,
|
now: () => now,
|
||||||
@@ -152,7 +159,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(find.text('MULTI-HOP'), findsOneWidget);
|
expect(find.text('FLOOD RESPONSE'), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.byTooltip('Clear live view'));
|
await tester.tap(find.byTooltip('Clear live view'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@@ -182,4 +189,43 @@ void main() {
|
|||||||
expect(find.text('No packets for this filter'), findsNothing);
|
expect(find.text('No packets for this filter'), findsNothing);
|
||||||
expect(find.textContaining('Size: 3 bytes'), findsOneWidget);
|
expect(find.textContaining('Size: 3 bytes'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('summary metrics expand across wide layouts', (tester) async {
|
||||||
|
tester.view.physicalSize = const Size(1200, 900);
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.reset);
|
||||||
|
|
||||||
|
final now = DateTime(2026, 3, 12, 12, 0, 0);
|
||||||
|
final logs = [
|
||||||
|
_log(
|
||||||
|
timestamp: now.subtract(const Duration(seconds: 10)),
|
||||||
|
direction: PacketDirection.rx,
|
||||||
|
rawData: _multiHopRaw(hops: [0xC0, 0x10, 0x63, 0x01]),
|
||||||
|
responseCode: 0x88,
|
||||||
|
snrDb: 9.5,
|
||||||
|
rssiDbm: -82,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
_testApp(LiveTrafficScreen(logReader: () => logs, now: () => now)),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.getSize(
|
||||||
|
find.byKey(const ValueKey('liveTrafficMetric:rxPackets')),
|
||||||
|
)
|
||||||
|
.width >
|
||||||
|
200,
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.getSize(find.byKey(const ValueKey('liveTrafficMetric:multihop')))
|
||||||
|
.width >
|
||||||
|
200,
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,23 +146,55 @@ void main() {
|
|||||||
test('supports clearing the live view without mutating source logs', () {
|
test('supports clearing the live view without mutating source logs', () {
|
||||||
final now = DateTime(2026, 3, 12, 12, 0, 0);
|
final now = DateTime(2026, 3, 12, 12, 0, 0);
|
||||||
final clearAt = now.subtract(const Duration(seconds: 8));
|
final clearAt = now.subtract(const Duration(seconds: 8));
|
||||||
final snapshot = LiveTrafficSummary.fromLogs([
|
final snapshot = LiveTrafficSummary.fromLogs(
|
||||||
_log(
|
[
|
||||||
timestamp: now.subtract(const Duration(seconds: 10)),
|
_log(
|
||||||
direction: PacketDirection.rx,
|
timestamp: now.subtract(const Duration(seconds: 10)),
|
||||||
rawData: [0x88, 0x00, 0x00],
|
direction: PacketDirection.rx,
|
||||||
responseCode: 0x88,
|
rawData: [0x88, 0x00, 0x00],
|
||||||
),
|
responseCode: 0x88,
|
||||||
_log(
|
),
|
||||||
timestamp: now.subtract(const Duration(seconds: 4)),
|
_log(
|
||||||
direction: PacketDirection.rx,
|
timestamp: now.subtract(const Duration(seconds: 4)),
|
||||||
rawData: [0x88, 0x00, 0x00],
|
direction: PacketDirection.rx,
|
||||||
responseCode: 0x88,
|
rawData: [0x88, 0x00, 0x00],
|
||||||
),
|
responseCode: 0x88,
|
||||||
], now: now, clearedAt: clearAt);
|
),
|
||||||
|
],
|
||||||
|
now: now,
|
||||||
|
clearedAt: clearAt,
|
||||||
|
);
|
||||||
|
|
||||||
expect(snapshot.totalCount, 1);
|
expect(snapshot.totalCount, 1);
|
||||||
expect(snapshot.visibleEntries, hasLength(1));
|
expect(snapshot.visibleEntries, hasLength(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('counts only RX data packets for device totals', () {
|
||||||
|
final now = DateTime(2026, 3, 12, 12, 0, 0);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
LiveTrafficSummary.countRxDataLogs([
|
||||||
|
_log(
|
||||||
|
timestamp: now.subtract(const Duration(seconds: 30)),
|
||||||
|
direction: PacketDirection.rx,
|
||||||
|
rawData: [0x88, 0x00, 0x00],
|
||||||
|
responseCode: 0x88,
|
||||||
|
),
|
||||||
|
_log(
|
||||||
|
timestamp: now.subtract(const Duration(seconds: 20)),
|
||||||
|
direction: PacketDirection.tx,
|
||||||
|
rawData: [0x88, 0x00, 0x00],
|
||||||
|
responseCode: 0x88,
|
||||||
|
),
|
||||||
|
_log(
|
||||||
|
timestamp: now.subtract(const Duration(seconds: 10)),
|
||||||
|
direction: PacketDirection.rx,
|
||||||
|
rawData: [0x05, 0x01, 0x02],
|
||||||
|
responseCode: 0x05,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user