import 'package:flutter/material.dart'; import 'package:airhub_app/theme/design_tokens.dart'; import 'package:airhub_app/widgets/glass_dialog.dart'; class AgentManagePage extends StatefulWidget { const AgentManagePage({super.key}); @override State createState() => _AgentManagePageState(); } class _AgentManagePageState extends State { // Mock data matching HTML final List> _agents = [ { 'id': 'Airhub_Mem_01', 'date': '2025/01/15', 'icon': '🧠', 'bind': 'Airhub_5G', 'nickname': '小毛球', 'status': 'bound', // bound, unbound }, { 'id': 'Airhub_Mem_02', 'date': '2024/08/22', 'icon': '🐾', 'bind': '未绑定设备', 'nickname': '豆豆', 'status': 'unbound', }, ]; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: Stack( children: [ Container(decoration: const BoxDecoration(color: Color(0xFFFEFEFE))), Column( children: [ _buildHeader(context), Expanded( child: ListView.builder( padding: EdgeInsets.only( top: 20, left: 20, right: 20, bottom: 40 + MediaQuery.of(context).padding.bottom, ), itemCount: _agents.length, itemBuilder: (context, index) { return _buildAgentCard(_agents[index]); }, ), ), ], ), ], ), ); } Widget _buildHeader(BuildContext context) { return Container( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 20, left: AppSpacing.lg, right: AppSpacing.lg, bottom: AppSpacing.md, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () => Navigator.pop(context), child: Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.iconBtnBg, borderRadius: BorderRadius.circular(AppRadius.button), border: Border.all(color: AppColors.iconBtnBorder), ), child: const Icon( Icons.arrow_back, color: AppColors.textPrimary, size: 20, ), ), ), const Text('角色记忆', style: AppTextStyles.title), GestureDetector( onTap: () { showGlassDialog( context: context, title: '什么是角色记忆?', description: '角色记忆是您与 AI 互动产生的人格数据,它是独立的数字资产,可以在不同设备间迁移,或分享给好友。', confirmText: '确定', onConfirm: () => Navigator.pop(context), ); }, child: Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.iconBtnBg, borderRadius: BorderRadius.circular(AppRadius.button), border: Border.all(color: AppColors.iconBtnBorder), ), child: const Center( child: Text( '?', style: TextStyle( fontSize: 18, color: AppColors.textSecondary, fontWeight: FontWeight.w500, ), ), ), ), ), ], ), ); } Widget _buildAgentCard(Map agent) { return Container( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: const Color(0xFFD4A373), // Fallback gradient: const LinearGradient(colors: AppColors.gradientCapybara), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: const Color(0xFFC9A07A).withOpacity(0.25), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Stack( children: [ // Top highlight layer Positioned( left: 0, right: 0, top: 0, height: 60, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.white.withOpacity(0.12), Colors.transparent], ), borderRadius: const BorderRadius.vertical( top: Radius.circular(20), ), ), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Text( agent['date']!, style: TextStyle( color: Colors.white.withOpacity(0.85), fontSize: 12, ), ), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular(12), ), alignment: Alignment.center, child: Text( agent['icon']!, style: const TextStyle(fontSize: 24), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( agent['id']!, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, shadows: [ Shadow( color: Color(0x1A000000), blurRadius: 2, offset: Offset(0, 1), ), ], ), ), ], ), ), ], ), const SizedBox(height: 12), _buildDetailRow('已绑定:', agent['bind']!), const SizedBox(height: 4), _buildDetailRow('角色昵称:', agent['nickname']!), const SizedBox(height: 12), Container(height: 1, color: Colors.white.withOpacity(0.2)), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ if (agent['status'] == 'bound') _buildActionBtn( '解绑', isDanger: true, onTap: () => _showUnbindDialog(agent['id']!), ) else _buildActionBtn( '注入设备', isInject: true, onTap: () => _showInjectDialog(agent['id']!), ), ], ), ], ), ], ), ); } Widget _buildDetailRow(String label, String value) { return RichText( text: TextSpan( style: TextStyle(fontSize: 14, color: Colors.white.withOpacity(0.85)), children: [ TextSpan(text: label), TextSpan( text: value, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w500, ), ), ], ), ); } Widget _buildActionBtn( String text, { bool isDanger = false, bool isInject = false, VoidCallback? onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white.withOpacity(0.3)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (isDanger) ...[ Icon( Icons.link_off, size: 14, color: AppColors.danger.withOpacity(0.9), ), // Use icon for visual const SizedBox(width: 4), ] else if (isInject) ...[ Icon(Icons.download, size: 14, color: const Color(0xFFB07D5A)), const SizedBox(width: 4), ], Text( text, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: isDanger ? AppColors.danger : (isInject ? const Color(0xFFB07D5A) : Colors.white), ), ), ], ), ), ); } void _showUnbindDialog(String id) { showGlassDialog( context: context, title: '确认解绑角色记忆?', description: '解绑后,该角色记忆将与当前设备断开连接,但数据会保留在云端。', cancelText: '取消', confirmText: '确认解绑', isDanger: true, // Note: GlassDialog implementation currently doesn't distinct danger style strongly but passed prop onConfirm: () { Navigator.pop(context); // Close dialog ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('已解绑: $id'))); }, ); } void _showInjectDialog(String id) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('正在查找附近的可用设备以注入: $id'))); } }