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 'package:nomo/config/theme/theme_extensions/theme_extension.dart'; 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: Get.reactiveTheme.scaffoldBackgroundColor, appBar: AppBar( elevation: 0, iconTheme: IconThemeData( color: Get.reactiveTheme.textTheme.bodyLarge!.color, ), title: Text( '开发者工具', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 17, color: Get.reactiveTheme.textTheme.bodyLarge!.color, ), ), bottom: PreferredSize( preferredSize: const Size.fromHeight(44), child: Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: TabBar( controller: _tabController, indicator: BoxDecoration( color: Colors.black87, borderRadius: BorderRadius.circular(6), ), indicatorSize: TabBarIndicatorSize.tab, dividerColor: Colors.transparent, labelColor: Colors.white, unselectedLabelColor: Colors.grey[600], labelStyle: const TextStyle( fontWeight: FontWeight.w500, fontSize: 13, ), unselectedLabelStyle: const TextStyle( fontWeight: FontWeight.w400, fontSize: 13, ), indicatorPadding: const EdgeInsets.all(3), tabs: const [ Tab(text: '日志'), Tab(text: 'API'), ], ), ), ), ), body: 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(12), ), child: ClipRRect( borderRadius: BorderRadius.circular(12), 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( padding: const EdgeInsets.all(12), child: Row( children: [ // 搜索框 Expanded( child: Container( height: 36, decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: TextField( controller: _searchController, style: const TextStyle(fontSize: 13), decoration: InputDecoration( hintText: '搜索...', hintStyle: TextStyle( color: Colors.grey[400], fontSize: 13, ), prefixIcon: Icon( Icons.search, color: Colors.grey[400], size: 18, ), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.symmetric( vertical: 8, horizontal: 0, ), ), onChanged: (_) => _updateLogs(), ), ), ), const SizedBox(width: 8), // 刷新按钮 _buildIconButton(Icons.refresh, _updateLogs), const SizedBox(width: 6), // 清除按钮 _buildIconButton(Icons.delete_outline, () { IXDeveloperTools.clearLogs(); _updateLogs(); }), ], ), ), // 日志列表 Expanded( child: _filteredLogs.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.inbox_outlined, size: 48, color: Colors.grey[300], ), const SizedBox(height: 12), Text( '暂无日志', style: TextStyle(fontSize: 14, color: Colors.grey[400]), ), ], ), ) : ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: _filteredLogs.length, separatorBuilder: (_, __) => const SizedBox(height: 1), itemBuilder: (context, index) { final log = _filteredLogs[index]; return SimpleLogCard(log: log); }, ), ), ], ); } Widget _buildIconButton(IconData icon, VoidCallback onTap) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Icon(icon, size: 18, color: Colors.grey[600]), ), ); } } /// 简化版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( padding: const EdgeInsets.all(12), child: Row( children: [ // 统计信息 Text( '${requests.length} 个请求', style: TextStyle(fontSize: 13, color: Colors.grey[600]), ), const Spacer(), // 刷新按钮 _buildIconButton(Icons.refresh, () => setState(() {})), const SizedBox(width: 6), // 清除按钮 _buildIconButton(Icons.delete_outline, () { IXDeveloperTools.clearApiRequests(); setState(() {}); }), ], ), ), // API请求列表 Expanded( child: requests.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.cloud_off_outlined, size: 48, color: Colors.grey[300], ), const SizedBox(height: 12), Text( '暂无请求', style: TextStyle(fontSize: 14, color: Colors.grey[400]), ), ], ), ) : ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: requests.length, separatorBuilder: (_, __) => const SizedBox(height: 1), itemBuilder: (context, index) { final request = requests[index]; return SimpleApiCard(request: request); }, ), ), ], ); } Widget _buildIconButton(IconData icon, VoidCallback onTap) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Icon(icon, size: 18, color: Colors.grey[600]), ), ); } }