From 36af248069555731f93be0fc1b7e78c63cb13735 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Mon, 19 Jan 2026 11:10:48 +0100 Subject: [PATCH] Show more talkgroup details --- lib/views/device_detail_view.dart | 189 +++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 42 deletions(-) diff --git a/lib/views/device_detail_view.dart b/lib/views/device_detail_view.dart index e57014e..b7beeaf 100644 --- a/lib/views/device_detail_view.dart +++ b/lib/views/device_detail_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/device.dart'; import '../models/static_talkgroup.dart'; +import '../models/device_profile.dart'; import '../services/authentication_manager.dart'; import '../services/brandmeister_client.dart'; import 'link_talkgroup_view.dart'; @@ -18,6 +19,7 @@ class DeviceDetailView extends StatefulWidget { class _DeviceDetailViewState extends State { List _talkgroups = []; Map _allTalkgroups = {}; + DeviceProfile? _deviceProfile; bool _isLoadingTalkgroups = false; bool _isLoadingDeviceDetails = false; String? _errorMessage; @@ -41,11 +43,13 @@ class _DeviceDetailViewState extends State { final results = await Future.wait([ authManager.getTalkgroups(widget.device.id), authManager.getAllTalkgroups(), + authManager.getDeviceProfile(widget.device.id), ]); setState(() { _talkgroups = results[0] as List; _allTalkgroups = results[1] as Map; + _deviceProfile = results[2] as DeviceProfile; _isLoadingTalkgroups = false; _isLoadingDeviceDetails = false; }); @@ -234,6 +238,12 @@ class _DeviceDetailViewState extends State { } Widget _buildTalkgroupsSection() { + final hasAnyTalkgroups = _talkgroups.isNotEmpty || + (_deviceProfile?.staticSubscriptions.isNotEmpty ?? false) || + (_deviceProfile?.dynamicSubscriptions.isNotEmpty ?? false) || + (_deviceProfile?.timedSubscriptions.isNotEmpty ?? false) || + (_deviceProfile?.blockedGroups.isNotEmpty ?? false); + return Container( padding: const EdgeInsets.all(16), child: Column( @@ -243,7 +253,7 @@ class _DeviceDetailViewState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Static Talkgroups', + 'Talkgroup Subscriptions', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), @@ -257,7 +267,7 @@ class _DeviceDetailViewState extends State { ], ), const SizedBox(height: 16), - if (_talkgroups.isEmpty) + if (!hasAnyTalkgroups) Center( child: Padding( padding: const EdgeInsets.all(32), @@ -270,23 +280,112 @@ class _DeviceDetailViewState extends State { ), const SizedBox(height: 8), Text( - 'No talkgroups linked', + 'No talkgroups configured', style: TextStyle(color: Colors.grey[600]), ), ], ), ), ) - else - ..._talkgroups.map((tg) => _TalkgroupRow( - talkgroup: tg, - talkgroupName: _allTalkgroups[tg.talkgroup], - onDelete: () => _unlinkTalkgroup(tg), - )), + else ...[ + // Static Subscriptions + if (_deviceProfile?.staticSubscriptions.isNotEmpty ?? false) ...[ + _buildTalkgroupCategory( + 'Static', + _deviceProfile!.staticSubscriptions, + Colors.blue, + Icons.link, + canDelete: true, + ), + const SizedBox(height: 16), + ], + // Dynamic Subscriptions (Autostatic) + if (_deviceProfile?.dynamicSubscriptions.isNotEmpty ?? false) ...[ + _buildTalkgroupCategory( + 'Dynamic / Autostatic', + _deviceProfile!.dynamicSubscriptions, + Colors.green, + Icons.autorenew, + canDelete: false, + ), + const SizedBox(height: 16), + ], + // Timed Subscriptions + if (_deviceProfile?.timedSubscriptions.isNotEmpty ?? false) ...[ + _buildTalkgroupCategory( + 'Timed', + _deviceProfile!.timedSubscriptions, + Colors.orange, + Icons.schedule, + canDelete: false, + ), + const SizedBox(height: 16), + ], + // Blocked Groups + if (_deviceProfile?.blockedGroups.isNotEmpty ?? false) ...[ + _buildTalkgroupCategory( + 'Blocked', + _deviceProfile!.blockedGroups, + Colors.red, + Icons.block, + canDelete: false, + ), + ], + ], ], ), ); } + + Widget _buildTalkgroupCategory( + String title, + List talkgroups, + Color color, + IconData icon, + {bool canDelete = false} + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 20, color: color), + const SizedBox(width: 8), + Text( + title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: color, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '${talkgroups.length}', + style: TextStyle( + color: color, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + ...talkgroups.map((tg) => _TalkgroupRow( + talkgroup: tg, + talkgroupName: _allTalkgroups[tg.talkgroup], + onDelete: canDelete ? () => _unlinkTalkgroup(tg) : null, + categoryColor: color, + )), + ], + ); + } } class _InfoRow extends StatelessWidget { @@ -330,61 +429,67 @@ class _InfoRow extends StatelessWidget { class _TalkgroupRow extends StatelessWidget { final StaticTalkgroup talkgroup; final String? talkgroupName; - final VoidCallback onDelete; + final VoidCallback? onDelete; + final Color? categoryColor; const _TalkgroupRow({ required this.talkgroup, this.talkgroupName, - required this.onDelete, + this.onDelete, + this.categoryColor, }); @override Widget build(BuildContext context) { + final color = categoryColor ?? Theme.of(context).colorScheme.primary; + return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: CircleAvatar( - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + backgroundColor: color.withValues(alpha: 0.2), child: Text( talkgroup.displaySlot, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onSecondaryContainer, + color: color, ), ), ), title: Text(talkgroupName ?? talkgroup.displayId), subtitle: Text('ID: ${talkgroup.displayId}'), - trailing: IconButton( - icon: const Icon(Icons.delete_outline), - color: Colors.red, - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Unlink Talkgroup'), - content: Text( - 'Are you sure you want to unlink talkgroup ${talkgroup.displayId}?', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - Navigator.pop(context); - onDelete(); - }, - style: TextButton.styleFrom(foregroundColor: Colors.red), - child: const Text('Unlink'), - ), - ], - ), - ); - }, - ), + trailing: onDelete != null + ? IconButton( + icon: const Icon(Icons.delete_outline), + color: Colors.red, + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Unlink Talkgroup'), + content: Text( + 'Are you sure you want to unlink talkgroup ${talkgroup.displayId}?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + onDelete!(); + }, + style: TextButton.styleFrom(foregroundColor: Colors.red), + child: const Text('Unlink'), + ), + ], + ), + ); + }, + ) + : null, ), ); }