import 'package:flutter/material.dart'; import 'package:miaoji_zhang/shared/api/api_client.dart'; import 'package:miaoji_zhang/shared/api/auth_api.dart'; import 'package:miaoji_zhang/shared/api/config_api.dart'; import 'package:miaoji_zhang/shared/theme/app_theme.dart'; import 'package:miaoji_zhang/shared/widgets/app_controls.dart'; import 'package:miaoji_zhang/shared/widgets/app_icons.dart'; /// AI 性格设置页(P5):形象/性格从后台 API 动态拉取 class CompanionPage extends StatefulWidget { const CompanionPage({super.key}); @override State createState() => _CompanionPageState(); } class _CompanionPageState extends State { String _avatar = 'cat', _persona = 'sassy_cat'; double _roast = 60, _sticker = 70, _proactive = 40; bool _saving = false, _loaded = false; List _avatars = []; List _personas = []; // 兜底 static const _fallbackAvatars = [ ('dog', '阿福汪', AppIcons.dog), ('cat', '小账喵', AppIcons.cat), ('robot', '账小智', AppIcons.robot), ]; static const _fallbackPersonas = [ ('sassy_cat', '毒舌猫娘', '乱花钱会被无情吐槽', AppTheme.ai), ('gentle', '温柔小暖', '永远鼓励,温柔提醒', AppTheme.primary), ('strict', '严格管家', '理性专业,数据说话', AppTheme.ai), ('meme', '沙雕损友', '玩梗高手,快乐记账', AppTheme.orange), ]; @override void initState() { super.initState(); _load(); } Future _load() async { try { final p = await AuthApi.me(); if (mounted && p.aiCompanion != null) setState(() { _avatar = p.aiCompanion!.avatarKey; _persona = p.aiCompanion!.personaKey; _roast = p.aiCompanion!.roastLevel.toDouble(); _sticker = p.aiCompanion!.stickerFrequency.toDouble(); _proactive = p.aiCompanion!.proactiveLevel.toDouble(); }); } catch (_) {} try { final av = await PublicConfigApi.avatars(); final ps = await PublicConfigApi.personas(); if (mounted) setState(() { _avatars = av; _personas = ps; }); } catch (_) {} if (mounted) setState(() => _loaded = true); } Future _save() async { setState(() => _saving = true); try { await AuthApi.updateCompanion( avatarKey: _avatar, personaKey: _persona, roastLevel: _roast.round(), stickerFrequency: _sticker.round(), proactiveLevel: _proactive.round(), ); await PublicConfigApi.refreshCompanion(); if (!mounted) return; Navigator.pop(context); ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('已保存'))); } catch (e) { if (mounted) ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(apiErrorMessage(e)))); } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { if (!_loaded) return const Scaffold( body: Center( child: CircularProgressIndicator( color: AppTheme.primary, strokeWidth: 2, ), ), ); final avatars = _avatars.isNotEmpty ? _avatars .map( (a) => ( a.key, a.defaultName, a.key == 'dog' ? AppIcons.dog : a.key == 'robot' ? AppIcons.robot : AppIcons.cat, ), ) .toList() : _fallbackAvatars; final personas = _personas.isNotEmpty ? _personas .map((p) => (p.key, p.name, p.description, AppTheme.ai)) .toList() : _fallbackPersonas; return Scaffold( appBar: AppBar(title: Text('AI 性格设置')), body: ListView( padding: const EdgeInsets.all(16), children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: avatars.map((a) { final on = a.$1 == _avatar; return GestureDetector( onTap: () => setState(() => _avatar = a.$1), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 11), child: Column( children: [ AnimatedContainer( duration: const Duration(milliseconds: 200), width: on ? 66 : 52, height: on ? 66 : 52, decoration: BoxDecoration( shape: BoxShape.circle, color: on ? AppTheme.ai : context.jz.aiBackground, boxShadow: on ? [ BoxShadow( color: AppTheme.ai.withValues(alpha: 0.3), blurRadius: 14, ), ] : null, ), child: Center( child: AppIcons.icon( a.$3, size: on ? 30 : 24, color: on ? Colors.white : context.jz.text3, ), ), ), SizedBox(height: 6), Text( a.$2, style: TextStyle( fontSize: 10, color: on ? AppTheme.ai : context.jz.text3, fontWeight: on ? FontWeight.w600 : null, ), ), ], ), ), ); }).toList(), ), SizedBox(height: 16), GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.9, children: personas.map((p) { final on = p.$1 == _persona; return GestureDetector( onTap: () => setState(() => _persona = p.$1), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: on ? context.jz.aiBackground : context.jz.card, borderRadius: BorderRadius.circular(14), border: Border.all( color: on ? AppTheme.ai : context.jz.line, width: 1.5, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( p.$2, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, ), ), SizedBox(height: 4), Text( p.$3, style: TextStyle(fontSize: 10, color: context.jz.text2), ), ], ), ), ); }).toList(), ), SizedBox(height: 14), Card( child: Padding( padding: const EdgeInsets.all(14), child: Column( children: [ _slider('吐槽力度', _roast, (v) => setState(() => _roast = v)), _slider( '表情包频率', _sticker, (v) => setState(() => _sticker = v), ), ], ), ), ), SizedBox(height: 16), ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: AppTheme.ai), onPressed: _saving ? null : _save, child: _saving ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text('保存设置'), ), ], ), ); } Widget _slider(String label, double value, ValueChanged onChanged) => Column( children: [ Row( children: [ Text( label, style: TextStyle(fontSize: 12, color: context.jz.text2), ), Spacer(), Text( '${value.round()}%', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600), ), ], ), JzSlider( value: value, min: 0, max: 100, color: AppTheme.ai, onChanged: onChanged, ), ], ); }