import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'ix_developer_tools.dart'; /// 轻量级日志条目组件 - 纵向排列,点击弹窗显示完整内容 class SimpleLogCard extends StatelessWidget { final ConsoleLogEntry log; const SimpleLogCard({super.key, required this.log}); @override Widget build(BuildContext context) { return InkWell( onTap: () => _showDetailDialog(context), borderRadius: BorderRadius.circular(10), child: Container( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.grey[200]!, width: 1), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.03), blurRadius: 4, offset: const Offset(0, 1), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:时间、Tag Row( children: [ // 状态指示点 Container( width: 8, height: 8, decoration: BoxDecoration( color: _getTagColor(), shape: BoxShape.circle, ), ), const SizedBox(width: 8), // Tag标签 Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3, ), decoration: BoxDecoration( color: _getTagColor().withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Text( log.tag, style: TextStyle( fontSize: 11, color: _getTagColor(), fontWeight: FontWeight.w600, ), ), ), const Spacer(), // 时间 Text( log.formattedTime, style: TextStyle( fontFamily: 'monospace', fontSize: 10, color: Colors.grey[400], ), ), ], ), const SizedBox(height: 8), // 第二行:日志内容(完整显示,最多3行) Text( log.message, style: const TextStyle( fontSize: 12, color: Colors.black87, height: 1.4, ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ], ), ), ); } Color _getTagColor() { final tag = log.tag.toLowerCase(); if (tag.contains('error') || tag.contains('err')) return Colors.red; if (tag.contains('warn')) return Colors.orange; if (tag.contains('info')) return Colors.blue; if (tag.contains('debug')) return Colors.purple; if (tag.contains('api') || tag.contains('http')) return Colors.teal; if (tag.contains('vpn') || tag.contains('xray')) return Colors.indigo; return Colors.blueGrey; } void _showDetailDialog(BuildContext context) { showDialog( context: context, builder: (context) => _LogDetailDialog(log: log), ); } } /// 日志详情弹窗 - 纵向排列 class _LogDetailDialog extends StatelessWidget { final ConsoleLogEntry log; const _LogDetailDialog({required this.log}); @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.grey[50], shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Container( width: Get.width * 0.92, constraints: BoxConstraints(maxHeight: Get.height * 0.75), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 头部 Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.vertical( top: Radius.circular(16), ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:Tag、时间、操作按钮 Row( children: [ // Tag Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), decoration: BoxDecoration( color: _getTagColor().withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: _getTagColor().withValues(alpha: 0.3), ), ), child: Text( log.tag, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: _getTagColor(), ), ), ), const SizedBox(width: 10), // 时间 Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(6), ), child: Text( log.formattedTime, style: TextStyle( fontSize: 12, color: Colors.grey[700], fontFamily: 'monospace', fontWeight: FontWeight.w500, ), ), ), const Spacer(), // 复制按钮 InkWell( onTap: () => _copyToClipboard('${log.tag}: ${log.message}'), borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.copy, size: 14, color: Colors.grey[600], ), const SizedBox(width: 4), Text( '复制', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.grey[600], ), ), ], ), ), ), const SizedBox(width: 8), // 关闭按钮 InkWell( onTap: () => Navigator.pop(context), borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.close, size: 18, color: Colors.grey[600], ), ), ), ], ), ], ), ), // 内容标题 Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Row( children: [ Icon( Icons.article_outlined, size: 16, color: Colors.grey[600], ), const SizedBox(width: 6), Text( '日志内容', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.grey[700], ), ), ], ), ), // 内容 Flexible( child: Container( margin: const EdgeInsets.fromLTRB(16, 0, 16, 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: SelectableText( log.message, style: const TextStyle( fontFamily: 'monospace', fontSize: 13, color: Colors.black87, height: 1.6, ), ), ), ), ), ], ), ), ); } Color _getTagColor() { final tag = log.tag.toLowerCase(); if (tag.contains('error') || tag.contains('err')) return Colors.red; if (tag.contains('warn')) return Colors.orange; if (tag.contains('info')) return Colors.blue; if (tag.contains('debug')) return Colors.purple; if (tag.contains('api') || tag.contains('http')) return Colors.teal; if (tag.contains('vpn') || tag.contains('xray')) return Colors.indigo; return Colors.blueGrey; } void _copyToClipboard(String text) { Clipboard.setData(ClipboardData(text: text)); Get.snackbar( '已复制', '日志内容已复制到剪贴板', duration: const Duration(seconds: 1), snackPosition: SnackPosition.bottom, backgroundColor: Colors.grey[800], colorText: Colors.white, borderRadius: 10, margin: const EdgeInsets.all(16), icon: const Padding( padding: EdgeInsets.only(left: 12), child: Icon(Icons.check_circle, color: Colors.white, size: 20), ), ); } }