feat: Enhance home screen status bar with connection indicators and improved layout

This commit is contained in:
Janez T
2025-10-16 14:54:25 +02:00
parent 4c8e0e5d61
commit 3a8eac2ec2

View File

@@ -579,184 +579,169 @@ class _HomeScreenState extends State<HomeScreen>
'🎨 [UI] Building status bar - isConnected: $isConnected, state: ${deviceInfo.connectionState}', '🎨 [UI] Building status bar - isConnected: $isConnected, state: ${deviceInfo.connectionState}',
); );
if (!isConnected) {
// Disconnected state: show connect button
return Row(
children: [
const Expanded(
child: Text(
'MeshCore',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
ElevatedButton.icon(
onPressed: provider.isReconnecting
? null
: () => _showConnectionDialog(context),
icon: provider.isReconnecting
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.black54,
),
),
)
: const Icon(Icons.bluetooth, size: 18),
label: Text(
provider.isReconnecting
? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}'
: 'Connect',
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
if (provider.isReconnecting) ...[
const SizedBox(width: 8),
IconButton(
onPressed: () => provider.cancelReconnection(),
icon: const Icon(Icons.close, size: 20),
tooltip: 'Cancel reconnection',
style: IconButton.styleFrom(
backgroundColor: Colors.red.shade700,
foregroundColor: Colors.white,
padding: const EdgeInsets.all(8),
),
),
],
],
);
}
// Connected state: LEFT | CENTER | RIGHT layout
return Row( return Row(
children: [ children: [
// LEFT SECTION: Name + BT/Battery status + Settings cog // LEFT: Name + BT/Battery + Cog
Expanded( Expanded(
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Name and status group
Flexible( Flexible(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
isConnected && deviceInfo.selfName != null deviceInfo.selfName ?? 'MeshCore',
? deviceInfo.selfName! style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
: 'MeshCore',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
if (isConnected) Row(
Row( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Icon(
// BLE connection strength indicator with RSSI Icons.bluetooth_connected,
color: deviceInfo.signalRssi != null
? _getSignalColor(deviceInfo.signalRssi!)
: Colors.grey,
size: 13,
),
if (deviceInfo.signalRssi != null) ...[
const SizedBox(width: 3),
Text(
'${deviceInfo.signalRssi}',
style: TextStyle(
fontSize: 11,
color: _getSignalColor(deviceInfo.signalRssi!),
),
),
],
if (deviceInfo.batteryPercent != null) ...[
const SizedBox(width: 8),
Icon( Icon(
Icons.bluetooth_connected, _getBatteryIcon(deviceInfo.batteryPercent!),
color: deviceInfo.signalRssi != null color: _getBatteryColor(deviceInfo.batteryPercent!),
? _getSignalColor(deviceInfo.signalRssi!) size: 13,
: Colors.grey,
size: 14,
), ),
const SizedBox(width: 3), const SizedBox(width: 3),
if (deviceInfo.signalRssi != null) Text(
Flexible( '${deviceInfo.batteryPercent!.round()}%',
child: Text( style: TextStyle(
'${deviceInfo.signalRssi}', fontSize: 11,
style: TextStyle(
fontSize: 12,
color: _getSignalColor(deviceInfo.signalRssi!),
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
// Battery indicator
if (deviceInfo.batteryPercent != null) ...[
Icon(
_getBatteryIcon(deviceInfo.batteryPercent!),
color: _getBatteryColor(deviceInfo.batteryPercent!), color: _getBatteryColor(deviceInfo.batteryPercent!),
size: 14,
), ),
const SizedBox(width: 3), ),
Flexible(
child: Text(
'${deviceInfo.batteryPercent!.round()}%',
style: TextStyle(
fontSize: 12,
color: _getBatteryColor(
deviceInfo.batteryPercent!,
),
),
overflow: TextOverflow.ellipsis,
),
),
],
], ],
) ],
else if (provider.isReconnecting) ),
Text(
'Reconnecting...',
style: TextStyle(fontSize: 12, color: Colors.orange[600]),
overflow: TextOverflow.ellipsis,
),
], ],
), ),
), ),
// Settings cog - part of left group const SizedBox(width: 8),
if (isConnected) ...[ GestureDetector(
const SizedBox(width: 8), onTap: () {
GestureDetector( Navigator.push(
onTap: () { context,
Navigator.push( MaterialPageRoute(
context, builder: (context) => const DeviceConfigScreen(),
MaterialPageRoute( ),
builder: (context) => const DeviceConfigScreen(), );
), },
); onLongPress: () {
}, Navigator.push(
onLongPress: () { context,
Navigator.push( MaterialPageRoute(
context, builder: (context) =>
MaterialPageRoute( PacketLogScreen(bleService: provider.bleService),
builder: (context) => ),
PacketLogScreen(bleService: provider.bleService), );
), },
); child: Container(
}, width: 32,
child: Container( height: 32,
width: 36, alignment: Alignment.center,
height: 36, child: const Icon(Icons.settings, size: 18),
alignment: Alignment.center,
child: const Icon(Icons.settings, size: 20),
),
), ),
], ),
], ],
), ),
), ),
// CENTER SECTION: Broadcast button (when connected) // CENTER: Broadcast button
if (isConnected) ...[ const SizedBox(width: 8),
FilledButton( FilledButton(
onPressed: () => _advertiseDevice(context), onPressed: () => _advertiseDevice(context),
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
backgroundColor: Colors.blue.shade700, backgroundColor: Colors.blue.shade700,
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
minimumSize: const Size(40, 40), minimumSize: const Size(40, 40),
shape: const CircleBorder(), shape: const CircleBorder(),
),
child: const Icon(Icons.campaign, size: 20),
), ),
], child: const Icon(Icons.campaign, size: 20),
),
const SizedBox(width: 8),
// RIGHT SECTION: Connect button OR RX/TX indicators // RIGHT: RX/TX indicators
if (!isConnected) if (_showRxTxIndicators)
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: provider.isReconnecting
? null // Disable button during reconnection
: () => _showConnectionDialog(context),
icon: provider.isReconnecting
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.black54,
),
),
)
: const Icon(Icons.bluetooth, size: 18),
label: Text(
provider.isReconnecting
? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}'
: 'Connect',
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
// Cancel button during reconnection
if (provider.isReconnecting) ...[
const SizedBox(width: 8),
IconButton(
onPressed: () => provider.cancelReconnection(),
icon: const Icon(Icons.close, size: 20),
tooltip: 'Cancel reconnection',
style: IconButton.styleFrom(
backgroundColor: Colors.red.shade700,
foregroundColor: Colors.white,
padding: const EdgeInsets.all(8),
),
),
],
],
)
else if (_showRxTxIndicators)
GestureDetector( GestureDetector(
onLongPress: () { onLongPress: () {
Navigator.push( Navigator.push(
@@ -769,15 +754,14 @@ class _HomeScreenState extends State<HomeScreen>
}, },
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
// RX indicator
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
width: 8, width: 7,
height: 8, height: 7,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: provider.rxActivity color: provider.rxActivity
@@ -785,24 +769,23 @@ class _HomeScreenState extends State<HomeScreen>
: Colors.grey.withOpacity(0.3), : Colors.grey.withOpacity(0.3),
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 3),
Text( Text(
'RX:${provider.rxPacketCount}', 'RX:${provider.rxPacketCount}',
style: const TextStyle( style: const TextStyle(
fontSize: 11, fontSize: 10,
color: Colors.grey, color: Colors.grey,
), ),
), ),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 3),
// TX indicator
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
width: 8, width: 7,
height: 8, height: 7,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: provider.txActivity color: provider.txActivity
@@ -810,11 +793,11 @@ class _HomeScreenState extends State<HomeScreen>
: Colors.grey.withOpacity(0.3), : Colors.grey.withOpacity(0.3),
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 3),
Text( Text(
'TX:${provider.txPacketCount}', 'TX:${provider.txPacketCount}',
style: const TextStyle( style: const TextStyle(
fontSize: 11, fontSize: 10,
color: Colors.grey, color: Colors.grey,
), ),
), ),
@@ -822,7 +805,9 @@ class _HomeScreenState extends State<HomeScreen>
), ),
], ],
), ),
), )
else
const SizedBox(width: 52), // Placeholder to maintain layout balance
], ],
); );
}, },