diff --git a/lib/models/last_heard_item.dart b/lib/models/last_heard_item.dart index 137e816..b851b86 100644 --- a/lib/models/last_heard_item.dart +++ b/lib/models/last_heard_item.dart @@ -119,9 +119,21 @@ class LastHeardItem { String get destinationDisplayName { if (destinationName != null && destinationName!.isNotEmpty) { - return '$destinationCall ($destinationName)'; + return 'TG $destinationID $destinationName'; + } + return 'TG $destinationID'; + } + + String get durationDisplay { + final durationSeconds = stop - start; + if (durationSeconds < 0) return ''; + if (durationSeconds < 60) { + return '${durationSeconds}s'; + } else { + final minutes = durationSeconds ~/ 60; + final seconds = durationSeconds % 60; + return seconds > 0 ? '${minutes}m ${seconds}s' : '${minutes}m'; } - return destinationCall; } DateTime get timestamp { diff --git a/lib/views/last_heard_view.dart b/lib/views/last_heard_view.dart index cb1f2e4..2606e18 100644 --- a/lib/views/last_heard_view.dart +++ b/lib/views/last_heard_view.dart @@ -3,8 +3,10 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/last_heard_item.dart'; +import '../models/user_info.dart'; import '../services/authentication_manager.dart'; import '../services/lastheard_websocket_client.dart'; +import '../widgets/user_header.dart'; class LastHeardView extends StatefulWidget { const LastHeardView({super.key}); @@ -19,6 +21,8 @@ class _LastHeardViewState extends State { final List _items = []; static const int _maxItems = 200; bool _isConnecting = true; + UserInfo? _userInfo; + String? _firstRadioId; @override void initState() { @@ -39,6 +43,7 @@ class _LastHeardViewState extends State { }); final authManager = context.read(); + final userInfo = authManager.userInfo; final devices = await authManager.getDevices(); final radioIds = devices.map((d) => d.id).toList(); @@ -52,6 +57,8 @@ class _LastHeardViewState extends State { }); setState(() { + _userInfo = userInfo; + _firstRadioId = devices.isNotEmpty ? devices.first.id.toString() : null; _isConnecting = false; }); } @@ -71,8 +78,10 @@ class _LastHeardViewState extends State { } setState(() { - // Add to the beginning of the list - _items.insert(0, item); + _items.add(item); + + // Sort by timestamp, most recent first + _items.sort((a, b) => b.start.compareTo(a.start)); // Keep only the latest 200 items if (_items.length > _maxItems) { @@ -136,9 +145,12 @@ class _LastHeardViewState extends State { } return ListView.builder( - itemCount: _items.length, + itemCount: _items.length + 1, itemBuilder: (context, index) { - final item = _items[index]; + if (index == 0) { + return UserHeader(userInfo: _userInfo, radioId: _firstRadioId); + } + final item = _items[index - 1]; return _LastHeardItemTile(item: item); }, ); @@ -150,123 +162,51 @@ class _LastHeardItemTile extends StatelessWidget { const _LastHeardItemTile({required this.item}); - Color _getEventColor(BuildContext context) { - switch (item.event) { - case 'Session-Start': - return Colors.green; - case 'Session-Stop': - return Colors.red; - default: - return Theme.of(context).colorScheme.primary; - } - } - - IconData _getEventIcon() { - switch (item.event) { - case 'Session-Start': - return Icons.phone_in_talk; - case 'Session-Stop': - return Icons.call_end; - default: - return Icons.settings_input_antenna; - } - } - @override Widget build(BuildContext context) { - final eventColor = _getEventColor(context); - return Card( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: ListTile( - leading: CircleAvatar( - backgroundColor: eventColor.withValues(alpha: 0.2), - child: Icon( - _getEventIcon(), - color: eventColor, - size: 20, - ), - ), - title: Row( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ + const Icon( + Icons.arrow_forward, + color: Colors.green, + size: 20, + ), + const SizedBox(width: 12), Expanded( - child: Text( - item.displayName, - style: const TextStyle(fontWeight: FontWeight.bold), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text( + item.destinationDisplayName, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + if (item.durationDisplay.isNotEmpty) ...[ + const SizedBox(width: 8), + Text( + item.durationDisplay, + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + ], + const Spacer(), + Text( + item.timeAgo, + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + ], + ), + ], ), ), - if (item.rssi != null) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.grey[200], - borderRadius: BorderRadius.circular(4), - ), - child: Text( - '${item.rssi} dBm', - style: TextStyle( - fontSize: 11, - color: Colors.grey[700], - ), - ), - ), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Row( - children: [ - Icon(Icons.arrow_forward, size: 12, color: Colors.grey[600]), - const SizedBox(width: 4), - Expanded( - child: Text( - item.destinationDisplayName, - style: TextStyle(color: Colors.grey[700]), - ), - ), - ], - ), - const SizedBox(height: 2), - Row( - children: [ - Container( - padding: - const EdgeInsets.symmetric(horizontal: 4, vertical: 1), - decoration: BoxDecoration( - color: eventColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(3), - ), - child: Text( - 'TS${item.slot}', - style: TextStyle( - fontSize: 10, - color: eventColor, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(width: 6), - Text( - item.linkTypeName, - style: TextStyle(fontSize: 11, color: Colors.grey[600]), - ), - const SizedBox(width: 6), - Text( - '•', - style: TextStyle(color: Colors.grey[400]), - ), - const SizedBox(width: 6), - Text( - item.timeAgo, - style: TextStyle(fontSize: 11, color: Colors.grey[600]), - ), - ], - ), ], ), - isThreeLine: true, ), ); }