From 095e9cc464f8a3d11ad83e84f5ef58a3c91ad7f7 Mon Sep 17 00:00:00 2001 From: YuanXuan Date: Mon, 15 Dec 2025 14:01:15 +0800 Subject: [PATCH] chore: release v1.0.4 --- analysis_options.yaml | 13 + debug_test.dart | 13 +- example/pubspec.lock | 24 +- .../yx_net_inspector_controller.dart | 31 +- lib/src/models/inspector_config.dart | 27 +- lib/src/models/inspector_theme.dart | 66 +- lib/src/models/network_log_entry.dart | 42 +- lib/src/widgets/floating_ball.dart | 169 +---- lib/src/widgets/floating_ball_config.dart | 86 +-- lib/src/widgets/inspector_panel.dart | 632 ++++-------------- lib/src/widgets/log_detail_page.dart | 19 +- .../panel/inspector_detail_header.dart | 102 +++ lib/src/widgets/panel/inspector_header.dart | 79 +++ lib/src/widgets/panel/inspector_log_list.dart | 191 ++++++ .../widgets/panel/inspector_search_bar.dart | 48 ++ .../widgets/panel/inspector_statistics.dart | 96 +++ lib/src/yx_net_inspector_app.dart | 67 +- lib/yx_net_inspector.dart | 5 +- pubspec.lock | 30 +- pubspec.yaml | 4 +- test/integration/full_workflow_test.dart | 29 +- test/test_all.dart | 12 +- test/unit/inspector_config_test.dart | 33 +- test/unit/network_log_entry_test.dart | 19 +- .../yx_net_inspector_controller_test.dart | 59 +- test/widget/floating_ball_test.dart | 12 +- test/widget/inspector_panel_test.dart | 18 +- 27 files changed, 981 insertions(+), 945 deletions(-) create mode 100644 analysis_options.yaml create mode 100644 lib/src/widgets/panel/inspector_detail_header.dart create mode 100644 lib/src/widgets/panel/inspector_header.dart create mode 100644 lib/src/widgets/panel/inspector_log_list.dart create mode 100644 lib/src/widgets/panel/inspector_search_bar.dart create mode 100644 lib/src/widgets/panel/inspector_statistics.dart diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..01bb970 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,13 @@ +include: package:very_good_analysis/analysis_options.yaml + +linter: + rules: + public_member_api_docs: false + sort_pub_dependencies: false + +analyzer: + errors: + cascade_invocations: ignore + inference_failure_on_collection_literal: ignore + lines_longer_than_80_chars: ignore # Too strict for existing code + diff --git a/debug_test.dart b/debug_test.dart index 60725cc..0e1e68e 100644 --- a/debug_test.dart +++ b/debug_test.dart @@ -14,19 +14,8 @@ class MyDebugApp extends StatelessWidget { title: 'YX 网络检查器调试', home: YxNetInspector( config: const YxNetInspectorConfig( - showFloatingBall: true, - ballSize: 60, ballColor: Colors.blue, ), - theme: const YxNetInspectorTheme( - primaryColor: Colors.blue, - backgroundColor: Colors.white, - textColor: Colors.black87, - successColor: Colors.green, - errorColor: Colors.red, - warningColor: Colors.orange, - cardColor: Colors.white, - ), child: Scaffold( appBar: AppBar( title: const Text('调试测试'), @@ -55,7 +44,7 @@ class MyDebugApp extends StatelessWidget { statusCode: 200, responseData: const { 'title': 'Test Post', - 'body': 'Test content' + 'body': 'Test content', }, duration: const Duration(seconds: 1), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index 6176505..3234acf 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -87,26 +87,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -196,10 +196,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.4" + version: "0.7.6" typed_data: dependency: transitive description: @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -238,7 +238,7 @@ packages: path: ".." relative: true source: path - version: "1.0.2" + version: "1.0.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/controller/yx_net_inspector_controller.dart b/lib/src/controller/yx_net_inspector_controller.dart index 3335af0..6253772 100644 --- a/lib/src/controller/yx_net_inspector_controller.dart +++ b/lib/src/controller/yx_net_inspector_controller.dart @@ -1,17 +1,17 @@ +// ignore_for_file: prefer_constructors_over_static_methods // Singleton pattern requires static access import 'package:flutter/foundation.dart'; -import '../models/network_log_entry.dart'; -import '../models/inspector_config.dart'; +import 'package:yx_net_inspector/src/models/inspector_config.dart'; +import 'package:yx_net_inspector/src/models/network_log_entry.dart'; /// 网络日志检查器控制器 /// 管理网络日志和检查器状态 class YxNetInspectorController extends ChangeNotifier { + YxNetInspectorController._internal(); static YxNetInspectorController? _instance; static YxNetInspectorController get instance { return _instance ??= YxNetInspectorController._internal(); } - YxNetInspectorController._internal(); - /// 配置信息 late YxNetInspectorConfig _config; YxNetInspectorConfig get config => _config; @@ -35,12 +35,34 @@ class YxNetInspectorController extends ChangeNotifier { bool _showFloatingBall = true; bool get showFloatingBall => _showFloatingBall && _config.isEnabled; + /// 面板显示状态 + bool _isPanelVisible = false; + bool get isPanelVisible => _isPanelVisible && _config.isEnabled; + /// 初始化控制器配置 void initialize(YxNetInspectorConfig config) { _config = config; _showFloatingBall = config.showFloatingBall; } + /// 显示面板 + void showPanel() { + _isPanelVisible = true; + notifyListeners(); + } + + /// 隐藏面板 + void hidePanel() { + _isPanelVisible = false; + notifyListeners(); + } + + /// 切换面板显示状态 + void togglePanel() { + _isPanelVisible = !_isPanelVisible; + notifyListeners(); + } + /// 记录网络请求 void logRequest({ required String id, @@ -61,7 +83,6 @@ class YxNetInspectorController extends ChangeNotifier { queryParameters: queryParameters, timestamp: DateTime.now(), isSuccess: false, // 响应到达时会更新 - status: NetworkRequestStatus.pending, // 初始状态为进行中 ); _logs.insert(0, entry); diff --git a/lib/src/models/inspector_config.dart b/lib/src/models/inspector_config.dart index 37a6be4..239fe07 100644 --- a/lib/src/models/inspector_config.dart +++ b/lib/src/models/inspector_config.dart @@ -2,7 +2,21 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// 网络检查器配置 +@immutable class YxNetInspectorConfig { + const YxNetInspectorConfig({ + this.showFloatingBall = true, + this.ballSize = 60.0, + this.ballColor, + this.showInDebugMode = true, + this.showInReleaseMode = false, + this.maxLogs = 1000, + this.initialPosition, + this.draggable = true, + this.showBadge = true, + this.autoHide = false, + }); + /// 是否显示悬浮球 final bool showFloatingBall; @@ -33,19 +47,6 @@ class YxNetInspectorConfig { /// 是否自动隐藏悬浮球 final bool autoHide; - const YxNetInspectorConfig({ - this.showFloatingBall = true, - this.ballSize = 60.0, - this.ballColor, - this.showInDebugMode = true, - this.showInReleaseMode = false, - this.maxLogs = 1000, - this.initialPosition, - this.draggable = true, - this.showBadge = true, - this.autoHide = false, - }); - /// 根据当前模式判断检查器是否应该启用 bool get isEnabled { if (kDebugMode) { diff --git a/lib/src/models/inspector_theme.dart b/lib/src/models/inspector_theme.dart index 4dc931c..de7eb96 100644 --- a/lib/src/models/inspector_theme.dart +++ b/lib/src/models/inspector_theme.dart @@ -1,7 +1,40 @@ import 'package:flutter/material.dart'; /// 网络检查器主题配置 +@immutable class YxNetInspectorTheme { + const YxNetInspectorTheme({ + this.primaryColor = Colors.blue, + this.backgroundColor = Colors.white, + this.textColor = Colors.black87, + this.secondaryTextColor = Colors.grey, + this.errorColor = Colors.red, + this.successColor = Colors.green, + this.warningColor = Colors.orange, + this.borderColor = const Color(0xFFE0E0E0), + this.cardColor = Colors.white, + this.floatingBallColor, + }); + + /// 创建暗色主题 + factory YxNetInspectorTheme.dark() { + return const YxNetInspectorTheme( + primaryColor: Colors.blueAccent, + backgroundColor: Color(0xFF121212), + textColor: Colors.white, + errorColor: Colors.redAccent, + successColor: Colors.greenAccent, + warningColor: Colors.orangeAccent, + borderColor: Color(0xFF333333), + cardColor: Color(0xFF1E1E1E), + ); + } + + /// 创建亮色主题(默认) + factory YxNetInspectorTheme.light() { + return const YxNetInspectorTheme(); + } + /// 检查器 UI 的主色调 final Color primaryColor; @@ -32,39 +65,6 @@ class YxNetInspectorTheme { /// 悬浮球颜色(如果设置则覆盖配置) final Color? floatingBallColor; - const YxNetInspectorTheme({ - this.primaryColor = Colors.blue, - this.backgroundColor = Colors.white, - this.textColor = Colors.black87, - this.secondaryTextColor = Colors.grey, - this.errorColor = Colors.red, - this.successColor = Colors.green, - this.warningColor = Colors.orange, - this.borderColor = const Color(0xFFE0E0E0), - this.cardColor = Colors.white, - this.floatingBallColor, - }); - - /// 创建暗色主题 - factory YxNetInspectorTheme.dark() { - return const YxNetInspectorTheme( - primaryColor: Colors.blueAccent, - backgroundColor: Color(0xFF121212), - textColor: Colors.white, - secondaryTextColor: Colors.grey, - errorColor: Colors.redAccent, - successColor: Colors.greenAccent, - warningColor: Colors.orangeAccent, - borderColor: Color(0xFF333333), - cardColor: Color(0xFF1E1E1E), - ); - } - - /// 创建亮色主题(默认) - factory YxNetInspectorTheme.light() { - return const YxNetInspectorTheme(); - } - /// 创建一个带有更新值的副本 YxNetInspectorTheme copyWith({ Color? primaryColor, diff --git a/lib/src/models/network_log_entry.dart b/lib/src/models/network_log_entry.dart index 1f80116..c429987 100644 --- a/lib/src/models/network_log_entry.dart +++ b/lib/src/models/network_log_entry.dart @@ -13,7 +13,23 @@ enum NetworkRequestStatus { } /// 网络请求日志条目 +@immutable class NetworkLogEntry { + const NetworkLogEntry({ + required this.id, + required this.method, + required this.url, + required this.timestamp, + required this.isSuccess, + this.headers, + this.requestData, + this.queryParameters, + this.statusCode, + this.responseData, + this.errorMessage, + this.duration, + this.status = NetworkRequestStatus.pending, + }); final String id; final String method; final String url; @@ -28,22 +44,6 @@ class NetworkLogEntry { final bool isSuccess; final NetworkRequestStatus status; - NetworkLogEntry({ - required this.id, - required this.method, - required this.url, - this.headers, - this.requestData, - this.queryParameters, - this.statusCode, - this.responseData, - this.errorMessage, - required this.timestamp, - this.duration, - required this.isSuccess, - this.status = NetworkRequestStatus.pending, - }); - /// 根据请求状态获取状态颜色 Color get statusColor { switch (status) { @@ -70,7 +70,7 @@ class NetworkLogEntry { /// 获取预估请求大小 int get requestSize { - int size = 0; + var size = 0; if (requestData != null) { size += requestData.toString().length; } @@ -102,7 +102,7 @@ class NetworkLogEntry { String get formattedDuration { if (duration == null) return '未知'; final ms = duration!.inMilliseconds; - if (ms < 1000) return '${ms} ms'; + if (ms < 1000) return '$ms ms'; return '${(ms / 1000).toStringAsFixed(1)} s'; } @@ -111,7 +111,7 @@ class NetworkLogEntry { try { final uri = Uri.parse(url); return '${uri.path}${uri.query.isNotEmpty ? '?${uri.query}' : ''}'; - } catch (e) { + } on Object catch (_) { return url; } } @@ -119,8 +119,8 @@ class NetworkLogEntry { String get hostUrl { try { final uri = Uri.parse(url); - return '${uri.host}'; - } catch (e) { + return uri.host; + } on Object catch (_) { return url; } } diff --git a/lib/src/widgets/floating_ball.dart b/lib/src/widgets/floating_ball.dart index adb983a..86f6b5d 100644 --- a/lib/src/widgets/floating_ball.dart +++ b/lib/src/widgets/floating_ball.dart @@ -1,22 +1,17 @@ import 'package:flutter/material.dart'; -import '../controller/yx_net_inspector_controller.dart'; -import '../models/inspector_config.dart'; -import '../models/inspector_theme.dart'; -import 'inspector_panel.dart'; +import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart'; +import 'package:yx_net_inspector/src/models/inspector_config.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; /// 悬浮调试球组件 class YxFloatingBall extends StatefulWidget { + const YxFloatingBall({ + required this.config, required this.theme, required this.controller, super.key, + }); final YxNetInspectorConfig config; final YxNetInspectorTheme theme; final YxNetInspectorController controller; - const YxFloatingBall({ - super.key, - required this.config, - required this.theme, - required this.controller, - }); - @override State createState() => _YxFloatingBallState(); } @@ -28,8 +23,6 @@ class _YxFloatingBallState extends State late Animation _opacityAnimation; Offset _position = const Offset(20, 200); - bool _isExpanded = false; - OverlayEntry? _currentOverlayEntry; @override void initState() { @@ -46,167 +39,23 @@ class _YxFloatingBallState extends State vsync: this, ); - _scaleAnimation = Tween(begin: 1.0, end: 1.2).animate( + _scaleAnimation = Tween(begin: 1, end: 1.2).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); - _opacityAnimation = Tween(begin: 1.0, end: 0.8).animate( + _opacityAnimation = Tween(begin: 1, end: 0.8).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); } @override void dispose() { - // 清理 overlay - if (_currentOverlayEntry != null) { - _currentOverlayEntry!.remove(); - _currentOverlayEntry = null; - } _animationController.dispose(); super.dispose(); } void _onTap() { - if (_isExpanded) { - _hideInspectorPanel(); - } else { - _showInspectorPanel(); - } - } - - void _showInspectorPanel() { - setState(() { - _isExpanded = true; - }); - _animationController.forward(); - - // 优先使用 Overlay,如果失败则使用 Navigator - _showInspectorOverlay(); - } - - void _showInspectorOverlay() { - // 延迟到下一帧执行,确保Widget树已经完全构建 - WidgetsBinding.instance.addPostFrameCallback((_) { - _tryShowOverlay(); - }); - } - - void _tryShowOverlay() { - // 尝试找到 Overlay - OverlayState? overlay; - try { - overlay = Overlay.of(context, rootOverlay: true); - } catch (e) { - debugPrint('YxNetInspector: 根 Overlay 查找失败: $e'); - // 如果 Overlay.of 失败,尝试手动查找 - overlay = _findOverlayInContext(context); - } - - if (overlay == null) { - // 如果仍然找不到 Overlay,使用备选方案 - _showInspectorDialog(); - return; - } - - try { - final overlayEntry = OverlayEntry( - builder: (context) => Material( - color: Colors.black54, - child: Stack( - children: [ - // 背景遮罩 - GestureDetector( - onTap: _hideInspectorPanel, - child: Container( - color: Colors.transparent, - width: double.infinity, - height: double.infinity, - ), - ), - // 检查器面板 - Center( - child: YxInspectorPanel( - theme: widget.theme, - controller: widget.controller, - onClose: _hideInspectorPanel, - ), - ), - ], - ), - ), - ); - - overlay.insert(overlayEntry); - _currentOverlayEntry = overlayEntry; - } catch (e) { - debugPrint('YxNetInspector: 插入 OverlayEntry 失败: $e'); - // 重置状态 - setState(() { - _isExpanded = false; - }); - _animationController.reverse(); - } - } - - OverlayState? _findOverlayInContext(BuildContext context) { - OverlayState? overlayState; - context.visitAncestorElements((element) { - if (element.widget is Overlay) { - overlayState = Overlay.of(element); - return false; // 停止遍历 - } - return true; // 继续向上查找 - }); - return overlayState; - } - - void _showInspectorDialog() { - // 作为最后的备选方案,创建一个自定义的全屏Overlay - // 这里我们不依赖Navigator,而是手动管理显示状态 - _createCustomOverlay(); - } - - void _createCustomOverlay() { - // 如果所有 Overlay 方法都失败,我们显示一个简单的调试信息 - // 并重置状态,避免悬浮球卡在展开状态 - - // 显示一个简单的 SnackBar 或 print 提示 - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - try { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('网络检查器暂时无法显示,请检查应用结构'), - duration: Duration(seconds: 2), - ), - ); - } catch (e) { - // 如果 ScaffoldMessenger 也不可用,只打印日志 - debugPrint('YxNetInspector: 无法显示检查器面板 - 请确保在 MaterialApp 内使用'); - } - - // 重置状态 - setState(() { - _isExpanded = false; - }); - _animationController.reverse(); - } - }); - } - - void _hideInspectorPanel() { - setState(() { - _isExpanded = false; - }); - _animationController.reverse(); - - // 如果有 overlay,移除它 - if (_currentOverlayEntry != null) { - _currentOverlayEntry!.remove(); - _currentOverlayEntry = null; - } - // 注意:如果使用了 Navigator 的 PageRouteBuilder, - // 对话框关闭会由 onClose 回调中的 Navigator.pop() 处理 + widget.controller.togglePanel(); } void _onPanStart(DragStartDetails details) { diff --git a/lib/src/widgets/floating_ball_config.dart b/lib/src/widgets/floating_ball_config.dart index dae47f8..ef3fa41 100644 --- a/lib/src/widgets/floating_ball_config.dart +++ b/lib/src/widgets/floating_ball_config.dart @@ -1,7 +1,50 @@ import 'package:flutter/material.dart'; /// 悬浮调试球配置 +@immutable class YxFloatingBallConfig { + const YxFloatingBallConfig({ + this.size = 60.0, + this.position = const Offset(20, 200), + this.draggable = true, + this.showBadge = true, + this.autoHide = false, + this.autoHideDuration = const Duration(seconds: 5), + this.color, + this.opacity = 1.0, + }); + + /// 创建小型悬浮球配置 + factory YxFloatingBallConfig.small({Offset? position, Color? color}) { + return YxFloatingBallConfig( + size: 40, + position: position ?? const Offset(20, 200), + color: color, + showBadge: false, + ); + } + + /// 创建大型悬浮球配置 + factory YxFloatingBallConfig.large({Offset? position, Color? color}) { + return YxFloatingBallConfig( + size: 80, + position: position ?? const Offset(20, 200), + color: color, + ); + } + + /// 创建最小化悬浮球配置 + factory YxFloatingBallConfig.minimal({Offset? position, Color? color}) { + return YxFloatingBallConfig( + size: 50, + position: position ?? const Offset(20, 200), + color: color, + showBadge: false, + draggable: false, + autoHide: true, + ); + } + /// 悬浮球大小 final double size; @@ -26,49 +69,6 @@ class YxFloatingBallConfig { /// 悬浮球透明度 final double opacity; - const YxFloatingBallConfig({ - this.size = 60.0, - this.position = const Offset(20, 200), - this.draggable = true, - this.showBadge = true, - this.autoHide = false, - this.autoHideDuration = const Duration(seconds: 5), - this.color, - this.opacity = 1.0, - }); - - /// 创建小型悬浮球配置 - factory YxFloatingBallConfig.small({Offset? position, Color? color}) { - return YxFloatingBallConfig( - size: 40.0, - position: position ?? const Offset(20, 200), - color: color, - showBadge: false, - ); - } - - /// 创建大型悬浮球配置 - factory YxFloatingBallConfig.large({Offset? position, Color? color}) { - return YxFloatingBallConfig( - size: 80.0, - position: position ?? const Offset(20, 200), - color: color, - showBadge: true, - ); - } - - /// 创建最小化悬浮球配置 - factory YxFloatingBallConfig.minimal({Offset? position, Color? color}) { - return YxFloatingBallConfig( - size: 50.0, - position: position ?? const Offset(20, 200), - color: color, - showBadge: false, - draggable: false, - autoHide: true, - ); - } - /// 创建一个带有更新值的副本 YxFloatingBallConfig copyWith({ double? size, diff --git a/lib/src/widgets/inspector_panel.dart b/lib/src/widgets/inspector_panel.dart index 01fef3c..137757b 100644 --- a/lib/src/widgets/inspector_panel.dart +++ b/lib/src/widgets/inspector_panel.dart @@ -1,22 +1,26 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import '../controller/yx_net_inspector_controller.dart'; -import '../models/inspector_theme.dart'; -import '../models/network_log_entry.dart'; -import 'log_detail_page.dart'; +import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/models/network_log_entry.dart'; +import 'package:yx_net_inspector/src/widgets/log_detail_page.dart'; +import 'package:yx_net_inspector/src/widgets/panel/inspector_detail_header.dart'; +import 'package:yx_net_inspector/src/widgets/panel/inspector_header.dart'; +import 'package:yx_net_inspector/src/widgets/panel/inspector_log_list.dart'; +import 'package:yx_net_inspector/src/widgets/panel/inspector_search_bar.dart'; +import 'package:yx_net_inspector/src/widgets/panel/inspector_statistics.dart'; /// 网络检查器面板组件 class YxInspectorPanel extends StatefulWidget { - final YxNetInspectorTheme theme; - final YxNetInspectorController controller; - final VoidCallback onClose; - const YxInspectorPanel({ - super.key, required this.theme, required this.controller, required this.onClose, + super.key, }); + final YxNetInspectorTheme theme; + final YxNetInspectorController controller; + final VoidCallback onClose; @override State createState() => _YxInspectorPanelState(); @@ -52,8 +56,14 @@ class _YxInspectorPanelState extends State { }); } + void _toggleFullScreen() { + setState(() { + _isFullScreen = !_isFullScreen; + }); + } + List get _filteredLogs { - List logs = widget.controller.logs; + var logs = widget.controller.logs; // 搜索过滤 if (_searchKeyword.isNotEmpty) { @@ -70,497 +80,8 @@ class _YxInspectorPanelState extends State { return logs; } - @override - Widget build(BuildContext context) { - return ListenableBuilder( - listenable: widget.controller, - builder: (context, child) { - Widget content; - - if (_showDetailPage && _selectedLog != null) { - // 显示详情页面 - content = Column( - children: [ - _buildDetailHeader(), - Expanded( - child: YxLogDetailPage( - log: _selectedLog!, - theme: widget.theme, - ), - ), - ], - ); - } else { - // 显示主列表页面 - content = Column( - children: [ - _buildHeader(), - _buildStatistics(), - _buildSearchBar(), - Expanded(child: _buildLogList()), - ], - ); - } - - if (_isFullScreen) { - // 全屏模式 - 考虑状态栏高度 - return Material( - color: Colors.black.withValues(alpha: 0.5), - child: SafeArea( - child: Container( - width: MediaQuery.of(context).size.width, - height: double.infinity, - color: widget.theme.backgroundColor, - child: content, - ), - ), - ); - } else { - // 对话框模式 - return Dialog( - backgroundColor: Colors.transparent, - child: Container( - width: MediaQuery.of(context).size.width * 0.9, - height: MediaQuery.of(context).size.height * 0.8, - decoration: BoxDecoration( - color: widget.theme.backgroundColor, - borderRadius: BorderRadius.circular(16), - ), - child: content, - ), - ); - } - }, - ); - } - - Widget _buildDetailHeader() { - final actions = [ - IconButton( - onPressed: () => _copyLogDetails(_selectedLog!), - icon: const Icon( - Icons.copy, - color: Colors.white, - ), - tooltip: '复制详情', - ), - IconButton( - onPressed: () { - _isFullScreen = !_isFullScreen; - setState(() {}); - }, - icon: Icon( - _isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, - color: Colors.white, - ), - tooltip: _isFullScreen ? '退出全屏' : '全屏显示', - ), - IconButton( - onPressed: widget.onClose, - icon: const Icon( - Icons.close, - color: Colors.white, - ), - tooltip: '关闭', - ), - ]; - - return _isFullScreen - ? AppBar( - title: Text('请求详情'), - backgroundColor: widget.theme.primaryColor, - foregroundColor: Colors.white, - // excludeHeaderSemantics: false - leading: IconButton( - onPressed: _hideLogDetail, - icon: const Icon( - Icons.arrow_back, - color: Colors.white, - ), - ), - centerTitle: false, - actions: actions, - ) - : Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: widget.theme.primaryColor, - borderRadius: _isFullScreen - ? BorderRadius.zero - : const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - child: Row( - children: [ - IconButton( - onPressed: _hideLogDetail, - icon: const Icon( - Icons.arrow_back, - color: Colors.white, - ), - tooltip: '返回列表', - ), - const Text( - '请求详情', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - ...actions - ], - ), - ); - } - - Widget _buildHeader() { - final actions = [ - IconButton( - onPressed: () { - setState(() { - _isFullScreen = !_isFullScreen; - }); - }, - icon: Icon( - _isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, - color: Colors.white, - ), - tooltip: _isFullScreen ? '退出全屏' : '全屏', - ), - IconButton( - onPressed: () { - widget.controller.clearLogs(); - }, - icon: const Icon(Icons.clear_all, color: Colors.white), - tooltip: '清空日志', - ), - IconButton( - onPressed: widget.onClose, - icon: const Icon(Icons.close, color: Colors.white), - ), - ]; - return _isFullScreen - ? AppBar( - title: Text('网络检查器'), - backgroundColor: widget.theme.primaryColor, - foregroundColor: Colors.white, - centerTitle: false, - actions: actions, - ) - : Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - decoration: BoxDecoration( - color: widget.theme.primaryColor, - borderRadius: _isFullScreen - ? BorderRadius.zero - : const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - child: Row( - children: [ - const Icon(Icons.network_check, color: Colors.white, size: 24), - const SizedBox(width: 8), - const Text( - '网络检查器', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - ...actions - ], - ), - ); - } - - Widget _buildStatistics() { - final stats = widget.controller.getStatistics(); - final totalRequests = stats['totalRequests'] as int; - final errorRequests = stats['errorRequests'] as int; - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: widget.theme.backgroundColor, - border: Border( - bottom: BorderSide( - color: widget.theme.secondaryTextColor.withValues(alpha: 0.2), - width: 1, - ), - ), - ), - child: Row( - children: [ - // 简化统计 - 只显示关键信息 - Text( - '总请求: $totalRequests', - style: TextStyle( - fontSize: 14, - color: widget.theme.textColor, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(width: 16), - if (errorRequests > 0) ...[ - Icon( - Icons.error_outline, - size: 16, - color: widget.theme.errorColor, - ), - const SizedBox(width: 4), - Text( - '错误: $errorRequests', - style: TextStyle( - fontSize: 14, - color: widget.theme.errorColor, - fontWeight: FontWeight.w600, - ), - ), - ] else ...[ - Icon( - Icons.check_circle_outline, - size: 16, - color: widget.theme.successColor, - ), - const SizedBox(width: 4), - Text( - '全部成功', - style: TextStyle( - fontSize: 14, - color: widget.theme.successColor, - fontWeight: FontWeight.w500, - ), - ), - ], - const Spacer(), - // 快速过滤按钮 - if (errorRequests > 0) - TextButton.icon( - onPressed: () { - setState(() { - _showOnlyErrors = !_showOnlyErrors; - }); - }, - icon: Icon( - _showOnlyErrors ? Icons.clear : Icons.filter_list, - size: 16, - ), - label: Text(_showOnlyErrors ? '显示全部' : '仅错误'), - style: TextButton.styleFrom( - foregroundColor: widget.theme.errorColor, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - minimumSize: Size.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - ), - ], - ), - ); - } - - Widget _buildSearchBar() { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _searchController, - style: TextStyle(color: widget.theme.textColor), - decoration: InputDecoration( - hintText: '搜索请求...', - hintStyle: TextStyle(color: widget.theme.secondaryTextColor), - prefixIcon: Icon( - Icons.search, - color: widget.theme.secondaryTextColor, - ), - border: OutlineInputBorder( - borderSide: BorderSide(color: widget.theme.borderColor), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - ), - onChanged: (value) { - setState(() { - _searchKeyword = value; - }); - }, - ), - ), - ], - ), - ); - } - - Widget _buildLogList() { - final logs = _filteredLogs; - - if (logs.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.inbox, size: 64, color: widget.theme.secondaryTextColor), - const SizedBox(height: 16), - Text( - '未找到网络请求', - style: TextStyle( - fontSize: 16, - color: widget.theme.secondaryTextColor, - ), - ), - ], - ), - ); - } - - return ListView.builder( - itemCount: logs.length, - itemBuilder: (context, index) { - final log = logs[index]; - return _buildLogItem(log); - }, - ); - } - - Widget _buildLogItem(NetworkLogEntry log) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), - decoration: BoxDecoration( - color: widget.theme.cardColor, - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: log.status == NetworkRequestStatus.failed - ? widget.theme.errorColor.withValues(alpha: 0.3) - : Colors.transparent, - width: log.status == NetworkRequestStatus.failed ? 1 : 0, - ), - ), - child: InkWell( - onTap: () { - _showLogDetail(log); - }, - borderRadius: BorderRadius.circular(6), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: Row( - children: [ - // URL和状态 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - log.displayUrl, - style: TextStyle( - fontSize: 13, - color: widget.theme.textColor, - fontWeight: FontWeight.w500, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Row( - children: [ - // 状态指示器 - Container( - width: 6, - height: 6, - decoration: BoxDecoration( - color: log.statusColor, - shape: BoxShape.circle, - ), - ), - const SizedBox(width: 8), - - // 方法标签 - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: _getMethodColor(log.method) - .withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), - ), - child: Text( - log.method, - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: _getMethodColor(log.method), - ), - ), - ), - const SizedBox(width: 8), - - Text( - '${log.statusCode ?? "?"} ${log.formattedDuration}', - style: TextStyle( - fontSize: 11, - color: log.statusColor, - fontWeight: FontWeight.w500, - ), - ), - if (log.errorMessage != null) ...[ - const SizedBox(width: 8), - Icon( - Icons.error_outline, - size: 12, - color: widget.theme.errorColor, - ), - ], - Spacer(), - // 时间 - Text( - log.formattedTime.contains(' ') - ? log.formattedTime.split(' ')[1] - : log.formattedTime, // 只显示时间部分 - style: TextStyle( - fontSize: 10, - color: widget.theme.secondaryTextColor, - ), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ), - ); - } - - Color _getMethodColor(String method) { - switch (method.toUpperCase()) { - case 'GET': - return Colors.blue; - case 'POST': - return Colors.green; - case 'PUT': - return Colors.orange; - case 'DELETE': - return Colors.red; - case 'PATCH': - return Colors.purple; - default: - return widget.theme.secondaryTextColor; - } - } - void _copyLogDetails(NetworkLogEntry log) { + // Note: This logic could be moved to util or DetailPage, but keeping here for now as callback final details = ''' 网络请求详情 ======================= @@ -600,7 +121,7 @@ ${log.duration != null ? '- 结束时间: ${_formatDateTime(log.timestamp.add(lo } else if (requestData is Map) { try { return requestData.toString(); - } catch (e) { + } on Object catch (_) { return requestData.toString(); } } else { @@ -609,7 +130,112 @@ ${log.duration != null ? '- 结束时间: ${_formatDateTime(log.timestamp.add(lo } String _formatDateTime(DateTime dateTime) { - return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' - '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}'; + return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-' + '${dateTime.day.toString().padLeft(2, '0')} ' + '${dateTime.hour.toString().padLeft(2, '0')}:' + '${dateTime.minute.toString().padLeft(2, '0')}:' + '${dateTime.second.toString().padLeft(2, '0')}'; + } + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: widget.controller, + builder: (context, child) { + Widget content; + + if (_showDetailPage && _selectedLog != null) { + // 显示详情页面 + content = Column( + children: [ + InspectorDetailHeader( + theme: widget.theme, + isFullScreen: _isFullScreen, + onToggleFullScreen: _toggleFullScreen, + onClose: widget.onClose, + onBack: _hideLogDetail, + onCopy: () => _copyLogDetails(_selectedLog!), + ), + Expanded( + child: YxLogDetailPage( + log: _selectedLog!, + theme: widget.theme, + ), + ), + ], + ); + } else { + // 显示主列表页面 + final stats = widget.controller.getStatistics(); + content = Column( + children: [ + InspectorHeader( + theme: widget.theme, + isFullScreen: _isFullScreen, + onToggleFullScreen: _toggleFullScreen, + onClearLogs: widget.controller.clearLogs, + onClose: widget.onClose, + ), + InspectorStatistics( + theme: widget.theme, + totalRequests: stats['totalRequests'] as int? ?? 0, + errorRequests: stats['errorRequests'] as int? ?? 0, + showOnlyErrors: _showOnlyErrors, + onToggleFilter: () { + setState(() { + _showOnlyErrors = !_showOnlyErrors; + }); + }, + ), + InspectorSearchBar( + theme: widget.theme, + controller: _searchController, + onChanged: (value) { + setState(() { + _searchKeyword = value; + }); + }, + ), + Expanded( + child: InspectorLogList( + logs: _filteredLogs, + theme: widget.theme, + onLogTap: _showLogDetail, + ), + ), + ], + ); + } + + if (_isFullScreen) { + // 全屏模式 - 考虑状态栏高度 + return Material( + color: Colors.black.withValues(alpha: 0.5), + child: SafeArea( + child: Container( + width: MediaQuery.of(context).size.width, + height: double.infinity, + color: widget.theme.backgroundColor, + child: content, + ), + ), + ); + } else { + // 对话框模式 + return Dialog( + backgroundColor: Colors.transparent, + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + height: MediaQuery.of(context).size.height * 0.8, + decoration: BoxDecoration( + color: widget.theme.backgroundColor, + borderRadius: BorderRadius.circular(16), + ), + child: content, + ), + ); + } + }, + ); } } diff --git a/lib/src/widgets/log_detail_page.dart b/lib/src/widgets/log_detail_page.dart index c18f735..9c31d69 100644 --- a/lib/src/widgets/log_detail_page.dart +++ b/lib/src/widgets/log_detail_page.dart @@ -1,18 +1,18 @@ import 'dart:convert'; + import 'package:flutter/material.dart'; -import '../models/network_log_entry.dart'; -import '../models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/models/network_log_entry.dart'; /// 网络日志详情页面 class YxLogDetailPage extends StatelessWidget { + const YxLogDetailPage({required this.log, required this.theme, super.key}); final NetworkLogEntry log; final YxNetInspectorTheme theme; - const YxLogDetailPage({super.key, required this.log, required this.theme}); - @override Widget build(BuildContext context) { - return Container( + return ColoredBox( color: theme.backgroundColor, child: SingleChildScrollView( padding: const EdgeInsets.all(16), @@ -372,7 +372,7 @@ class YxLogDetailPage extends StatelessWidget { } else if (requestData is Map) { try { return const JsonEncoder.withIndent(' ').convert(requestData); - } catch (e) { + } on Object catch (_) { return requestData.toString(); } } else { @@ -381,7 +381,10 @@ class YxLogDetailPage extends StatelessWidget { } String _formatDateTime(DateTime dateTime) { - return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' - '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}:${dateTime.second.toString().padLeft(2, '0')}'; + return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-' + '${dateTime.day.toString().padLeft(2, '0')} ' + '${dateTime.hour.toString().padLeft(2, '0')}:' + '${dateTime.minute.toString().padLeft(2, '0')}:' + '${dateTime.second.toString().padLeft(2, '0')}'; } } diff --git a/lib/src/widgets/panel/inspector_detail_header.dart b/lib/src/widgets/panel/inspector_detail_header.dart new file mode 100644 index 0000000..4a4ec29 --- /dev/null +++ b/lib/src/widgets/panel/inspector_detail_header.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; + +class InspectorDetailHeader extends StatelessWidget { + const InspectorDetailHeader({ + required this.theme, + required this.isFullScreen, + required this.onToggleFullScreen, + required this.onClose, + required this.onBack, + required this.onCopy, + super.key, + }); + + final YxNetInspectorTheme theme; + final bool isFullScreen; + final VoidCallback onToggleFullScreen; + final VoidCallback onClose; + final VoidCallback onBack; + final VoidCallback onCopy; + + @override + Widget build(BuildContext context) { + final actions = [ + IconButton( + onPressed: onCopy, + icon: const Icon( + Icons.copy, + color: Colors.white, + ), + tooltip: '复制详情', + ), + IconButton( + onPressed: onToggleFullScreen, + icon: Icon( + isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, + color: Colors.white, + ), + tooltip: isFullScreen ? '退出全屏' : '全屏显示', + ), + IconButton( + onPressed: onClose, + icon: const Icon( + Icons.close, + color: Colors.white, + ), + tooltip: '关闭', + ), + ]; + + return isFullScreen + ? AppBar( + title: const Text('请求详情'), + backgroundColor: theme.primaryColor, + foregroundColor: Colors.white, + // excludeHeaderSemantics: false + leading: IconButton( + onPressed: onBack, + icon: const Icon( + Icons.arrow_back, + color: Colors.white, + ), + ), + centerTitle: false, + actions: actions, + ) + : Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.primaryColor, + borderRadius: isFullScreen + ? BorderRadius.zero + : const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + IconButton( + onPressed: onBack, + icon: const Icon( + Icons.arrow_back, + color: Colors.white, + ), + tooltip: '返回列表', + ), + const Text( + '请求详情', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + ...actions, + ], + ), + ); + } +} diff --git a/lib/src/widgets/panel/inspector_header.dart b/lib/src/widgets/panel/inspector_header.dart new file mode 100644 index 0000000..9f9b245 --- /dev/null +++ b/lib/src/widgets/panel/inspector_header.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; + +class InspectorHeader extends StatelessWidget { + const InspectorHeader({ + required this.theme, + required this.isFullScreen, + required this.onToggleFullScreen, + required this.onClearLogs, + required this.onClose, + super.key, + }); + + final YxNetInspectorTheme theme; + final bool isFullScreen; + final VoidCallback onToggleFullScreen; + final VoidCallback onClearLogs; + final VoidCallback onClose; + + @override + Widget build(BuildContext context) { + final actions = [ + IconButton( + onPressed: onToggleFullScreen, + icon: Icon( + isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, + color: Colors.white, + ), + tooltip: isFullScreen ? '退出全屏' : '全屏', + ), + IconButton( + onPressed: onClearLogs, + icon: const Icon(Icons.clear_all, color: Colors.white), + tooltip: '清空日志', + ), + IconButton( + onPressed: onClose, + icon: const Icon(Icons.close, color: Colors.white), + ), + ]; + + return isFullScreen + ? AppBar( + title: const Text('网络检查器'), + backgroundColor: theme.primaryColor, + foregroundColor: Colors.white, + centerTitle: false, + actions: actions, + ) + : Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + decoration: BoxDecoration( + color: theme.primaryColor, + borderRadius: isFullScreen + ? BorderRadius.zero + : const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + const Icon(Icons.network_check, color: Colors.white, size: 24), + const SizedBox(width: 8), + const Text( + '网络检查器', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + ...actions, + ], + ), + ); + } +} diff --git a/lib/src/widgets/panel/inspector_log_list.dart b/lib/src/widgets/panel/inspector_log_list.dart new file mode 100644 index 0000000..6cfc697 --- /dev/null +++ b/lib/src/widgets/panel/inspector_log_list.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/models/network_log_entry.dart'; + +class InspectorLogList extends StatelessWidget { + const InspectorLogList({ + required this.logs, + required this.theme, + required this.onLogTap, + super.key, + }); + + final List logs; + final YxNetInspectorTheme theme; + final ValueChanged onLogTap; + + @override + Widget build(BuildContext context) { + if (logs.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.inbox, size: 64, color: theme.secondaryTextColor), + const SizedBox(height: 16), + Text( + '未找到网络请求', + style: TextStyle( + fontSize: 16, + color: theme.secondaryTextColor, + ), + ), + ], + ), + ); + } + + return ListView.builder( + itemCount: logs.length, + itemBuilder: (context, index) { + final log = logs[index]; + return InspectorLogItem( + log: log, + theme: theme, + onTap: () => onLogTap(log), + ); + }, + ); + } +} + +class InspectorLogItem extends StatelessWidget { + const InspectorLogItem({ + required this.log, + required this.theme, + required this.onTap, + super.key, + }); + + final NetworkLogEntry log; + final YxNetInspectorTheme theme; + final VoidCallback onTap; + + Color _getMethodColor(String method) { + switch (method.toUpperCase()) { + case 'GET': + return Colors.blue; + case 'POST': + return Colors.green; + case 'PUT': + return Colors.orange; + case 'DELETE': + return Colors.red; + case 'PATCH': + return Colors.purple; + default: + return theme.secondaryTextColor; + } + } + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + decoration: BoxDecoration( + color: theme.cardColor, + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: log.status == NetworkRequestStatus.failed + ? theme.errorColor.withValues(alpha: 0.3) + : Colors.transparent, + width: log.status == NetworkRequestStatus.failed ? 1 : 0, + ), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(6), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: Row( + children: [ + // URL和状态 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + log.displayUrl, + style: TextStyle( + fontSize: 13, + color: theme.textColor, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Row( + children: [ + // 状态指示器 + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: log.statusColor, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + + // 方法标签 + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: _getMethodColor(log.method) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + log.method, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: _getMethodColor(log.method), + ), + ), + ), + const SizedBox(width: 8), + + Text( + '${log.statusCode ?? "?"} ${log.formattedDuration}', + style: TextStyle( + fontSize: 11, + color: log.statusColor, + fontWeight: FontWeight.w500, + ), + ), + if (log.errorMessage != null) ...[ + const SizedBox(width: 8), + Icon( + Icons.error_outline, + size: 12, + color: theme.errorColor, + ), + ], + const Spacer(), + // 时间 - Fix: Use split safely or standard formatting + Text( + log.formattedTime.contains(' ') + ? log.formattedTime.split(' ')[1] + : log.formattedTime, + style: TextStyle( + fontSize: 10, + color: theme.secondaryTextColor, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/widgets/panel/inspector_search_bar.dart b/lib/src/widgets/panel/inspector_search_bar.dart new file mode 100644 index 0000000..5dd3964 --- /dev/null +++ b/lib/src/widgets/panel/inspector_search_bar.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; + +class InspectorSearchBar extends StatelessWidget { + const InspectorSearchBar({ + required this.theme, + required this.controller, + required this.onChanged, + super.key, + }); + + final YxNetInspectorTheme theme; + final TextEditingController controller; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: TextField( + controller: controller, + style: TextStyle(color: theme.textColor), + decoration: InputDecoration( + hintText: '搜索请求...', + hintStyle: TextStyle(color: theme.secondaryTextColor), + prefixIcon: Icon( + Icons.search, + color: theme.secondaryTextColor, + ), + border: OutlineInputBorder( + borderSide: BorderSide(color: theme.borderColor), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + onChanged: onChanged, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/widgets/panel/inspector_statistics.dart b/lib/src/widgets/panel/inspector_statistics.dart new file mode 100644 index 0000000..7102bc5 --- /dev/null +++ b/lib/src/widgets/panel/inspector_statistics.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; + +class InspectorStatistics extends StatelessWidget { + const InspectorStatistics({ + required this.theme, + required this.totalRequests, + required this.errorRequests, + required this.showOnlyErrors, + required this.onToggleFilter, + super.key, + }); + + final YxNetInspectorTheme theme; + final int totalRequests; + final int errorRequests; + final bool showOnlyErrors; + final VoidCallback onToggleFilter; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: theme.backgroundColor, + border: Border( + bottom: BorderSide( + color: theme.secondaryTextColor.withValues(alpha: 0.2), + ), + ), + ), + child: Row( + children: [ + // 简化统计 - 只显示关键信息 + Text( + '总请求: $totalRequests', + style: TextStyle( + fontSize: 14, + color: theme.textColor, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 16), + if (errorRequests > 0) ...[ + Icon( + Icons.error_outline, + size: 16, + color: theme.errorColor, + ), + const SizedBox(width: 4), + Text( + '错误: $errorRequests', + style: TextStyle( + fontSize: 14, + color: theme.errorColor, + fontWeight: FontWeight.w600, + ), + ), + ] else ...[ + Icon( + Icons.check_circle_outline, + size: 16, + color: theme.successColor, + ), + const SizedBox(width: 4), + Text( + '全部成功', + style: TextStyle( + fontSize: 14, + color: theme.successColor, + fontWeight: FontWeight.w500, + ), + ), + ], + const Spacer(), + // 快速过滤按钮 + if (errorRequests > 0) + TextButton.icon( + onPressed: onToggleFilter, + icon: Icon( + showOnlyErrors ? Icons.clear : Icons.filter_list, + size: 16, + ), + label: Text(showOnlyErrors ? '显示全部' : '仅错误'), + style: TextButton.styleFrom( + foregroundColor: theme.errorColor, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/yx_net_inspector_app.dart b/lib/src/yx_net_inspector_app.dart index f59fd20..b37b2b4 100644 --- a/lib/src/yx_net_inspector_app.dart +++ b/lib/src/yx_net_inspector_app.dart @@ -1,24 +1,15 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'controller/yx_net_inspector_controller.dart'; -import 'models/inspector_config.dart'; -import 'models/inspector_theme.dart'; -import 'widgets/floating_ball.dart'; +import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart'; +import 'package:yx_net_inspector/src/models/inspector_config.dart'; +import 'package:yx_net_inspector/src/models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/widgets/floating_ball.dart'; +import 'package:yx_net_inspector/src/widgets/inspector_panel.dart'; /// 包装你的应用并提供网络检查功能的主要组件 class YxNetInspector extends StatefulWidget { - /// 你的应用组件 - final Widget child; - - /// 检查器配置 - final YxNetInspectorConfig config; - - /// 检查器主题 - final YxNetInspectorTheme theme; const YxNetInspector({ - super.key, - required this.child, + required this.child, super.key, this.config = const YxNetInspectorConfig(), this.theme = const YxNetInspectorTheme(), }); @@ -51,6 +42,14 @@ class YxNetInspector extends StatefulWidget { child: child, ); } + /// 你的应用组件 + final Widget child; + + /// 检查器配置 + final YxNetInspectorConfig config; + + /// 检查器主题 + final YxNetInspectorTheme theme; @override State createState() => _YxNetInspectorState(); @@ -97,6 +96,44 @@ class _YxNetInspectorState extends State { ); }, ), + + // 检查器面板覆盖层 + ListenableBuilder( + listenable: _controller, + builder: (context, child) { + if (!_controller.isPanelVisible) { + return const SizedBox.shrink(); + } + + return Overlay( + initialEntries: [ + OverlayEntry( + builder: (context) => Stack( + children: [ + // 背景遮罩 + GestureDetector( + onTap: _controller.hidePanel, + child: Container( + color: Colors.black54, + width: double.infinity, + height: double.infinity, + ), + ), + // 检查器面板 + Center( + child: YxInspectorPanel( + theme: widget.theme, + controller: _controller, + onClose: _controller.hidePanel, + ), + ), + ], + ), + ), + ], + ); + }, + ), ], ); } diff --git a/lib/yx_net_inspector.dart b/lib/yx_net_inspector.dart index 425ead8..e403fbc 100644 --- a/lib/yx_net_inspector.dart +++ b/lib/yx_net_inspector.dart @@ -1,12 +1,11 @@ -library yx_net_inspector; // 核心导出 -export 'src/yx_net_inspector_app.dart' show YxNetInspector; export 'src/controller/yx_net_inspector_controller.dart'; -export 'src/models/network_log_entry.dart'; export 'src/models/inspector_config.dart'; export 'src/models/inspector_theme.dart'; +export 'src/models/network_log_entry.dart'; export 'src/widgets/floating_ball_config.dart'; +export 'src/yx_net_inspector_app.dart' show YxNetInspector; // Dio 拦截器需要单独导入: // import 'package:yx_net_inspector/src/interceptors/dio_interceptor.dart'; diff --git a/pubspec.lock b/pubspec.lock index c7dd4c9..8c256a9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -63,26 +63,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" matcher: dependency: transitive description: @@ -164,18 +164,26 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.4" + version: "0.7.6" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.0" vm_service: dependency: transitive description: @@ -185,5 +193,5 @@ packages: source: hosted version: "15.0.0" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index a9e9b1c..b4861fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: yx_net_inspector description: A powerful network inspector with floating debug ball for Flutter apps. Monitor HTTP requests, responses, and debug network issues in real-time. -version: 1.0.3 +version: 1.0.4 homepage: https://github.com/your-username/yx_net_inspector environment: @@ -15,4 +15,6 @@ dev_dependencies: flutter_test: sdk: flutter + very_good_analysis: ^7.0.0 + diff --git a/test/integration/full_workflow_test.dart b/test/integration/full_workflow_test.dart index 7c450ee..7494b8e 100644 --- a/test/integration/full_workflow_test.dart +++ b/test/integration/full_workflow_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:yx_net_inspector/yx_net_inspector.dart'; import 'package:yx_net_inspector/src/widgets/floating_ball.dart'; +import 'package:yx_net_inspector/yx_net_inspector.dart'; void main() { group('YX Net Inspector 集成测试', () { @@ -9,12 +9,6 @@ void main() { // 创建测试应用 await tester.pumpWidget( YxNetInspector( - config: const YxNetInspectorConfig( - showFloatingBall: true, - ballSize: 60.0, - showInDebugMode: true, - ), - theme: const YxNetInspectorTheme(), child: MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('测试应用')), @@ -52,8 +46,8 @@ void main() { statusCode: 200, responseData: { 'users': [ - {'id': 1, 'name': 'John'} - ] + {'id': 1, 'name': 'John'}, + ], }, duration: const Duration(milliseconds: 300), ); @@ -146,10 +140,6 @@ void main() { testWidgets('悬浮球拖拽和位置测试', (WidgetTester tester) async { await tester.pumpWidget( YxNetInspector( - config: const YxNetInspectorConfig( - showFloatingBall: true, - draggable: true, - ), child: MaterialApp( home: Scaffold( body: Container(), @@ -179,13 +169,13 @@ void main() { testWidgets('配置禁用时不显示悬浮球', (WidgetTester tester) async { await tester.pumpWidget( - YxNetInspector( - config: const YxNetInspectorConfig( + const YxNetInspector( + config: YxNetInspectorConfig( showFloatingBall: false, ), child: MaterialApp( home: Scaffold( - body: const Text('Test App'), + body: Text('Test App'), ), ), ), @@ -203,7 +193,6 @@ void main() { testWidgets('主题配置测试', (WidgetTester tester) async { await tester.pumpWidget( YxNetInspector( - config: const YxNetInspectorConfig(), theme: const YxNetInspectorTheme( primaryColor: Colors.purple, backgroundColor: Colors.black, @@ -222,7 +211,7 @@ void main() { // 添加一些日志 final controller = YxNetInspectorController.instance; controller.logRequest( - id: 'test', method: 'GET', url: 'https://example.com'); + id: 'test', method: 'GET', url: 'https://example.com',); await tester.pump(); @@ -253,7 +242,7 @@ void main() { controller.clearLogs(); // 添加大量日志 - for (int i = 0; i < 150; i++) { + for (var i = 0; i < 150; i++) { controller.logRequest( id: 'test-$i', method: 'GET', @@ -302,7 +291,7 @@ void main() { controller.clearLogs(); // 添加超过限制的日志 - for (int i = 0; i < 20; i++) { + for (var i = 0; i < 20; i++) { controller.logRequest( id: 'memory-test-$i', method: 'GET', diff --git a/test/test_all.dart b/test/test_all.dart index 1917cda..cb67bd8 100644 --- a/test/test_all.dart +++ b/test/test_all.dart @@ -1,18 +1,16 @@ // 测试入口文件 - 运行所有测试 import 'package:flutter_test/flutter_test.dart'; +// 集成测试 +import 'integration/full_workflow_test.dart' as integration_test; +import 'unit/inspector_config_test.dart' as config_test; // 单元测试 import 'unit/network_log_entry_test.dart' as network_log_entry_test; import 'unit/yx_net_inspector_controller_test.dart' as controller_test; -import 'unit/inspector_config_test.dart' as config_test; - // Widget测试 import 'widget/floating_ball_test.dart' as floating_ball_test; import 'widget/inspector_panel_test.dart' as inspector_panel_test; -// 集成测试 -import 'integration/full_workflow_test.dart' as integration_test; - void main() { group('YX Net Inspector 完整测试套件', () { group('单元测试', () { @@ -26,8 +24,6 @@ void main() { inspector_panel_test.main(); }); - group('集成测试', () { - integration_test.main(); - }); + group('集成测试', integration_test.main); }); } diff --git a/test/unit/inspector_config_test.dart b/test/unit/inspector_config_test.dart index a7a59bc..560e2a6 100644 --- a/test/unit/inspector_config_test.dart +++ b/test/unit/inspector_config_test.dart @@ -23,7 +23,7 @@ void main() { test('应该允许自定义配置', () { const config = YxNetInspectorConfig( showFloatingBall: false, - ballSize: 80.0, + ballSize: 80, ballColor: Colors.red, showInDebugMode: false, showInReleaseMode: true, @@ -51,8 +51,7 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.android; const configEnabledInDebug = YxNetInspectorConfig( - showInDebugMode: true, - showInReleaseMode: false, + ); const configDisabledInDebug = YxNetInspectorConfig( @@ -75,27 +74,24 @@ void main() { test('copyWith 应该正确创建副本', () { const originalConfig = YxNetInspectorConfig( - showFloatingBall: true, - ballSize: 60.0, - maxLogs: 1000, + ); final copiedConfig = originalConfig.copyWith( showFloatingBall: false, - ballSize: 80.0, + ballSize: 80, ); expect(copiedConfig.showFloatingBall, isFalse); expect(copiedConfig.ballSize, equals(80.0)); expect(copiedConfig.maxLogs, equals(1000)); // 未更改的值应该保持原样 expect( - copiedConfig.showInDebugMode, equals(originalConfig.showInDebugMode)); + copiedConfig.showInDebugMode, equals(originalConfig.showInDebugMode),); }); test('toString 应该返回有用的字符串表示', () { const config = YxNetInspectorConfig( - showFloatingBall: true, - ballSize: 70.0, + ballSize: 70, ); final string = config.toString(); @@ -106,21 +102,15 @@ void main() { test('相等性比较应该正确工作', () { const config1 = YxNetInspectorConfig( - showFloatingBall: true, - ballSize: 60.0, - maxLogs: 1000, + ); const config2 = YxNetInspectorConfig( - showFloatingBall: true, - ballSize: 60.0, - maxLogs: 1000, + ); const config3 = YxNetInspectorConfig( showFloatingBall: false, // 不同的值 - ballSize: 60.0, - maxLogs: 1000, ); expect(config1, equals(config2)); @@ -130,9 +120,8 @@ void main() { }); test('空值参数应该正确处理', () { - final config = YxNetInspectorConfig().copyWith( - ballColor: null, - initialPosition: null, + final config = const YxNetInspectorConfig().copyWith( + ); expect(config.ballColor, isNull); @@ -141,7 +130,7 @@ void main() { test('边界值应该正确处理', () { const config = YxNetInspectorConfig( - ballSize: 0.0, // 最小值 + ballSize: 0, // 最小值 maxLogs: 1, // 最小日志数 ); diff --git a/test/unit/network_log_entry_test.dart b/test/unit/network_log_entry_test.dart index c0769c9..28122df 100644 --- a/test/unit/network_log_entry_test.dart +++ b/test/unit/network_log_entry_test.dart @@ -1,5 +1,5 @@ -import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:yx_net_inspector/src/models/network_log_entry.dart'; void main() { @@ -12,12 +12,11 @@ void main() { id: 'test-1', method: 'GET', url: 'https://api.example.com/users?page=1&limit=20', - headers: {'Authorization': 'Bearer token'}, - requestData: null, - queryParameters: {'page': '1', 'limit': '20'}, + headers: const {'Authorization': 'Bearer token'}, + queryParameters: const {'page': '1', 'limit': '20'}, statusCode: 200, - responseData: {'users': []}, - timestamp: DateTime(2024, 1, 1, 12, 0, 0), + responseData: const {'users': []}, + timestamp: DateTime(2024, 1, 1, 12), duration: const Duration(milliseconds: 500), isSuccess: true, ); @@ -26,11 +25,11 @@ void main() { id: 'test-2', method: 'POST', url: 'https://api.example.com/users', - headers: {'Content-Type': 'application/json'}, - requestData: {'name': 'Test User'}, + headers: const {'Content-Type': 'application/json'}, + requestData: const {'name': 'Test User'}, statusCode: 404, errorMessage: '用户不存在', - timestamp: DateTime(2024, 1, 1, 12, 5, 0), + timestamp: DateTime(2024, 1, 1, 12, 5), duration: const Duration(milliseconds: 1500), isSuccess: false, ); @@ -65,7 +64,7 @@ void main() { test('requestSize 应该计算正确的请求大小', () { expect(successfulEntry.requestSize, greaterThan(0)); expect(errorEntry.requestSize, - greaterThanOrEqualTo(successfulEntry.requestSize)); + greaterThanOrEqualTo(successfulEntry.requestSize),); }); test('responseSize 应该计算正确的响应大小', () { diff --git a/test/unit/yx_net_inspector_controller_test.dart b/test/unit/yx_net_inspector_controller_test.dart index 48602db..910aa4e 100644 --- a/test/unit/yx_net_inspector_controller_test.dart +++ b/test/unit/yx_net_inspector_controller_test.dart @@ -22,7 +22,7 @@ void main() { }); test('初始化后应该有正确的默认值', () { - final config = YxNetInspectorConfig(); + const config = YxNetInspectorConfig(); controller.initialize(config); expect(controller.logs, isEmpty); @@ -33,7 +33,7 @@ void main() { }); test('logRequest 应该正确添加请求日志', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); controller.logRequest( id: 'test-1', @@ -50,7 +50,7 @@ void main() { }); test('logResponse 应该正确更新请求日志', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); // 先添加请求 controller.logRequest( @@ -72,11 +72,11 @@ void main() { expect(controller.logs.first.statusCode, equals(200)); expect(controller.logs.first.isSuccess, isTrue); expect(controller.logs.first.duration, - equals(const Duration(milliseconds: 500))); + equals(const Duration(milliseconds: 500)),); }); test('logError 应该正确处理错误日志', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); // 先添加请求 controller.logRequest( @@ -101,11 +101,11 @@ void main() { }); test('clearLogs 应该清空所有日志和统计', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); // 添加一些日志 controller.logRequest( - id: 'test-1', method: 'GET', url: 'https://example.com'); + id: 'test-1', method: 'GET', url: 'https://example.com',); controller.logResponse(id: 'test-1', statusCode: 200); expect(controller.logs, isNotEmpty); @@ -122,7 +122,7 @@ void main() { }); test('悬浮球显示状态应该正确切换', () { - controller.initialize(YxNetInspectorConfig(showFloatingBall: true)); + controller.initialize(const YxNetInspectorConfig()); expect(controller.showFloatingBall, isTrue); @@ -137,23 +137,23 @@ void main() { }); test('getStatistics 应该返回正确的统计信息', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); // 添加成功请求 controller.logRequest( - id: 'success', method: 'GET', url: 'https://example.com'); + id: 'success', method: 'GET', url: 'https://example.com',); controller.logResponse( id: 'success', statusCode: 200, - duration: const Duration(milliseconds: 300)); + duration: const Duration(milliseconds: 300),); // 添加失败请求 controller.logRequest( - id: 'error', method: 'POST', url: 'https://example.com'); + id: 'error', method: 'POST', url: 'https://example.com',); controller.logError( id: 'error', error: '错误', - duration: const Duration(milliseconds: 500)); + duration: const Duration(milliseconds: 500),); final stats = controller.getStatistics(); @@ -165,12 +165,12 @@ void main() { }); test('getRecentLogs 应该返回指定数量的最近日志', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); // 添加多个日志 - for (int i = 0; i < 10; i++) { + for (var i = 0; i < 10; i++) { controller.logRequest( - id: 'test-$i', method: 'GET', url: 'https://example.com/$i'); + id: 'test-$i', method: 'GET', url: 'https://example.com/$i',); } final recentLogs = controller.getRecentLogs(count: 5); @@ -179,15 +179,15 @@ void main() { }); test('getLogsByStatus 应该正确过滤日志', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); // 添加成功和失败的请求 controller.logRequest( - id: 'success', method: 'GET', url: 'https://example.com'); + id: 'success', method: 'GET', url: 'https://example.com',); controller.logResponse(id: 'success', statusCode: 200); controller.logRequest( - id: 'error', method: 'POST', url: 'https://example.com'); + id: 'error', method: 'POST', url: 'https://example.com',); controller.logError(id: 'error', error: '错误'); final successLogs = controller.getLogsByStatus(isSuccess: true); @@ -200,16 +200,16 @@ void main() { }); test('searchLogs 应该正确搜索日志', () { - controller.initialize(YxNetInspectorConfig()); + controller.initialize(const YxNetInspectorConfig()); controller.logRequest( - id: 'user-req', method: 'GET', url: 'https://api.example.com/users'); + id: 'user-req', method: 'GET', url: 'https://api.example.com/users',); controller.logRequest( - id: 'post-req', method: 'POST', url: 'https://api.example.com/posts'); + id: 'post-req', method: 'POST', url: 'https://api.example.com/posts',); controller.logRequest( id: 'error-req', method: 'DELETE', - url: 'https://api.example.com/user/1'); + url: 'https://api.example.com/user/1',); controller.logError(id: 'error-req', error: '用户不存在'); final userLogs = controller.searchLogs('user'); @@ -222,12 +222,12 @@ void main() { }); test('日志数量应该受到maxLogs限制', () { - controller.initialize(YxNetInspectorConfig(maxLogs: 5)); + controller.initialize(const YxNetInspectorConfig(maxLogs: 5)); // 添加超过限制的日志 - for (int i = 0; i < 10; i++) { + for (var i = 0; i < 10; i++) { controller.logRequest( - id: 'test-$i', method: 'GET', url: 'https://example.com/$i'); + id: 'test-$i', method: 'GET', url: 'https://example.com/$i',); } expect(controller.logs.length, equals(5)); @@ -236,13 +236,12 @@ void main() { }); test('配置未启用时不应该记录日志', () { - controller.initialize(YxNetInspectorConfig( + controller.initialize(const YxNetInspectorConfig( showInDebugMode: false, - showInReleaseMode: false, - )); + ),); controller.logRequest( - id: 'test', method: 'GET', url: 'https://example.com'); + id: 'test', method: 'GET', url: 'https://example.com',); expect(controller.logs, isEmpty); expect(controller.requestCount, equals(0)); diff --git a/test/widget/floating_ball_test.dart b/test/widget/floating_ball_test.dart index 7b51d4e..b3a5b36 100644 --- a/test/widget/floating_ball_test.dart +++ b/test/widget/floating_ball_test.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:yx_net_inspector/src/widgets/floating_ball.dart'; import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart'; import 'package:yx_net_inspector/src/models/inspector_config.dart'; import 'package:yx_net_inspector/src/models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/widgets/floating_ball.dart'; void main() { group('YxFloatingBall Widget Tests', () { @@ -61,9 +61,9 @@ void main() { testWidgets('应该显示请求数量徽章', (WidgetTester tester) async { // 添加一些请求 controller.logRequest( - id: 'test-1', method: 'GET', url: 'https://example.com'); + id: 'test-1', method: 'GET', url: 'https://example.com',); controller.logRequest( - id: 'test-2', method: 'POST', url: 'https://example.com'); + id: 'test-2', method: 'POST', url: 'https://example.com',); await tester.pumpWidget( MaterialApp( @@ -86,7 +86,7 @@ void main() { testWidgets('应该显示错误数量徽章', (WidgetTester tester) async { // 添加请求和错误 controller.logRequest( - id: 'test-1', method: 'GET', url: 'https://example.com'); + id: 'test-1', method: 'GET', url: 'https://example.com',); controller.logError(id: 'test-1', error: '网络错误'); await tester.pumpWidget( @@ -181,7 +181,7 @@ void main() { // 添加一些请求 controller.logRequest( - id: 'test-1', method: 'GET', url: 'https://example.com'); + id: 'test-1', method: 'GET', url: 'https://example.com',); await tester.pumpWidget( MaterialApp( @@ -221,7 +221,7 @@ void main() { }); testWidgets('自定义大小应该正确应用', (WidgetTester tester) async { - final customSizeConfig = config.copyWith(ballSize: 80.0); + final customSizeConfig = config.copyWith(ballSize: 80); await tester.pumpWidget( MaterialApp( diff --git a/test/widget/inspector_panel_test.dart b/test/widget/inspector_panel_test.dart index 918b23f..d4e6294 100644 --- a/test/widget/inspector_panel_test.dart +++ b/test/widget/inspector_panel_test.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:yx_net_inspector/src/widgets/inspector_panel.dart'; import 'package:yx_net_inspector/src/controller/yx_net_inspector_controller.dart'; import 'package:yx_net_inspector/src/models/inspector_config.dart'; import 'package:yx_net_inspector/src/models/inspector_theme.dart'; +import 'package:yx_net_inspector/src/widgets/inspector_panel.dart'; void main() { group('YxInspectorPanel Widget Tests', () { @@ -43,11 +43,11 @@ void main() { testWidgets('应该显示统计信息', (WidgetTester tester) async { // 添加一些测试数据 controller.logRequest( - id: 'test-1', method: 'GET', url: 'https://example.com'); + id: 'test-1', method: 'GET', url: 'https://example.com',); controller.logResponse(id: 'test-1', statusCode: 200); controller.logRequest( - id: 'test-2', method: 'POST', url: 'https://example.com'); + id: 'test-2', method: 'POST', url: 'https://example.com',); controller.logError(id: 'test-2', error: '网络错误'); await tester.pumpWidget( @@ -96,9 +96,9 @@ void main() { testWidgets('搜索功能应该正常工作', (WidgetTester tester) async { // 添加测试数据 controller.logRequest( - id: 'user-1', method: 'GET', url: 'https://api.example.com/users'); + id: 'user-1', method: 'GET', url: 'https://api.example.com/users',); controller.logRequest( - id: 'post-1', method: 'POST', url: 'https://api.example.com/posts'); + id: 'post-1', method: 'POST', url: 'https://api.example.com/posts',); await tester.pumpWidget( MaterialApp( @@ -125,11 +125,11 @@ void main() { testWidgets('错误过滤器应该正常工作', (WidgetTester tester) async { // 添加成功和失败的请求 controller.logRequest( - id: 'success', method: 'GET', url: 'https://example.com'); + id: 'success', method: 'GET', url: 'https://example.com',); controller.logResponse(id: 'success', statusCode: 200); controller.logRequest( - id: 'error', method: 'POST', url: 'https://example.com'); + id: 'error', method: 'POST', url: 'https://example.com',); controller.logError(id: 'error', error: '网络错误'); await tester.pumpWidget( @@ -158,7 +158,7 @@ void main() { testWidgets('清空日志按钮应该正常工作', (WidgetTester tester) async { // 添加一些日志 controller.logRequest( - id: 'test', method: 'GET', url: 'https://example.com'); + id: 'test', method: 'GET', url: 'https://example.com',); await tester.pumpWidget( MaterialApp( @@ -204,7 +204,7 @@ void main() { }); testWidgets('关闭按钮应该调用回调', (WidgetTester tester) async { - bool closeCalled = false; + var closeCalled = false; await tester.pumpWidget( MaterialApp(