yx_net_inspector_flutter/example/lib/pages/demo_page.dart

630 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:yx_net_inspector/yx_net_inspector.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:math';
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
int _requestCounter = 0;
final Random _random = Random();
bool _isLoading = false;
// 真实的开放API端点
final List<Map<String, dynamic>> _endpoints = [
{
'name': '用户列表',
'method': 'GET',
'url': 'https://jsonplaceholder.typicode.com/users',
'description': '获取用户列表 (JSONPlaceholder)',
'icon': Icons.people,
},
{
'name': '文章列表',
'method': 'GET',
'url': 'https://jsonplaceholder.typicode.com/posts',
'description': '获取文章列表 (JSONPlaceholder)',
'icon': Icons.article,
},
{
'name': '随机猫咪事实',
'method': 'GET',
'url': 'https://catfact.ninja/fact',
'description': '获取随机猫咪事实 (Cat Facts API)',
'icon': Icons.pets,
},
{
'name': '创建文章',
'method': 'POST',
'url': 'https://jsonplaceholder.typicode.com/posts',
'description': '创建新文章 (JSONPlaceholder)',
'icon': Icons.post_add,
},
{
'name': 'HTTP测试',
'method': 'GET',
'url': 'https://httpbin.org/json',
'description': '测试HTTP响应 (HTTPBin)',
'icon': Icons.http,
},
{
'name': '用户信息',
'method': 'GET',
'url': 'https://reqres.in/api/users/2',
'description': '获取单个用户 (ReqRes)',
'icon': Icons.person,
},
{
'name': 'IP地址信息',
'method': 'GET',
'url': 'https://httpbin.org/ip',
'description': '获取IP地址信息 (HTTPBin)',
'icon': Icons.location_on,
},
{
'name': '用户代理',
'method': 'GET',
'url': 'https://httpbin.org/user-agent',
'description': '获取用户代理信息 (HTTPBin)',
'icon': Icons.info,
},
{
'name': '更新用户',
'method': 'PUT',
'url': 'https://reqres.in/api/users/2',
'description': '更新用户信息 (ReqRes)',
'icon': Icons.edit,
},
{
'name': '删除用户',
'method': 'DELETE',
'url': 'https://reqres.in/api/users/2',
'description': '删除用户 (ReqRes)',
'icon': Icons.delete,
},
];
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 状态卡片
_buildStatusCard(),
const SizedBox(height: 20),
// 快速操作区域
_buildQuickActions(),
const SizedBox(height: 20),
// API端点列表
Text(
'API 端点测试',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
...(_endpoints.map((endpoint) => _buildEndpointCard(endpoint))),
const SizedBox(height: 20),
// 批量测试区域
_buildBatchTestSection(),
const SizedBox(height: 20),
// 使用提示
_buildHelpSection(),
],
),
);
}
Widget _buildStatusCard() {
return Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.network_check,
size: 32,
color: Colors.blue,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'网络请求统计',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'已发送请求: $_requestCounter',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.blue,
fontWeight: FontWeight.w600,
),
),
],
),
),
if (_isLoading)
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
),
),
);
}
Widget _buildQuickActions() {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'快速操作',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
onPressed: _simulateRandomRequest,
icon: const Icon(Icons.shuffle, size: 18),
label: const Text('随机请求'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
),
),
ElevatedButton.icon(
onPressed: _simulateMultipleRequests,
icon: const Icon(Icons.burst_mode, size: 18),
label: const Text('批量请求'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
),
),
ElevatedButton.icon(
onPressed: _simulateErrorRequest,
icon: const Icon(Icons.error_outline, size: 18),
label: const Text('错误请求'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
),
OutlinedButton.icon(
onPressed: _clearLogs,
icon: const Icon(Icons.clear_all, size: 18),
label: const Text('清空日志'),
),
],
),
],
),
),
);
}
Widget _buildEndpointCard(Map<String, dynamic> endpoint) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _getMethodColor(endpoint['method']).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
endpoint['icon'],
color: _getMethodColor(endpoint['method']),
size: 20,
),
),
title: Text(
endpoint['name'],
style: const TextStyle(fontWeight: FontWeight.w600),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(endpoint['description']),
const SizedBox(height: 4),
Row(
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: _getMethodColor(endpoint['method']),
borderRadius: BorderRadius.circular(4),
),
child: Text(
endpoint['method'],
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
endpoint['url'],
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontFamily: 'monospace',
),
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
trailing: IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: () => _simulateSpecificRequest(endpoint),
tooltip: '发送请求',
),
isThreeLine: true,
),
);
}
Widget _buildBatchTestSection() {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'批量测试',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
'测试大量网络请求的性能和日志管理功能',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => _simulateBatchRequests(10),
icon: const Icon(Icons.speed),
label: const Text('10个请求'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: () => _simulateBatchRequests(50),
icon: const Icon(Icons.flash_on),
label: const Text('50个请求'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.deepOrange,
),
),
),
],
),
],
),
),
);
}
Widget _buildHelpSection() {
return Card(
color: Colors.blue.shade50,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.lightbulb_outline, color: Colors.blue.shade700),
const SizedBox(width: 8),
Text(
'使用提示',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.blue.shade700,
),
),
],
),
const SizedBox(height: 12),
_buildHelpItem('1. 点击屏幕上的蓝色悬浮球打开网络检查器'),
_buildHelpItem('2. 发送各种类型的网络请求查看详细信息'),
_buildHelpItem('3. 在检查器中搜索和过滤特定请求'),
_buildHelpItem('4. 点击日志条目查看完整的请求/响应详情'),
_buildHelpItem('5. 使用批量测试验证性能和内存管理'),
],
),
),
);
}
Widget _buildHelpItem(String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
text,
style: TextStyle(
color: Colors.blue.shade700,
fontSize: 13,
),
),
);
}
Color _getMethodColor(String method) {
switch (method.toUpperCase()) {
case 'GET':
return Colors.green;
case 'POST':
return Colors.blue;
case 'PUT':
return Colors.orange;
case 'DELETE':
return Colors.red;
case 'PATCH':
return Colors.purple;
default:
return Colors.grey;
}
}
void _simulateRandomRequest() {
final endpoint = _endpoints[_random.nextInt(_endpoints.length)];
_simulateSpecificRequest(endpoint);
}
void _simulateSpecificRequest(Map<String, dynamic> endpoint) {
setState(() {
_requestCounter++;
_isLoading = true;
});
_sendRealHttpRequest(endpoint);
}
Future<void> _sendRealHttpRequest(Map<String, dynamic> endpoint) async {
final requestId = 'request_${DateTime.now().millisecondsSinceEpoch}';
final method = endpoint['method'] as String;
final url = endpoint['url'] as String;
final startTime = DateTime.now();
// 准备请求头
final headers = {
'Content-Type': 'application/json',
'User-Agent': 'YX-Net-Inspector-Demo/1.0.0',
'Accept': 'application/json',
'X-Request-ID': requestId,
};
// 记录请求开始
YxNetInspectorController.instance.logRequest(
id: requestId,
method: method,
url: url,
headers: headers,
requestData: _getRequestData(method),
queryParameters: method == 'GET' ? _getQueryParameters() : null,
);
try {
http.Response? response;
switch (method.toUpperCase()) {
case 'GET':
response = await http.get(Uri.parse(url), headers: headers);
break;
case 'POST':
final requestData = _getRequestData(method);
response = await http.post(
Uri.parse(url),
headers: headers,
body: requestData != null ? json.encode(requestData) : null,
);
break;
case 'PUT':
final requestData = _getRequestData(method);
response = await http.put(
Uri.parse(url),
headers: headers,
body: requestData != null ? json.encode(requestData) : null,
);
break;
case 'DELETE':
response = await http.delete(Uri.parse(url), headers: headers);
break;
default:
response = await http.get(Uri.parse(url), headers: headers);
}
final duration = DateTime.now().difference(startTime);
// 解析响应数据
dynamic responseData;
try {
responseData = json.decode(response.body);
} catch (e) {
responseData = response.body;
}
// 记录响应
YxNetInspectorController.instance.logResponse(
id: requestId,
statusCode: response.statusCode,
responseData: responseData,
duration: duration,
);
} catch (error) {
final duration = DateTime.now().difference(startTime);
// 记录错误
YxNetInspectorController.instance.logError(
id: requestId,
error: error.toString(),
duration: duration,
);
} finally {
setState(() {
_isLoading = false;
});
}
}
void _simulateMultipleRequests() {
for (int i = 0; i < 5; i++) {
Future.delayed(Duration(milliseconds: i * 300), () {
_simulateRandomRequest();
});
}
}
void _simulateErrorRequest() {
setState(() {
_requestCounter++;
_isLoading = true;
});
// 使用HTTPBin的状态码端点来模拟错误
final errorCodes = [400, 401, 403, 404, 500, 502, 503];
final errorCode = errorCodes[_random.nextInt(errorCodes.length)];
final errorEndpoint = {
'method': 'GET',
'url': 'https://httpbin.org/status/$errorCode',
'name': '错误请求测试',
'description': '模拟HTTP错误状态码',
};
_sendRealHttpRequest(errorEndpoint);
}
void _simulateBatchRequests(int count) {
for (int i = 0; i < count; i++) {
Future.delayed(Duration(milliseconds: i * 50), () {
_simulateRandomRequest();
});
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('正在发送 $count 个请求...'),
duration: const Duration(seconds: 2),
),
);
}
void _clearLogs() {
YxNetInspectorController.instance.clearLogs();
setState(() {
_requestCounter = 0;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('日志已清空'),
duration: Duration(seconds: 1),
),
);
}
Map<String, dynamic>? _getRequestData(String method) {
if (method == 'GET') return null;
switch (method) {
case 'POST':
return {
'name': '张三',
'email': 'zhangsan@example.com',
'age': _random.nextInt(50) + 18,
'department': '技术部',
'skills': ['Flutter', 'Dart', 'Mobile Development'],
'metadata': {
'created_at': DateTime.now().toIso8601String(),
'source': 'demo_app',
'version': '1.0.0',
}
};
case 'PUT':
return {
'profile': {
'avatar': 'https://example.com/avatar.jpg',
'bio': '这是一个示例用户简介',
'preferences': {
'theme': 'dark',
'language': 'zh-CN',
'notifications': true,
}
}
};
case 'PATCH':
return {
'status': 'active',
'last_login': DateTime.now().toIso8601String(),
};
default:
return {'action': method.toLowerCase()};
}
}
Map<String, String>? _getQueryParameters() {
return {
'page': (_random.nextInt(10) + 1).toString(),
'limit': '20',
'sort': ['name', 'created_at', 'updated_at'][_random.nextInt(3)],
'order': ['asc', 'desc'][_random.nextInt(2)],
'filter': 'active',
};
}
}