mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-21 08:11:06 +10:00
Add SNRIndicator to AppBar and refactor BatteryIndicator layout
This commit is contained in:
@@ -3,6 +3,8 @@ import 'package:meshcore_open/connector/meshcore_connector.dart';
|
|||||||
import 'package:meshcore_open/widgets/battery_indicator.dart';
|
import 'package:meshcore_open/widgets/battery_indicator.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'snr_indicator.dart';
|
||||||
|
|
||||||
class AppBarTitle extends StatelessWidget {
|
class AppBarTitle extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final TextStyle? style;
|
final TextStyle? style;
|
||||||
@@ -38,7 +40,13 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
BatteryIndicator(connector: connector),
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
BatteryIndicator(connector: connector),
|
||||||
|
SNRIndicator(connector: connector),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (trailing != null) trailing!,
|
if (trailing != null) trailing!,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:meshcore_open/widgets/snr_indicator.dart';
|
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
|
|
||||||
@@ -43,7 +42,6 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final percent = widget.connector.batteryPercent;
|
final percent = widget.connector.batteryPercent;
|
||||||
final millivolts = widget.connector.batteryMillivolts;
|
final millivolts = widget.connector.batteryMillivolts;
|
||||||
final directRepeaters = widget.connector.directRepeaters;
|
|
||||||
|
|
||||||
if (millivolts == null) {
|
if (millivolts == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -57,20 +55,6 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final batteryUi = batteryUiForPercent(percent);
|
final batteryUi = batteryUiForPercent(percent);
|
||||||
final directBestRepeaters = List.of(directRepeaters)
|
|
||||||
..sort((a, b) {
|
|
||||||
final dateCompare = b.lastUpdated.compareTo(a.lastUpdated);
|
|
||||||
if (dateCompare != 0) return dateCompare;
|
|
||||||
return (b.snr).compareTo(a.snr);
|
|
||||||
});
|
|
||||||
final directRepeater = directBestRepeaters.isEmpty
|
|
||||||
? null
|
|
||||||
: directBestRepeaters.first;
|
|
||||||
|
|
||||||
final snrUi = snrUiFromSNR(
|
|
||||||
directBestRepeaters.isNotEmpty ? directRepeater!.snr : null,
|
|
||||||
widget.connector.currentSf,
|
|
||||||
);
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -103,57 +87,9 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 2),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(snrUi.icon, size: 18, color: snrUi.color),
|
|
||||||
Text(
|
|
||||||
snrUi.text,
|
|
||||||
style: TextStyle(fontSize: 12, color: snrUi.color),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (directRepeater != null)
|
|
||||||
Text(
|
|
||||||
'${directRepeaters.length}: ${directRepeater.pubkeyFirstByte.toRadixString(16).padLeft(2, '0')}: ${_formatLastUpdated(directRepeater.lastUpdated)}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatLastUpdated(DateTime lastSeen) {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final diff = now.difference(lastSeen);
|
|
||||||
|
|
||||||
if (diff.isNegative || diff.inMinutes < 1) {
|
|
||||||
return "${diff.inSeconds}s";
|
|
||||||
}
|
|
||||||
if (diff.inMinutes < 60) {
|
|
||||||
return "${diff.inMinutes}m";
|
|
||||||
}
|
|
||||||
if (diff.inHours < 24) {
|
|
||||||
final hours = diff.inHours;
|
|
||||||
return hours == 1 ? "1h" : "${hours}hs";
|
|
||||||
}
|
|
||||||
final days = diff.inDays;
|
|
||||||
return days == 1 ? "1d" : "${days}ds";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../connector/meshcore_connector.dart';
|
||||||
|
import '../l10n/l10n.dart';
|
||||||
|
|
||||||
class SNRUi {
|
class SNRUi {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
@@ -56,3 +60,141 @@ SNRUi snrUiFromSNR(double? snr, int? spreadingFactor) {
|
|||||||
|
|
||||||
return SNRUi(icon, color, text);
|
return SNRUi(icon, color, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SNRIndicator extends StatefulWidget {
|
||||||
|
final MeshCoreConnector connector;
|
||||||
|
|
||||||
|
const SNRIndicator({super.key, required this.connector});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SNRIndicator> createState() => _SNRIndicatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SNRIndicatorState extends State<SNRIndicator> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final directRepeaters = widget.connector.directRepeaters;
|
||||||
|
final directBestRepeaters = List.of(directRepeaters)
|
||||||
|
..sort((a, b) => (b.snr).compareTo(a.snr));
|
||||||
|
final directRepeater = directBestRepeaters.isEmpty
|
||||||
|
? null
|
||||||
|
: directBestRepeaters.first;
|
||||||
|
|
||||||
|
final snrUi = snrUiFromSNR(
|
||||||
|
directBestRepeaters.isNotEmpty ? directRepeater!.snr : null,
|
||||||
|
widget.connector.currentSf,
|
||||||
|
);
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if (directRepeater != null) {
|
||||||
|
_showFullPathDialog(context, directBestRepeaters);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(snrUi.icon, size: 18, color: snrUi.color),
|
||||||
|
Text(
|
||||||
|
snrUi.text,
|
||||||
|
style: TextStyle(fontSize: 12, color: snrUi.color),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (directRepeater != null)
|
||||||
|
Text(
|
||||||
|
'${directRepeaters.length}: ${directRepeater.pubkeyFirstByte.toRadixString(16).padLeft(2, '0')}: ${_formatLastUpdated(directRepeater.lastUpdated)}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatLastUpdated(DateTime lastSeen) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final diff = now.difference(lastSeen);
|
||||||
|
|
||||||
|
if (diff.isNegative || diff.inMinutes < 1) {
|
||||||
|
return "${diff.inSeconds}s";
|
||||||
|
}
|
||||||
|
if (diff.inMinutes < 60) {
|
||||||
|
return "${diff.inMinutes}m";
|
||||||
|
}
|
||||||
|
if (diff.inHours < 24) {
|
||||||
|
final hours = diff.inHours;
|
||||||
|
return hours == 1 ? "1h" : "${hours}hs";
|
||||||
|
}
|
||||||
|
final days = diff.inDays;
|
||||||
|
return days == 1 ? "1d" : "${days}ds";
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showFullPathDialog(
|
||||||
|
BuildContext context,
|
||||||
|
List<DirectRepeater> directBestRepeaters,
|
||||||
|
) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text("Nearby Repeaters"),
|
||||||
|
content: Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
child: ListView.separated(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
itemCount: directBestRepeaters.length,
|
||||||
|
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final repeater = directBestRepeaters[index];
|
||||||
|
final snrUi = snrUiFromSNR(
|
||||||
|
repeater.snr,
|
||||||
|
widget.connector.currentSf,
|
||||||
|
);
|
||||||
|
|
||||||
|
final name = widget.connector.contacts
|
||||||
|
.where((c) => c.publicKey.first == repeater.pubkeyFirstByte)
|
||||||
|
.map((c) => c.name)
|
||||||
|
.firstOrNull;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(snrUi.icon, color: snrUi.color),
|
||||||
|
title: Text(
|
||||||
|
name ??
|
||||||
|
'${repeater.pubkeyFirstByte.toRadixString(16).padLeft(2, '0')}',
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
'SNR: ${repeater.snr.toStringAsFixed(1)} dB\nLast seen: ${_formatLastUpdated(repeater.lastUpdated)}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(l10n.common_close),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+8
-8
@@ -69,10 +69,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -497,18 +497,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.18"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -910,10 +910,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.9"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user