146 lines
4.0 KiB
Dart
146 lines
4.0 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:provider/provider.dart';
|
||
|
|
import '../providers/client_provider.dart';
|
||
|
|
import '../services/log_service.dart';
|
||
|
|
import 'dart:async';
|
||
|
|
|
||
|
|
class LogViewerScreen extends StatefulWidget {
|
||
|
|
const LogViewerScreen({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<LogViewerScreen> createState() => _LogViewerScreenState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _LogViewerScreenState extends State<LogViewerScreen> {
|
||
|
|
final LogService _logService = LogService();
|
||
|
|
final ScrollController _scrollController = ScrollController();
|
||
|
|
String _logs = '';
|
||
|
|
StreamSubscription<String>? _logSubscription;
|
||
|
|
StreamSubscription<String>? _clientLogSubscription;
|
||
|
|
bool _autoScroll = true;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
_loadLogs();
|
||
|
|
_startMonitoring();
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _loadLogs() async {
|
||
|
|
final logs = await _logService.getRecentLogs();
|
||
|
|
setState(() {
|
||
|
|
_logs = logs;
|
||
|
|
});
|
||
|
|
if (_autoScroll && _scrollController.hasClients) {
|
||
|
|
_scrollToBottom();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void _startMonitoring() {
|
||
|
|
// 监控日志文件
|
||
|
|
_logSubscription = _logService.logStream.listen((log) {
|
||
|
|
setState(() {
|
||
|
|
_logs += '\n$log';
|
||
|
|
});
|
||
|
|
if (_autoScroll && _scrollController.hasClients) {
|
||
|
|
_scrollToBottom();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 监控客户端日志流
|
||
|
|
final clientProvider = Provider.of<ClientProvider>(context, listen: false);
|
||
|
|
_clientLogSubscription = clientProvider.logStream.listen((log) {
|
||
|
|
setState(() {
|
||
|
|
_logs += '\n$log';
|
||
|
|
});
|
||
|
|
if (_autoScroll && _scrollController.hasClients) {
|
||
|
|
_scrollToBottom();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
void _scrollToBottom() {
|
||
|
|
if (_scrollController.hasClients) {
|
||
|
|
_scrollController.animateTo(
|
||
|
|
_scrollController.position.maxScrollExtent,
|
||
|
|
duration: const Duration(milliseconds: 300),
|
||
|
|
curve: Curves.easeOut,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
void dispose() {
|
||
|
|
_logSubscription?.cancel();
|
||
|
|
_clientLogSubscription?.cancel();
|
||
|
|
_scrollController.dispose();
|
||
|
|
super.dispose();
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Scaffold(
|
||
|
|
appBar: AppBar(
|
||
|
|
title: const Text('日志查看'),
|
||
|
|
actions: [
|
||
|
|
IconButton(
|
||
|
|
icon: Icon(_autoScroll ? Icons.arrow_downward : Icons.arrow_upward),
|
||
|
|
onPressed: () {
|
||
|
|
setState(() {
|
||
|
|
_autoScroll = !_autoScroll;
|
||
|
|
});
|
||
|
|
},
|
||
|
|
tooltip: _autoScroll ? '关闭自动滚动' : '开启自动滚动',
|
||
|
|
),
|
||
|
|
IconButton(
|
||
|
|
icon: const Icon(Icons.refresh),
|
||
|
|
onPressed: _loadLogs,
|
||
|
|
tooltip: '刷新日志',
|
||
|
|
),
|
||
|
|
IconButton(
|
||
|
|
icon: const Icon(Icons.clear),
|
||
|
|
onPressed: () async {
|
||
|
|
final confirmed = await showDialog<bool>(
|
||
|
|
context: context,
|
||
|
|
builder: (context) => AlertDialog(
|
||
|
|
title: const Text('确认清理'),
|
||
|
|
content: const Text('确定要清理所有日志吗?'),
|
||
|
|
actions: [
|
||
|
|
TextButton(
|
||
|
|
onPressed: () => Navigator.pop(context, false),
|
||
|
|
child: const Text('取消'),
|
||
|
|
),
|
||
|
|
TextButton(
|
||
|
|
onPressed: () => Navigator.pop(context, true),
|
||
|
|
child: const Text('确定'),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
if (confirmed == true) {
|
||
|
|
await _logService.clearLogs();
|
||
|
|
_loadLogs();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
tooltip: '清理日志',
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
body: Container(
|
||
|
|
padding: const EdgeInsets.all(8.0),
|
||
|
|
child: SingleChildScrollView(
|
||
|
|
controller: _scrollController,
|
||
|
|
child: SelectableText(
|
||
|
|
_logs.isEmpty ? '暂无日志' : _logs,
|
||
|
|
style: const TextStyle(
|
||
|
|
fontFamily: 'monospace',
|
||
|
|
fontSize: 12,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|