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

740
lib/debug_screen.dart Normal file
View File

@@ -0,0 +1,740 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/scheduler.dart';
import 'package:gwid/cache_management_screen.dart'; // Добавлен импорт
import 'package:provider/provider.dart';
import 'package:gwid/theme_provider.dart';
import 'package:gwid/api_service.dart';
import 'package:gwid/phone_entry_screen.dart';
import 'package:gwid/custom_request_screen.dart';
import 'dart:async';
class DebugScreen extends StatelessWidget {
const DebugScreen({super.key});
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final theme = context.watch<ThemeProvider>();
return Scaffold(
appBar: AppBar(
title: const Text('Debug Settings'),
backgroundColor: colors.surface,
foregroundColor: colors.onSurface,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_OutlinedSection(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"Performance Debug",
style: TextStyle(
color: colors.primary,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.speed),
title: const Text("Показать FPS overlay"),
subtitle: const Text("Отображение FPS и производительности"),
trailing: Switch(
value: theme.debugShowPerformanceOverlay,
onChanged: (value) =>
theme.setDebugShowPerformanceOverlay(value),
),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.refresh),
title: const Text("Показать панель обновления чатов"),
subtitle: const Text(
"Debug панель для обновления списка чатов",
),
trailing: Switch(
value: theme.debugShowChatsRefreshPanel,
onChanged: (value) =>
theme.setDebugShowChatsRefreshPanel(value),
),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.message),
title: const Text("Показать счётчик сообщений"),
subtitle: const Text("Отладочная информация о сообщениях"),
trailing: Switch(
value: theme.debugShowMessageCount,
onChanged: (value) => theme.setDebugShowMessageCount(value),
),
),
],
),
),
const SizedBox(height: 16),
_OutlinedSection(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"Инструменты разработчика",
style: TextStyle(
color: colors.primary,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.code),
title: const Text("Custom API Request"),
subtitle: const Text("Отправить сырой запрос на сервер"),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const CustomRequestScreen(),
),
);
},
),
],
),
),
const SizedBox(height: 16),
_OutlinedSection(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"Data Management",
style: TextStyle(
color: colors.primary,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.delete_forever),
title: const Text("Очистить все данные"),
subtitle: const Text("Полная очистка кэшей и данных"),
trailing: const Icon(Icons.chevron_right),
onTap: () => _showClearAllDataDialog(context),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.phone),
title: const Text("Показать ввод номера"),
subtitle: const Text("Открыть экран ввода номера без выхода"),
trailing: const Icon(Icons.chevron_right),
onTap: () => _showPhoneEntryScreen(context),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.traffic),
title: const Text("Статистика трафика"),
subtitle: const Text("Просмотр использованного трафика"),
trailing: const Icon(Icons.chevron_right),
onTap: () => _showTrafficStats(context),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.storage),
title: const Text("Использование памяти"),
subtitle: const Text("Просмотр статистики памяти"),
trailing: const Icon(Icons.chevron_right),
onTap: () => _showMemoryUsage(context),
),
const SizedBox(height: 8),
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.cached),
title: const Text("Управление кэшем"),
subtitle: const Text("Настройки кэширования и статистика"),
trailing: const Icon(Icons.chevron_right),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const CacheManagementScreen(),
),
);
},
),
],
),
),
],
),
);
}
void _showClearAllDataDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Очистить все данные'),
content: const Text(
'Это действие удалит ВСЕ данные приложения:\n\n'
'Все кэши и сообщения\n'
'• Настройки и профиль\n'
'• Токен авторизации\n'
'• История чатов\n\n'
'После очистки приложение будет закрыто.\n'
'Вы уверены?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Отмена'),
),
FilledButton(
onPressed: () async {
Navigator.of(context).pop();
await _performFullDataClear(context);
},
style: FilledButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Очистить и закрыть'),
),
],
),
);
}
Future<void> _performFullDataClear(BuildContext context) async {
try {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const AlertDialog(
content: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(width: 16),
Text('Очистка данных...'),
],
),
),
);
await ApiService.instance.clearAllData();
if (context.mounted) {
Navigator.of(context).pop();
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Все данные очищены. Приложение будет закрыто.'),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
}
await Future.delayed(const Duration(seconds: 2));
if (context.mounted) {
SystemNavigator.pop();
}
} catch (e) {
if (context.mounted) {
Navigator.of(context).pop();
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Ошибка при очистке данных: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
void _showPhoneEntryScreen(BuildContext context) {
Navigator.of(
context,
).push(MaterialPageRoute(builder: (context) => const PhoneEntryScreen()));
}
void _showTrafficStats(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Статистика трафика'),
content: const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('📊 Статистика использования данных:'),
SizedBox(height: 16),
Text('• Отправлено сообщений: 1,247'),
Text('• Получено сообщений: 3,891'),
Text('• Загружено фото: 156 MB'),
Text('• Загружено видео: 89 MB'),
Text('• Общий трафик: 2.1 GB'),
SizedBox(height: 16),
Text('📅 За последние 30 дней'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Закрыть'),
),
],
),
);
}
void _showMemoryUsage(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Использование памяти'),
content: const Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('💾 Использование памяти:'),
SizedBox(height: 16),
Text('• Кэш сообщений: 45.2 MB'),
Text('• Кэш контактов: 12.8 MB'),
Text('• Кэш чатов: 8.3 MB'),
Text('• Медиа файлы: 156.7 MB'),
Text('• Общее использование: 223.0 MB'),
SizedBox(height: 16),
Text('📱 Доступно памяти: 2.1 GB'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Закрыть'),
),
FilledButton(
onPressed: () {
Navigator.of(context).pop();
ApiService.instance.clearAllCaches();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Кэш очищен'),
backgroundColor: Colors.green,
),
);
},
child: const Text('Очистить кэш'),
),
],
),
);
}
}
class _OutlinedSection extends StatelessWidget {
final Widget child;
const _OutlinedSection({required this.child});
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: colors.outline.withOpacity(0.3)),
borderRadius: BorderRadius.circular(12),
),
child: child,
);
}
}
class Session {
final String client;
final String location;
final bool current;
final int time;
final String info;
Session({
required this.client,
required this.location,
required this.current,
required this.time,
required this.info,
});
factory Session.fromJson(Map<String, dynamic> json) {
return Session(
client: json['client'] ?? '',
location: json['location'] ?? '',
current: json['current'] ?? false,
time: json['time'] ?? 0,
info: json['info'] ?? '',
);
}
}
class SessionsScreen extends StatefulWidget {
const SessionsScreen({super.key});
@override
State<SessionsScreen> createState() => _SessionsScreenState();
}
class _SessionsScreenState extends State<SessionsScreen> {
List<Session> _sessions = [];
bool _isLoading = true;
bool _isInitialLoad = true;
StreamSubscription? _apiSubscription;
@override
void initState() {
super.initState();
_listenToApi();
}
void _loadSessions() {
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_isLoading = true;
});
}
});
ApiService.instance.requestSessions();
}
void _terminateAllSessions() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Сбросить все сессии?'),
content: const Text(
'Все остальные сессии будут завершены. '
'Текущая сессия останется активной.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Отмена'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
foregroundColor: Theme.of(context).colorScheme.onError,
),
child: const Text('Сбросить'),
),
],
),
);
if (confirmed == true) {
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_isLoading = true;
});
}
});
ApiService.instance.terminateAllSessions();
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_loadSessions();
}
});
}
});
}
}
void _listenToApi() {
_apiSubscription = ApiService.instance.messages.listen((message) {
if (message['opcode'] == 96 && mounted) {
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_isLoading = false;
});
}
});
final payload = message['payload'];
if (payload != null && payload['sessions'] != null) {
final sessionsList = payload['sessions'] as List;
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_sessions = sessionsList
.map((session) => Session.fromJson(session))
.toList();
});
}
});
}
}
});
}
String _formatTime(int timestamp) {
final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
final now = DateTime.now();
final difference = now.difference(date);
String relativeTime;
if (difference.inDays > 0) {
relativeTime = '${difference.inDays} дн. назад';
} else if (difference.inHours > 0) {
relativeTime = '${difference.inHours} ч. назад';
} else if (difference.inMinutes > 0) {
relativeTime = '${difference.inMinutes} мин. назад';
} else {
relativeTime = 'Только что';
}
final exactTime =
'${date.day.toString().padLeft(2, '0')}.${date.month.toString().padLeft(2, '0')}.${date.year} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
return '$relativeTime ($exactTime)';
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
if (_isInitialLoad && _sessions.isEmpty) {
_isInitialLoad = false;
_loadSessions();
}
return Scaffold(
appBar: AppBar(
title: const Text("Активные сессии"),
actions: [
IconButton(onPressed: _loadSessions, icon: const Icon(Icons.refresh)),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: _sessions.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.security,
size: 64,
color: colors.onSurfaceVariant,
),
const SizedBox(height: 16),
Text(
"Нет активных сессий",
style: TextStyle(
color: colors.onSurfaceVariant,
fontSize: 18,
),
),
],
),
)
: Column(
children: [
if (_sessions.any((s) => !s.current))
Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
child: FilledButton.icon(
onPressed: _terminateAllSessions,
style: FilledButton.styleFrom(
backgroundColor: colors.error,
foregroundColor: colors.onError,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
icon: const Icon(Icons.logout, size: 24),
label: const Text(
"Завершить все сессии кроме текущей",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _sessions.length,
itemBuilder: (context, index) {
final session = _sessions[index];
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: CircleAvatar(
backgroundColor: session.current
? colors.primary
: colors.surfaceContainerHighest,
child: Icon(
session.current
? Icons.phone_android
: Icons.computer,
color: session.current
? colors.onPrimary
: colors.onSurfaceVariant,
),
),
title: Text(
session.current ? "Текущая сессия" : session.client,
style: TextStyle(
fontWeight: session.current
? FontWeight.bold
: FontWeight.normal,
color: session.current
? colors.primary
: colors.onSurface,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
session.location,
style: TextStyle(
color: colors.onSurfaceVariant,
fontSize: 14,
),
),
const SizedBox(height: 2),
Text(
session.info,
style: TextStyle(
color: colors.onSurfaceVariant,
fontSize: 12,
),
),
const SizedBox(height: 2),
Text(
_formatTime(session.time),
style: TextStyle(
color: colors.onSurfaceVariant,
fontSize: 12,
),
),
],
),
trailing: session.current
? Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: colors.primary,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"Активна",
style: TextStyle(
color: colors.onPrimary,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
)
: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"Неактивна",
style: TextStyle(
color: colors.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
),
);
},
),
),
],
),
);
}
@override
void dispose() {
_apiSubscription?.cancel();
super.dispose();
}
}