Show more talkgroup details
This commit is contained in:
@@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user