import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ainoval/models/novel_setting_item.dart'; import 'package:ainoval/models/ai_context_tracking.dart'; import 'package:ainoval/blocs/setting/setting_bloc.dart'; import 'package:ainoval/utils/web_theme.dart'; import 'package:ainoval/widgets/common/top_toast.dart'; /// 设定追踪配置标签页 class SettingTrackingTab extends StatefulWidget { final NovelSettingItem settingItem; final String novelId; final Function(NovelSettingItem) onItemUpdated; const SettingTrackingTab({ Key? key, required this.settingItem, required this.novelId, required this.onItemUpdated, }) : super(key: key); @override State createState() => _SettingTrackingTabState(); } class _SettingTrackingTabState extends State { late NameAliasTracking _nameAliasTracking; late AIContextTracking _aiContextTracking; late SettingReferenceUpdate _referenceUpdatePolicy; bool _hasChanges = false; bool _isSaving = false; @override void initState() { super.initState(); _nameAliasTracking = widget.settingItem.nameAliasTracking; _aiContextTracking = widget.settingItem.aiContextTracking; _referenceUpdatePolicy = widget.settingItem.referenceUpdatePolicy; } @override Widget build(BuildContext context) { final isDark = WebTheme.isDarkMode(context); return SingleChildScrollView( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 名称/别名追踪设置 _buildNameAliasTrackingSection(isDark), const SizedBox(height: 16), // AI上下文追踪设置 _buildAIContextTrackingSection(isDark), const SizedBox(height: 16), // 引用更新策略设置 _buildReferenceUpdateSection(isDark), const SizedBox(height: 20), // 保存按钮 if (_hasChanges) _buildSaveButton(isDark), ], ), ); } /// 构建名称/别名追踪设置区域 Widget _buildNameAliasTrackingSection(bool isDark) { return _buildSettingSection( title: '名称/别名追踪', description: '控制是否通过名称和别名来追踪此设定条目', icon: Icons.label, iconColor: Colors.blue, child: Column( children: NameAliasTracking.values.map((option) { return _buildRadioTile( value: option, groupValue: _nameAliasTracking, title: option.displayName, description: option.description, onChanged: (value) { if (value != null) { setState(() { _nameAliasTracking = value; _hasChanges = true; }); } }, isDark: isDark, ); }).toList(), ), ); } /// 构建AI上下文追踪设置区域 Widget _buildAIContextTrackingSection(bool isDark) { return _buildSettingSection( title: 'AI上下文', description: '控制此设定条目如何包含在AI上下文中', icon: Icons.psychology, iconColor: Colors.purple, child: Column( children: AIContextTracking.values.map((option) { return _buildRadioTile( value: option, groupValue: _aiContextTracking, title: option.displayName, description: option.description, onChanged: (value) { if (value != null) { setState(() { _aiContextTracking = value; _hasChanges = true; }); } }, isDark: isDark, isRecommended: option == AIContextTracking.detected, ); }).toList(), ), ); } /// 构建引用更新策略设置区域 Widget _buildReferenceUpdateSection(bool isDark) { return _buildSettingSection( title: '引用更新策略', description: '当修改此设定时,如何处理引用此设定的其他内容', icon: Icons.update, iconColor: Colors.orange, child: Column( children: SettingReferenceUpdate.values.map((option) { return _buildRadioTile( value: option, groupValue: _referenceUpdatePolicy, title: option.displayName, description: option.description, onChanged: (value) { if (value != null) { setState(() { _referenceUpdatePolicy = value; _hasChanges = true; }); } }, isDark: isDark, isRecommended: option == SettingReferenceUpdate.ask, ); }).toList(), ), ); } /// 构建设置区域的通用框架 Widget _buildSettingSection({ required String title, required String description, required IconData icon, required Color iconColor, required Widget child, }) { final isDark = WebTheme.isDarkMode(context); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? WebTheme.darkGrey100 : WebTheme.grey50, borderRadius: BorderRadius.circular(12), border: Border.all( color: isDark ? WebTheme.darkGrey700 : WebTheme.grey200, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题区域 Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: iconColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( icon, size: 18, color: iconColor, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: WebTheme.getTextColor(context), ), ), const SizedBox(height: 4), Text( description, style: TextStyle( fontSize: 12, color: WebTheme.getSecondaryTextColor(context), height: 1.4, ), ), ], ), ), ], ), const SizedBox(height: 16), // 选项内容 child, ], ), ); } /// 构建单选按钮瓦片 Widget _buildRadioTile({ required T value, required T groupValue, required String title, required String description, required ValueChanged onChanged, required bool isDark, bool isRecommended = false, }) { final isSelected = value == groupValue; return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: isSelected ? (isDark ? WebTheme.darkGrey700 : Colors.blue.withOpacity(0.1)) : WebTheme.getSurfaceColor(context), borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? Colors.blue : (isDark ? WebTheme.darkGrey600 : WebTheme.grey300), width: isSelected ? 2 : 1, ), ), child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () => onChanged(value), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // 单选按钮 Radio( value: value, groupValue: groupValue, onChanged: onChanged, activeColor: Colors.blue, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox(width: 12), // 内容区域 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( title, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: isSelected ? Colors.blue : WebTheme.getTextColor(context), ), ), if (isRecommended) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), borderRadius: BorderRadius.circular(4), border: Border.all( color: Colors.green.withOpacity(0.3), ), ), child: Text( '推荐', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: Colors.green, ), ), ), ], ], ), const SizedBox(height: 4), Text( description, style: TextStyle( fontSize: 12, color: WebTheme.getSecondaryTextColor(context), height: 1.3, ), ), ], ), ), ], ), ), ), ); } /// 构建保存按钮 Widget _buildSaveButton(bool isDark) { return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? WebTheme.darkGrey800 : WebTheme.grey50, borderRadius: BorderRadius.circular(12), border: Border.all( color: isDark ? WebTheme.darkGrey700 : WebTheme.grey200, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '保存更改', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: WebTheme.getTextColor(context), ), ), const SizedBox(height: 8), Text( '您的追踪配置已修改,点击保存以应用更改。', style: TextStyle( fontSize: 13, color: WebTheme.getSecondaryTextColor(context), ), ), const SizedBox(height: 16), Row( children: [ // 重置按钮 TextButton( onPressed: _isSaving ? null : _resetChanges, child: Text( '重置', style: TextStyle( color: WebTheme.getSecondaryTextColor(context), ), ), ), const SizedBox(width: 12), // 保存按钮 Expanded( child: ElevatedButton( onPressed: _isSaving ? null : _saveChanges, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isSaving ? Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ), const SizedBox(width: 8), Text('保存中...'), ], ) : Text('保存更改'), ), ), ], ), ], ), ); } /// 重置更改 void _resetChanges() { setState(() { _nameAliasTracking = widget.settingItem.nameAliasTracking; _aiContextTracking = widget.settingItem.aiContextTracking; _referenceUpdatePolicy = widget.settingItem.referenceUpdatePolicy; _hasChanges = false; }); TopToast.info(context, '已重置所有更改'); } /// 保存更改 Future _saveChanges() async { if (widget.settingItem.id == null) return; setState(() { _isSaving = true; }); try { // 更新设定项目 final updatedItem = widget.settingItem.copyWith( nameAliasTracking: _nameAliasTracking, aiContextTracking: _aiContextTracking, referenceUpdatePolicy: _referenceUpdatePolicy, ); // 先更新本地状态 setState(() { _hasChanges = false; _isSaving = false; }); // 立即通知父组件 widget.onItemUpdated(updatedItem); // 显示成功提示 TopToast.success(context, '追踪配置已保存'); // 异步保存到后端,不阻塞UI _saveToBackendAsync(updatedItem); } catch (e) { setState(() { _isSaving = false; }); TopToast.error(context, '保存失败: ${e.toString()}'); } } /// 异步保存到后端 Future _saveToBackendAsync(NovelSettingItem updatedItem) async { try { // 通过BLoC更新后端 context.read().add(UpdateSettingItem( novelId: widget.novelId, itemId: widget.settingItem.id!, item: updatedItem, )); } catch (e) { // 静默处理错误,不干扰用户体验 } } }