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 '../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<DeviceDetailView> {
List<StaticTalkgroup> _talkgroups = [];
Map<String, String> _allTalkgroups = {};
DeviceProfile? _deviceProfile;
bool _isLoadingTalkgroups = false;
bool _isLoadingDeviceDetails = false;
String? _errorMessage;
@@ -41,11 +43,13 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
final results = await Future.wait([
authManager.getTalkgroups(widget.device.id),
authManager.getAllTalkgroups(),
authManager.getDeviceProfile(widget.device.id),
]);
setState(() {
_talkgroups = results[0] as List<StaticTalkgroup>;
_allTalkgroups = results[1] as Map<String, String>;
_deviceProfile = results[2] as DeviceProfile;
_isLoadingTalkgroups = false;
_isLoadingDeviceDetails = false;
});
@@ -234,6 +238,12 @@ class _DeviceDetailViewState extends State<DeviceDetailView> {
}
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<DeviceDetailView> {
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<DeviceDetailView> {
],
),
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<DeviceDetailView> {
),
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<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 {
@@ -330,33 +429,38 @@ 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(
trailing: onDelete != null
? IconButton(
icon: const Icon(Icons.delete_outline),
color: Colors.red,
onPressed: () {
@@ -375,7 +479,7 @@ class _TalkgroupRow extends StatelessWidget {
TextButton(
onPressed: () {
Navigator.pop(context);
onDelete();
onDelete!();
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Unlink'),
@@ -384,7 +488,8 @@ class _TalkgroupRow extends StatelessWidget {
),
);
},
),
)
: null,
),
);
}