import 'dart:convert'; import 'package:archive/archive.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:dio/dio.dart' as dio; import '../../app/constants/keys.dart'; import '../crypto.dart'; import '../log/logger.dart'; import 'simple_log_card.dart'; import 'simple_api_card.dart'; /// 简化版开发者工具 - 只包含控制台日志和API监控 class IXDeveloperTools { static final IXDeveloperTools _instance = IXDeveloperTools._internal(); factory IXDeveloperTools() => _instance; IXDeveloperTools._internal(); final List _logs = []; final List _apiRequests = []; static const int maxLogs = 1000; static const int maxApiRequests = 500; /// 添加控制台日志 static void addLog(String message, String tag) { final entry = ConsoleLogEntry( timestamp: DateTime.now(), message: message, tag: tag, ); _instance._logs.insert(0, entry); if (_instance._logs.length > maxLogs) { _instance._logs.removeRange(maxLogs, _instance._logs.length); } } /// 添加API请求信息 static void addApiRequest(ApiRequestInfo request) { _instance._apiRequests.insert(0, request); if (_instance._apiRequests.length > maxApiRequests) { _instance._apiRequests.removeRange( maxApiRequests, _instance._apiRequests.length, ); } } /// 获取所有日志 static List get logs => List.unmodifiable(_instance._logs); /// 获取所有API请求 static List get apiRequests => List.unmodifiable(_instance._apiRequests); /// 清除所有日志 static void clearLogs() { _instance._logs.clear(); } /// 清除所有API请求 static void clearApiRequests() { _instance._apiRequests.clear(); } /// 显示开发者工具 static void show() { try { Get.to( () => const SimpleDeveloperToolsScreen(), transition: Transition.cupertino, ); } catch (e) { log('IXDeveloperTools', 'Unable to open Developer Tools: $e'); } } } /// 控制台日志条目 class ConsoleLogEntry { final DateTime timestamp; final String message; final String tag; ConsoleLogEntry({ required this.timestamp, required this.message, required this.tag, }); String get formattedTime { return '${timestamp.hour.toString().padLeft(2, '0')}:' '${timestamp.minute.toString().padLeft(2, '0')}:' '${timestamp.second.toString().padLeft(2, '0')}.' '${timestamp.millisecond.toString().padLeft(3, '0')}'; } } /// API请求信息 class ApiRequestInfo { final String id; final DateTime timestamp; final String method; final String url; final Map? headers; final dynamic requestData; final int? statusCode; final dynamic responseData; final String? error; final Duration? duration; ApiRequestInfo({ required this.id, required this.timestamp, required this.method, required this.url, this.headers, this.requestData, this.statusCode, this.responseData, this.error, this.duration, }); String get statusText { if (error != null) return 'ERROR'; if (statusCode == null) return 'PENDING'; if (statusCode! >= 200 && statusCode! < 300) return 'SUCCESS'; if (statusCode! >= 400) return 'ERROR'; return 'UNKNOWN'; } Color get statusColor { switch (statusText) { case 'SUCCESS': return Colors.green; case 'ERROR': return Colors.red; case 'PENDING': return Colors.orange; default: return Colors.grey; } } } /// 简化版API监控拦截器 class SimpleApiMonitorInterceptor extends dio.Interceptor { final Map _requestStartTimes = {}; @override void onRequest( dio.RequestOptions options, dio.RequestInterceptorHandler handler, ) { final requestId = _generateRequestId(); _requestStartTimes[requestId] = DateTime.now(); final request = ApiRequestInfo( id: requestId, timestamp: DateTime.now(), method: options.method, url: options.uri.toString(), headers: options.headers, requestData: options.data is FormData ? options.data.fields : _decryptData(options.data), ); IXDeveloperTools.addApiRequest(request); handler.next(options); } @override void onResponse( dio.Response response, dio.ResponseInterceptorHandler handler, ) { final requestId = _findRequestId(response.requestOptions.uri.toString()); if (requestId != null) { final startTime = _requestStartTimes.remove(requestId); final duration = startTime != null ? DateTime.now().difference(startTime) : null; final updatedRequest = ApiRequestInfo( id: requestId, timestamp: DateTime.now(), method: response.requestOptions.method, url: response.requestOptions.uri.toString(), headers: response.requestOptions.headers, requestData: response.requestOptions.data is FormData ? response.requestOptions.data.fields : _decryptData(response.requestOptions.data), statusCode: response.statusCode, responseData: _decryptData(response.data), duration: duration, ); _updateApiRequest(updatedRequest); } handler.next(response); } @override void onError(dio.DioException err, dio.ErrorInterceptorHandler handler) { final requestId = _findRequestId(err.requestOptions.uri.toString()); if (requestId != null) { final startTime = _requestStartTimes.remove(requestId); final duration = startTime != null ? DateTime.now().difference(startTime) : null; final updatedRequest = ApiRequestInfo( id: requestId, timestamp: DateTime.now(), method: err.requestOptions.method, url: err.requestOptions.uri.toString(), headers: err.requestOptions.headers, requestData: err.requestOptions.data is FormData ? err.requestOptions.data.fields : _decryptData(err.requestOptions.data), statusCode: err.response?.statusCode, error: err.message, duration: duration, ); _updateApiRequest(updatedRequest); } handler.next(err); } dynamic _decryptData(dynamic encryptedData) { try { // 1. Base64 编码 final base64Data = base64Encode(encryptedData); // 2. AES 解密 final decryptedBytes = Crypto.decryptBytes( base64Data, Keys.aesKey, Keys.aesIv, ); // 3. GZip 解压 final gzipDecoder = GZipDecoder(); final decompressedData = gzipDecoder.decodeBytes(decryptedBytes); // 4. UTF-8 解码 final data = utf8.decode(decompressedData); // string to json return jsonDecode(data); } catch (e) { return encryptedData; } } String _generateRequestId() { return '${DateTime.now().millisecondsSinceEpoch}_${IXDeveloperTools.apiRequests.length}'; } String? _findRequestId(String url) { try { final request = IXDeveloperTools.apiRequests.firstWhere( (r) => r.url == url && r.statusCode == null, ); return request.id; } catch (e) { return null; } } void _updateApiRequest(ApiRequestInfo updatedRequest) { final requests = IXDeveloperTools._instance._apiRequests; final index = requests.indexWhere((r) => r.id == updatedRequest.id); if (index != -1) { requests[index] = updatedRequest; } } } class SimpleNoSignApiMonitorInterceptor extends dio.Interceptor { final Map _requestStartTimes = {}; @override void onRequest( dio.RequestOptions options, dio.RequestInterceptorHandler handler, ) { final requestId = _generateRequestId(); _requestStartTimes[requestId] = DateTime.now(); final request = ApiRequestInfo( id: requestId, timestamp: DateTime.now(), method: options.method, url: options.uri.toString(), headers: options.headers, requestData: options.data, ); IXDeveloperTools.addApiRequest(request); handler.next(options); } @override void onResponse( dio.Response response, dio.ResponseInterceptorHandler handler, ) { final requestId = _findRequestId(response.requestOptions.uri.toString()); if (requestId != null) { final startTime = _requestStartTimes.remove(requestId); final duration = startTime != null ? DateTime.now().difference(startTime) : null; final updatedRequest = ApiRequestInfo( id: requestId, timestamp: DateTime.now(), method: response.requestOptions.method, url: response.requestOptions.uri.toString(), headers: response.requestOptions.headers, requestData: response.requestOptions.data, statusCode: response.statusCode, responseData: response.data, duration: duration, ); _updateApiRequest(updatedRequest); } handler.next(response); } @override void onError(dio.DioException err, dio.ErrorInterceptorHandler handler) { final requestId = _findRequestId(err.requestOptions.uri.toString()); if (requestId != null) { final startTime = _requestStartTimes.remove(requestId); final duration = startTime != null ? DateTime.now().difference(startTime) : null; final updatedRequest = ApiRequestInfo( id: requestId, timestamp: DateTime.now(), method: err.requestOptions.method, url: err.requestOptions.uri.toString(), headers: err.requestOptions.headers, requestData: err.requestOptions.data, statusCode: err.response?.statusCode, error: err.message, duration: duration, ); _updateApiRequest(updatedRequest); } handler.next(err); } String _generateRequestId() { return '${DateTime.now().millisecondsSinceEpoch}_${IXDeveloperTools.apiRequests.length}'; } String? _findRequestId(String url) { try { final request = IXDeveloperTools.apiRequests.firstWhere( (r) => r.url == url && r.statusCode == null, ); return request.id; } catch (e) { return null; } } void _updateApiRequest(ApiRequestInfo updatedRequest) { final requests = IXDeveloperTools._instance._apiRequests; final index = requests.indexWhere((r) => r.id == updatedRequest.id); if (index != -1) { requests[index] = updatedRequest; } } } /// 简化版开发者工具主界面 class SimpleDeveloperToolsScreen extends StatefulWidget { const SimpleDeveloperToolsScreen({super.key}); @override State createState() => _SimpleDeveloperToolsScreenState(); } class _SimpleDeveloperToolsScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _tabController.index = 1; } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[50], appBar: AppBar( elevation: 0, backgroundColor: Colors.black, title: const Text( '开发者工具', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white, ), ), bottom: PreferredSize( preferredSize: const Size.fromHeight(50), child: Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( color: Colors.grey[300]!.withValues(alpha: 0.5), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: TabBar( controller: _tabController, indicator: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue[500]!, Colors.purple[500]!], ), borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( color: Colors.blue[200]!.withValues(alpha: 0.5), blurRadius: 6, offset: const Offset(0, 2), ), ], ), indicatorSize: TabBarIndicatorSize.tab, dividerColor: Colors.transparent, labelColor: Colors.white, unselectedLabelColor: Colors.grey[600], labelStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), unselectedLabelStyle: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14, ), tabs: const [ Tab( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.terminal, size: 18), SizedBox(width: 6), Text('控制台日志'), ], ), ), Tab( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.network_check, size: 18), SizedBox(width: 6), Text('API监控'), ], ), ), ], ), ), ), ), body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.grey, Colors.blue], ), ), child: TabBarView( controller: _tabController, children: const [SimpleConsoleLogTab(), SimpleApiMonitorTab()], ), ), ); } } /// 简化版开发者工具对话框 class SimpleDeveloperToolsDialog extends StatelessWidget { const SimpleDeveloperToolsDialog({super.key}); @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, elevation: 0, child: Container( width: Get.width * 0.95, height: Get.height * 0.9, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: const SimpleDeveloperToolsScreen(), ), ), ); } } /// 简化版控制台日志标签页 class SimpleConsoleLogTab extends StatefulWidget { const SimpleConsoleLogTab({super.key}); @override State createState() => _SimpleConsoleLogTabState(); } class _SimpleConsoleLogTabState extends State { final TextEditingController _searchController = TextEditingController(); List _filteredLogs = []; @override void initState() { super.initState(); _updateLogs(); } void _updateLogs() { setState(() { final allLogs = IXDeveloperTools.logs; if (_searchController.text.isEmpty) { _filteredLogs = allLogs; } else { _filteredLogs = allLogs .where( (log) => log.message.toLowerCase().contains( _searchController.text.toLowerCase(), ) || log.tag.toLowerCase().contains( _searchController.text.toLowerCase(), ), ) .toList(); } }); } @override Widget build(BuildContext context) { return Column( children: [ // 搜索和操作栏 Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.grey[200]!.withValues(alpha: 0.8), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( children: [ // 搜索框 Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue[200]!, width: 1), ), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: '搜索日志内容...', hintStyle: TextStyle(color: Colors.grey[500]), prefixIcon: Icon(Icons.search, color: Colors.blue[600]), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), onChanged: (_) => _updateLogs(), ), ), const SizedBox(height: 12), // 操作按钮 Row( children: [ Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.refresh, size: 18), label: const Text('刷新'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue[500], foregroundColor: Colors.white, elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: _updateLogs, ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.clear_all, size: 18), label: const Text('清除'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red[500], foregroundColor: Colors.white, elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () { IXDeveloperTools.clearLogs(); _updateLogs(); }, ), ), ], ), ], ), ), // 日志列表 Expanded( child: _filteredLogs.isEmpty ? Center( child: Container( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue[100]!, Colors.purple[100]!], ), shape: BoxShape.circle, ), child: Icon( Icons.terminal, size: 48, color: Colors.blue[600], ), ), const SizedBox(height: 16), Text( '暂无日志', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey[600], ), ), const SizedBox(height: 8), Text( '开始使用应用后,日志将显示在这里', style: TextStyle( fontSize: 14, color: Colors.grey[500], ), ), ], ), ), ) : RefreshIndicator( onRefresh: () async { _updateLogs(); }, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), itemCount: _filteredLogs.length, itemBuilder: (context, index) { final log = _filteredLogs[index]; return SimpleLogCard(log: log); }, ), ), ), ], ); } } /// 简化版API监控标签页 class SimpleApiMonitorTab extends StatefulWidget { const SimpleApiMonitorTab({super.key}); @override State createState() => _SimpleApiMonitorTabState(); } class _SimpleApiMonitorTabState extends State { @override Widget build(BuildContext context) { final requests = IXDeveloperTools.apiRequests; return Column( children: [ // 操作栏 Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.grey[200]!.withValues(alpha: 0.8), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( children: [ // 统计信息 Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue[50]!, Colors.purple[50]!], ), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue[200]!, width: 1), ), child: Row( children: [ Icon(Icons.analytics, color: Colors.blue[600], size: 20), const SizedBox(width: 8), Text( '共 ${requests.length} 个请求', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.blue[700], ), ), const Spacer(), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.blue[100], borderRadius: BorderRadius.circular(8), ), child: Text( '实时监控', style: TextStyle( fontSize: 12, color: Colors.blue[700], fontWeight: FontWeight.w500, ), ), ), ], ), ), const SizedBox(height: 12), // 操作按钮 Row( children: [ Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.refresh, size: 18), label: const Text('刷新'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green[500], foregroundColor: Colors.white, elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () => setState(() {}), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.clear_all, size: 18), label: const Text('清除'), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange[500], foregroundColor: Colors.white, elevation: 2, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: () { IXDeveloperTools.clearApiRequests(); setState(() {}); }, ), ), ], ), ], ), ), // API请求列表 Expanded( child: requests.isEmpty ? Center( child: Container( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.green[100]!, Colors.blue[100]!], ), shape: BoxShape.circle, ), child: Icon( Icons.network_check, size: 48, color: Colors.green[600], ), ), const SizedBox(height: 16), Text( '暂无API请求', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey[600], ), ), const SizedBox(height: 8), Text( '开始网络请求后,API信息将显示在这里', style: TextStyle( fontSize: 14, color: Colors.grey[500], ), ), ], ), ), ) : RefreshIndicator( onRefresh: () async { setState(() {}); }, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), itemCount: requests.length, itemBuilder: (context, index) { final request = requests[index]; return SimpleApiCard(request: request); }, ), ), ), ], ); } }