Initial Commit

This commit is contained in:
ivan2282
2025-11-15 20:06:40 +03:00
commit 205d11df0d
233 changed files with 52572 additions and 0 deletions

View File

@@ -0,0 +1,676 @@
import 'package:flutter/material.dart';
import 'dart:async';
import '../connection/connection_logger.dart';
import '../connection/connection_state.dart' as conn_state;
import '../connection/health_monitor.dart';
import '../api_service_v2.dart';
class ConnectionDebugPanel extends StatefulWidget {
final bool isVisible;
final VoidCallback? onClose;
const ConnectionDebugPanel({super.key, this.isVisible = false, this.onClose});
@override
State<ConnectionDebugPanel> createState() => _ConnectionDebugPanelState();
}
class _ConnectionDebugPanelState extends State<ConnectionDebugPanel>
with TickerProviderStateMixin {
late TabController _tabController;
List<LogEntry> _logs = [];
final List<conn_state.ConnectionInfo> _stateHistory = [];
final List<HealthMetrics> _healthMetrics = [];
late StreamSubscription<List<LogEntry>> _logsSubscription;
late StreamSubscription<conn_state.ConnectionInfo> _stateSubscription;
late StreamSubscription<HealthMetrics> _healthSubscription;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
_setupSubscriptions();
}
void _setupSubscriptions() {
_logsSubscription = Stream.periodic(const Duration(seconds: 1))
.asyncMap((_) async => ApiServiceV2.instance.logs.take(100).toList())
.listen((logs) {
if (mounted) {
setState(() {
_logs = logs;
});
}
});
_stateSubscription = ApiServiceV2.instance.connectionState.listen((state) {
if (mounted) {
setState(() {
_stateHistory.add(state);
if (_stateHistory.length > 50) {
_stateHistory.removeAt(0);
}
});
}
});
_healthSubscription = ApiServiceV2.instance.healthMetrics.listen((health) {
if (mounted) {
setState(() {
_healthMetrics.add(health);
if (_healthMetrics.length > 50) {
_healthMetrics.removeAt(0);
}
});
}
});
}
@override
void dispose() {
_logsSubscription.cancel();
_stateSubscription.cancel();
_healthSubscription.cancel();
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!widget.isVisible) return const SizedBox.shrink();
return Container(
height: 400,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: Column(
children: [
_buildHeader(),
_buildTabBar(),
Expanded(child: _buildTabContent()),
],
),
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Row(
children: [
Icon(Icons.bug_report, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 8),
Text(
'Отладка подключения',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
IconButton(
onPressed: widget.onClose,
icon: const Icon(Icons.close),
iconSize: 20,
),
],
),
);
}
Widget _buildTabBar() {
return TabBar(
controller: _tabController,
isScrollable: true,
tabs: const [
Tab(text: 'Логи'),
Tab(text: 'Состояния'),
Tab(text: 'Здоровье'),
Tab(text: 'Статистика'),
],
);
}
Widget _buildTabContent() {
return TabBarView(
controller: _tabController,
children: [
_buildLogsTab(),
_buildStatesTab(),
_buildHealthTab(),
_buildStatsTab(),
],
);
}
Widget _buildLogsTab() {
return Column(
children: [
_buildLogsControls(),
Expanded(child: _buildLogsList()),
],
);
}
Widget _buildLogsControls() {
return Container(
padding: const EdgeInsets.all(8),
child: Row(
children: [
ElevatedButton.icon(
onPressed: _clearLogs,
icon: const Icon(Icons.clear, size: 16),
label: const Text('Очистить'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: _exportLogs,
icon: const Icon(Icons.download, size: 16),
label: const Text('Экспорт'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
const Spacer(),
Text(
'Логов: ${_logs.length}',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
);
}
Widget _buildLogsList() {
if (_logs.isEmpty) {
return const Center(child: Text('Нет логов'));
}
return ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, index) {
final log = _logs[index];
return _buildLogItem(log);
},
);
}
Widget _buildLogItem(LogEntry log) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _getLogColor(log.level).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _getLogColor(log.level).withOpacity(0.3),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
_getLogIcon(log.level),
size: 16,
color: _getLogColor(log.level),
),
const SizedBox(width: 8),
Text(
log.category,
style: TextStyle(
color: _getLogColor(log.level),
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
const Spacer(),
Text(
_formatTime(log.timestamp),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
const SizedBox(height: 4),
Text(log.message, style: Theme.of(context).textTheme.bodyMedium),
if (log.data != null) ...[
const SizedBox(height: 4),
Text(
'Data: ${log.data}',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'),
),
],
if (log.error != null) ...[
const SizedBox(height: 4),
Text(
'Error: ${log.error}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.red,
fontFamily: 'monospace',
),
),
],
],
),
);
}
Widget _buildStatesTab() {
return ListView.builder(
itemCount: _stateHistory.length,
itemBuilder: (context, index) {
final state = _stateHistory[index];
return _buildStateItem(state);
},
);
}
Widget _buildStateItem(conn_state.ConnectionInfo state) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _getStateColor(state.state).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _getStateColor(state.state).withOpacity(0.3),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
_getStateIcon(state.state),
size: 16,
color: _getStateColor(state.state),
),
const SizedBox(width: 8),
Text(
_getStateText(state.state),
style: TextStyle(
color: _getStateColor(state.state),
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Text(
_formatTime(state.timestamp),
style: Theme.of(context).textTheme.bodySmall,
),
],
),
if (state.message != null) ...[
const SizedBox(height: 4),
Text(state.message!, style: Theme.of(context).textTheme.bodyMedium),
],
if (state.serverUrl != null) ...[
const SizedBox(height: 4),
Text(
'Сервер: ${state.serverUrl}',
style: Theme.of(context).textTheme.bodySmall,
),
],
if (state.latency != null) ...[
const SizedBox(height: 4),
Text(
'Задержка: ${state.latency}ms',
style: Theme.of(context).textTheme.bodySmall,
),
],
],
),
);
}
Widget _buildHealthTab() {
if (_healthMetrics.isEmpty) {
return const Center(child: Text('Нет данных о здоровье'));
}
final latestHealth = _healthMetrics.last;
return Column(
children: [
_buildHealthSummary(latestHealth),
Expanded(child: _buildHealthChart()),
],
);
}
Widget _buildHealthSummary(HealthMetrics health) {
return Container(
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _getHealthColor(health.quality).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _getHealthColor(health.quality).withOpacity(0.3),
width: 1,
),
),
child: Column(
children: [
Row(
children: [
Icon(
_getHealthIcon(health.quality),
size: 24,
color: _getHealthColor(health.quality),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Здоровье соединения',
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'${health.healthScore}/100 - ${_getHealthText(health.quality)}',
style: TextStyle(
color: _getHealthColor(health.quality),
fontWeight: FontWeight.bold,
),
),
],
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildHealthMetric('Задержка', '${health.latency}ms'),
_buildHealthMetric('Потери', '${health.packetLoss}%'),
_buildHealthMetric('Переподключения', '${health.reconnects}'),
_buildHealthMetric('Ошибки', '${health.errors}'),
],
),
],
),
);
}
Widget _buildHealthMetric(String label, String value) {
return Column(
children: [
Text(
value,
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
Text(label, style: Theme.of(context).textTheme.bodySmall),
],
);
}
Widget _buildHealthChart() {
return Container(
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
),
),
child: const Center(
child: Text('График здоровья соединения\n(в разработке)'),
),
);
}
Widget _buildStatsTab() {
return FutureBuilder<Map<String, dynamic>>(
future: ApiServiceV2.instance
.getStatistics(), // Указываем Future, который нужно ожидать
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text('Ошибка загрузки статистики: ${snapshot.error}'),
);
}
if (!snapshot.hasData || snapshot.data == null) {
return const Center(child: Text('Нет данных для отображения'));
}
final stats = snapshot.data!; // Теперь это точно Map<String, dynamic>
return ListView(
padding: const EdgeInsets.all(16),
children: [
_buildStatsSection('API Service', stats['api_service']),
const SizedBox(height: 16),
_buildStatsSection('Connection', stats['connection']),
],
);
},
);
}
Widget _buildStatsSection(String title, Map<String, dynamic>? data) {
if (data == null) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
...data.entries.map((entry) => _buildStatsRow(entry.key, entry.value)),
],
);
}
Widget _buildStatsRow(String key, dynamic value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(key, style: Theme.of(context).textTheme.bodyMedium),
),
Expanded(
flex: 1,
child: Text(
value.toString(),
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500),
),
),
],
),
);
}
Color _getLogColor(LogLevel level) {
switch (level) {
case LogLevel.debug:
return Colors.blue;
case LogLevel.info:
return Colors.green;
case LogLevel.warning:
return Colors.orange;
case LogLevel.error:
return Colors.red;
case LogLevel.critical:
return Colors.red.shade800;
}
}
IconData _getLogIcon(LogLevel level) {
switch (level) {
case LogLevel.debug:
return Icons.bug_report;
case LogLevel.info:
return Icons.info;
case LogLevel.warning:
return Icons.warning;
case LogLevel.error:
return Icons.error;
case LogLevel.critical:
return Icons.dangerous;
}
}
Color _getStateColor(conn_state.ConnectionState state) {
switch (state) {
case conn_state.ConnectionState.ready:
return Colors.green;
case conn_state.ConnectionState.connected:
return Colors.blue;
case conn_state.ConnectionState.connecting:
case conn_state.ConnectionState.reconnecting:
return Colors.orange;
case conn_state.ConnectionState.error:
return Colors.red;
case conn_state.ConnectionState.disconnected:
case conn_state.ConnectionState.disabled:
return Colors.grey;
}
}
IconData _getStateIcon(conn_state.ConnectionState state) {
switch (state) {
case conn_state.ConnectionState.ready:
return Icons.check_circle;
case conn_state.ConnectionState.connected:
return Icons.link;
case conn_state.ConnectionState.connecting:
case conn_state.ConnectionState.reconnecting:
return Icons.sync;
case conn_state.ConnectionState.error:
return Icons.error;
case conn_state.ConnectionState.disconnected:
case conn_state.ConnectionState.disabled:
return Icons.link_off;
}
}
String _getStateText(conn_state.ConnectionState state) {
switch (state) {
case conn_state.ConnectionState.ready:
return 'Готов';
case conn_state.ConnectionState.connected:
return 'Подключен';
case conn_state.ConnectionState.connecting:
return 'Подключение';
case conn_state.ConnectionState.reconnecting:
return 'Переподключение';
case conn_state.ConnectionState.error:
return 'Ошибка';
case conn_state.ConnectionState.disconnected:
return 'Отключен';
case conn_state.ConnectionState.disabled:
return 'Отключен';
}
}
Color _getHealthColor(ConnectionQuality quality) {
switch (quality) {
case ConnectionQuality.excellent:
return Colors.green;
case ConnectionQuality.good:
return Colors.lightGreen;
case ConnectionQuality.fair:
return Colors.orange;
case ConnectionQuality.poor:
return Colors.red;
case ConnectionQuality.critical:
return Colors.red.shade800;
}
}
IconData _getHealthIcon(ConnectionQuality quality) {
switch (quality) {
case ConnectionQuality.excellent:
return Icons.signal_cellular_4_bar;
case ConnectionQuality.good:
return Icons.signal_cellular_4_bar;
case ConnectionQuality.fair:
return Icons.signal_cellular_4_bar;
case ConnectionQuality.poor:
return Icons.signal_cellular_0_bar;
case ConnectionQuality.critical:
return Icons.signal_cellular_0_bar;
}
}
String _getHealthText(ConnectionQuality quality) {
switch (quality) {
case ConnectionQuality.excellent:
return 'Отлично';
case ConnectionQuality.good:
return 'Хорошо';
case ConnectionQuality.fair:
return 'Удовлетворительно';
case ConnectionQuality.poor:
return 'Плохо';
case ConnectionQuality.critical:
return 'Критично';
}
}
String _formatTime(DateTime time) {
return '${time.hour.toString().padLeft(2, '0')}:'
'${time.minute.toString().padLeft(2, '0')}:'
'${time.second.toString().padLeft(2, '0')}';
}
void _clearLogs() {
}
void _exportLogs() {
}
}