Show more talkgroup details

This commit is contained in:
2026-01-19 11:10:48 +01:00
parent d359970fa8
commit 36af248069

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../models/device.dart'; import '../models/device.dart';
import '../models/static_talkgroup.dart'; import '../models/static_talkgroup.dart';
import '../models/device_profile.dart';
import '../services/authentication_manager.dart'; import '../services/authentication_manager.dart';
import '../services/brandmeister_client.dart'; import '../services/brandmeister_client.dart';
import 'link_talkgroup_view.dart'; import 'link_talkgroup_view.dart';
@@ -18,6 +19,7 @@ class DeviceDetailView extends StatefulWidget {
class _DeviceDetailViewState extends State<DeviceDetailView> { class _DeviceDetailViewState extends State<DeviceDetailView> {
List<StaticTalkgroup> _talkgroups = []; List<StaticTalkgroup> _talkgroups = [];
Map<String, String> _allTalkgroups = {}; Map<String, String> _allTalkgroups = {};
DeviceProfile? _deviceProfile;
bool _isLoadingTalkgroups = false; bool _isLoadingTalkgroups = false;
bool _isLoadingDeviceDetails = false; bool _isLoadingDeviceDetails = false;
String? _errorMessage; String? _errorMessage;
@@ -41,11 +43,13 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
final results = await Future.wait([ final results = await Future.wait([
authManager.getTalkgroups(widget.device.id), authManager.getTalkgroups(widget.device.id),
authManager.getAllTalkgroups(), authManager.getAllTalkgroups(),
authManager.getDeviceProfile(widget.device.id),
]); ]);
setState(() { setState(() {
_talkgroups = results[0] as List<StaticTalkgroup>; _talkgroups = results[0] as List<StaticTalkgroup>;
_allTalkgroups = results[1] as Map<String, String>; _allTalkgroups = results[1] as Map<String, String>;
_deviceProfile = results[2] as DeviceProfile;
_isLoadingTalkgroups = false; _isLoadingTalkgroups = false;
_isLoadingDeviceDetails = false; _isLoadingDeviceDetails = false;
}); });
@@ -234,6 +238,12 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
} }
Widget _buildTalkgroupsSection() { Widget _buildTalkgroupsSection() {
final hasAnyTalkgroups = _talkgroups.isNotEmpty ||
(_deviceProfile?.staticSubscriptions.isNotEmpty ?? false) ||
(_deviceProfile?.dynamicSubscriptions.isNotEmpty ?? false) ||
(_deviceProfile?.timedSubscriptions.isNotEmpty ?? false) ||
(_deviceProfile?.blockedGroups.isNotEmpty ?? false);
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@@ -243,7 +253,7 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Static Talkgroups', 'Talkgroup Subscriptions',
style: Theme.of(context).textTheme.titleLarge?.copyWith( style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@@ -257,7 +267,7 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (_talkgroups.isEmpty) if (!hasAnyTalkgroups)
Center( Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(32),
@@ -270,23 +280,112 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'No talkgroups linked', 'No talkgroups configured',
style: TextStyle(color: Colors.grey[600]), style: TextStyle(color: Colors.grey[600]),
), ),
], ],
), ),
), ),
) )
else else ...[
..._talkgroups.map((tg) => _TalkgroupRow( // Static Subscriptions
talkgroup: tg, if (_deviceProfile?.staticSubscriptions.isNotEmpty ?? false) ...[
talkgroupName: _allTalkgroups[tg.talkgroup], _buildTalkgroupCategory(
onDelete: () => _unlinkTalkgroup(tg), '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<StaticTalkgroup> 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 { class _InfoRow extends StatelessWidget {
@@ -330,61 +429,67 @@ class _InfoRow extends StatelessWidget {
class _TalkgroupRow extends StatelessWidget { class _TalkgroupRow extends StatelessWidget {
final StaticTalkgroup talkgroup; final StaticTalkgroup talkgroup;
final String? talkgroupName; final String? talkgroupName;
final VoidCallback onDelete; final VoidCallback? onDelete;
final Color? categoryColor;
const _TalkgroupRow({ const _TalkgroupRow({
required this.talkgroup, required this.talkgroup,
this.talkgroupName, this.talkgroupName,
required this.onDelete, this.onDelete,
this.categoryColor,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = categoryColor ?? Theme.of(context).colorScheme.primary;
return Card( return Card(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer, backgroundColor: color.withValues(alpha: 0.2),
child: Text( child: Text(
talkgroup.displaySlot, talkgroup.displaySlot,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSecondaryContainer, color: color,
), ),
), ),
), ),
title: Text(talkgroupName ?? talkgroup.displayId), title: Text(talkgroupName ?? talkgroup.displayId),
subtitle: Text('ID: ${talkgroup.displayId}'), subtitle: Text('ID: ${talkgroup.displayId}'),
trailing: IconButton( trailing: onDelete != null
icon: const Icon(Icons.delete_outline), ? IconButton(
color: Colors.red, icon: const Icon(Icons.delete_outline),
onPressed: () { color: Colors.red,
showDialog( onPressed: () {
context: context, showDialog(
builder: (context) => AlertDialog( context: context,
title: const Text('Unlink Talkgroup'), builder: (context) => AlertDialog(
content: Text( title: const Text('Unlink Talkgroup'),
'Are you sure you want to unlink talkgroup ${talkgroup.displayId}?', content: Text(
), 'Are you sure you want to unlink talkgroup ${talkgroup.displayId}?',
actions: [ ),
TextButton( actions: [
onPressed: () => Navigator.pop(context), TextButton(
child: const Text('Cancel'), onPressed: () => Navigator.pop(context),
), child: const Text('Cancel'),
TextButton( ),
onPressed: () { TextButton(
Navigator.pop(context); onPressed: () {
onDelete(); Navigator.pop(context);
}, onDelete!();
style: TextButton.styleFrom(foregroundColor: Colors.red), },
child: const Text('Unlink'), style: TextButton.styleFrom(foregroundColor: Colors.red),
), child: const Text('Unlink'),
], ),
), ],
); ),
}, );
), },
)
: null,
), ),
); );
} }