| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- import 'dart:convert';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:get/get.dart';
- import 'ix_developer_tools.dart';
- /// 轻量级API请求卡片
- class SimpleApiCard extends StatefulWidget {
- final ApiRequestInfo request;
- const SimpleApiCard({super.key, required this.request});
- @override
- State<SimpleApiCard> createState() => _SimpleApiCardState();
- }
- class _SimpleApiCardState extends State<SimpleApiCard>
- with SingleTickerProviderStateMixin {
- bool _isExpanded = false;
- late AnimationController _animationController;
- late Animation<double> _expandAnimation;
- late Animation<double> _iconRotation;
- @override
- void initState() {
- super.initState();
- _animationController = AnimationController(
- duration: const Duration(milliseconds: 300),
- vsync: this,
- );
- _expandAnimation = CurvedAnimation(
- parent: _animationController,
- curve: Curves.easeInOut,
- );
- _iconRotation =
- Tween<double>(begin: 0.0, end: 0.5).animate(_expandAnimation);
- }
- @override
- void dispose() {
- _animationController.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(12),
- gradient: const LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [
- Colors.white,
- Colors.blue,
- ],
- ),
- border: Border.all(color: Colors.blue[200]!, width: 1),
- boxShadow: [
- BoxShadow(
- color: Colors.blue[100]!.withValues(alpha: 0.3),
- blurRadius: 8,
- offset: const Offset(0, 3),
- ),
- ],
- ),
- child: Column(
- children: [
- // 主要内容区域
- InkWell(
- onTap: _toggleExpansion,
- borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- // 状态指示器
- Container(
- padding: const EdgeInsets.all(6),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- colors: widget.request.statusText == 'SUCCESS'
- ? [Colors.green[500]!, Colors.teal[600]!]
- : [Colors.red[500]!, Colors.pink[600]!],
- ),
- shape: BoxShape.circle,
- boxShadow: [
- BoxShadow(
- color:
- widget.request.statusColor.withValues(alpha: 0.4),
- blurRadius: 4,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Icon(
- widget.request.statusText == 'SUCCESS'
- ? Icons.check
- : Icons.close,
- color: Colors.white,
- size: 14,
- ),
- ),
- const SizedBox(width: 12),
- // HTTP方法
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 4),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- colors: [
- _getMethodColor(),
- _getMethodColor().withValues(alpha: 0.8)
- ],
- ),
- borderRadius: BorderRadius.circular(10),
- boxShadow: [
- BoxShadow(
- color: _getMethodColor().withValues(alpha: 0.3),
- blurRadius: 3,
- offset: const Offset(0, 1),
- ),
- ],
- ),
- child: Text(
- widget.request.method,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 11,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- const SizedBox(width: 8),
- // 状态标签
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 8, vertical: 3),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- colors: widget.request.statusText == 'SUCCESS'
- ? [Colors.lightGreen[400]!, Colors.green[500]!]
- : [Colors.orange[400]!, Colors.red[500]!],
- ),
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text(
- widget.request.statusText,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 10,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- const Spacer(),
- // 状态码和响应时间
- Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (widget.request.statusCode != null)
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: Colors.indigo[600],
- borderRadius: BorderRadius.circular(6),
- ),
- child: Text(
- '${widget.request.statusCode}',
- style: const TextStyle(
- fontSize: 10,
- color: Colors.white,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- if (widget.request.duration != null) ...[
- const SizedBox(width: 6),
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- colors: [
- Colors.amber[500]!,
- Colors.orange[600]!
- ],
- ),
- borderRadius: BorderRadius.circular(6),
- ),
- child: Text(
- '${widget.request.duration!.inMilliseconds}ms',
- style: const TextStyle(
- fontSize: 10,
- color: Colors.white,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- ],
- const SizedBox(width: 8),
- // 展开图标
- AnimatedBuilder(
- animation: _iconRotation,
- builder: (context, child) {
- return Transform.rotate(
- angle: _iconRotation.value * 3.14159,
- child: Container(
- padding: const EdgeInsets.all(4),
- decoration: BoxDecoration(
- color: Colors.blue[100],
- shape: BoxShape.circle,
- ),
- child: Icon(
- Icons.keyboard_arrow_down,
- color: Colors.blue[700],
- size: 16,
- ),
- ),
- );
- },
- ),
- ],
- ),
- ],
- ),
- const SizedBox(height: 8),
- // URL显示
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: Colors.grey[100],
- borderRadius: BorderRadius.circular(8),
- border: Border.all(color: Colors.grey[300]!, width: 1),
- ),
- child: Text(
- widget.request.url,
- style: const TextStyle(
- fontSize: 12,
- color: Colors.black87,
- fontFamily: 'monospace',
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- // 展开的详细内容
- SizeTransition(
- sizeFactor: _expandAnimation,
- child: Container(
- width: double.infinity,
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [Colors.indigo[50]!, Colors.blue[50]!],
- ),
- borderRadius: const BorderRadius.only(
- bottomLeft: Radius.circular(12),
- bottomRight: Radius.circular(12),
- ),
- ),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- _buildDataSection('Headers', widget.request.headers),
- if (widget.request.requestData != null)
- _buildDataSection('Request', widget.request.requestData),
- if (widget.request.responseData != null)
- _buildDataSection(
- 'Response', widget.request.responseData),
- if (widget.request.error != null)
- _buildDataSection('Error', widget.request.error),
- ],
- ),
- ),
- ),
- ),
- ],
- ),
- );
- }
- Widget _buildDataSection(String title, dynamic data) {
- final jsonString = _formatAsJson(data);
- return Padding(
- padding: const EdgeInsets.only(bottom: 16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Container(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- colors: [Colors.deepPurple[500]!, Colors.purple[600]!],
- ),
- borderRadius: BorderRadius.circular(8),
- boxShadow: [
- BoxShadow(
- color: Colors.purple[200]!.withValues(alpha: 0.5),
- blurRadius: 3,
- offset: const Offset(0, 1),
- ),
- ],
- ),
- child: Text(
- title,
- style: const TextStyle(
- fontWeight: FontWeight.bold,
- color: Colors.white,
- fontSize: 12,
- ),
- ),
- ),
- const Spacer(),
- ElevatedButton.icon(
- icon: const Icon(Icons.copy, size: 14),
- label: const Text('复制'),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.teal[600],
- foregroundColor: Colors.white,
- elevation: 3,
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8),
- ),
- ),
- onPressed: () => _copyToClipboard(jsonString),
- ),
- ],
- ),
- const SizedBox(height: 8),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: Colors.grey[50],
- borderRadius: BorderRadius.circular(8),
- border: Border.all(color: Colors.blue[200]!, width: 1),
- ),
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- child: SelectableText(
- jsonString,
- style: const TextStyle(
- fontFamily: 'monospace',
- fontSize: 12,
- color: Colors.black87,
- height: 1.4,
- ),
- ),
- ),
- ),
- ],
- ),
- );
- }
- void _toggleExpansion() {
- setState(() {
- _isExpanded = !_isExpanded;
- if (_isExpanded) {
- _animationController.forward();
- } else {
- _animationController.reverse();
- }
- });
- }
- String _formatAsJson(dynamic data) {
- try {
- if (data == null) return 'null';
- // 如果已经是字符串,检查是否是JSON格式
- if (data is String) {
- try {
- final decoded = jsonDecode(data);
- return const JsonEncoder.withIndent(' ').convert(decoded);
- } catch (e) {
- // 不是JSON格式,直接返回字符串
- return data;
- }
- }
- // 对象转JSON,不限制大小
- return const JsonEncoder.withIndent(' ').convert(data);
- } catch (e) {
- if (data.runtimeType.toString() == 'FormData') {
- if (data.fields is List<MapEntry<String, String>>) {
- final fields = data.fields as List<MapEntry<String, String>>;
- final jsonMap = {
- ...Map.fromEntries(fields.map((e) => MapEntry(e.key, e.value))),
- };
- return const JsonEncoder.withIndent(' ').convert(jsonMap);
- }
- return data.fields.toString();
- }
- // JSON序列化失败,返回字符串形式
- return '${data.toString()}\n\n(JSON序列化失败: $e)';
- }
- }
- void _copyToClipboard(String text) {
- Clipboard.setData(ClipboardData(text: text));
- Get.snackbar(
- '🎉 复制成功',
- '内容已复制到剪贴板',
- duration: const Duration(seconds: 1),
- snackPosition: SnackPosition.bottom,
- backgroundColor: Colors.green[500],
- colorText: Colors.white,
- borderRadius: 10,
- margin: const EdgeInsets.all(16),
- boxShadows: [
- BoxShadow(
- color: Colors.green[200]!.withValues(alpha: 0.5),
- blurRadius: 6,
- offset: const Offset(0, 3),
- ),
- ],
- );
- }
- Color _getMethodColor() {
- switch (widget.request.method.toUpperCase()) {
- case 'GET':
- return Colors.blue[600]!;
- case 'POST':
- return Colors.green[600]!;
- case 'PUT':
- return Colors.orange[600]!;
- case 'DELETE':
- return Colors.red[600]!;
- case 'PATCH':
- return Colors.purple[600]!;
- default:
- return Colors.grey[600]!;
- }
- }
- }
|