import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../../shared/widgets/voice_input_button.dart'; import '../providers/dialogue_provider.dart'; import '../widgets/dialogue_background.dart'; import '../widgets/character_avatar.dart'; import '../widgets/dialogue_bubble.dart'; import '../widgets/task_display_panel.dart'; import '../widgets/vocabulary_panel.dart'; import '../widgets/reply_assistance_panel.dart'; /// 情境對話主界面 /// /// 實現完整的AI對話功能,包括: /// - 沉浸式場景背景 /// - 角色對話展示 /// - 語音和文字輸入 /// - 任務進度追蹤 /// - 指定詞彙提示 /// - 回覆輔助功能 /// - 限時挑戰模式 class DialogueMainScreen extends ConsumerStatefulWidget { /// 場景ID final String scenarioId; /// 關卡ID final String levelId; /// 是否為限時挑戰模式 final bool isTimeChallenge; const DialogueMainScreen({ super.key, required this.scenarioId, required this.levelId, this.isTimeChallenge = false, }); @override ConsumerState createState() => _DialogueMainScreenState(); } class _DialogueMainScreenState extends ConsumerState with TickerProviderStateMixin { final TextEditingController _textController = TextEditingController(); final FocusNode _textFocusNode = FocusNode(); late AnimationController _timerController; late Animation _timerAnimation; @override void initState() { super.initState(); // 限時挑戰計時器動畫 _timerController = AnimationController( duration: const Duration(seconds: 300), // 300秒 = 5分鐘 vsync: this, ); _timerAnimation = Tween( begin: 1.0, end: 0.0, ).animate(CurvedAnimation( parent: _timerController, curve: Curves.linear, )); // 如果是限時挑戰,開始計時 if (widget.isTimeChallenge) { _timerController.forward(); _timerController.addStatusListener(_onTimerComplete); } // 初始化對話 WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(dialogueProvider.notifier).initializeDialogue( scenarioId: widget.scenarioId, levelId: widget.levelId, isTimeChallenge: widget.isTimeChallenge, ); }); } @override void dispose() { _textController.dispose(); _textFocusNode.dispose(); _timerController.dispose(); super.dispose(); } /// 計時器完成處理 void _onTimerComplete(AnimationStatus status) { if (status == AnimationStatus.completed) { _showTimeUpDialog(); } } @override Widget build(BuildContext context) { final dialogueState = ref.watch(dialogueProvider); return Scaffold( backgroundColor: Colors.black, body: SafeArea( child: Stack( children: [ // 背景場景 DialogueBackground( scenarioId: widget.scenarioId, backgroundUrl: dialogueState.currentScene?.backgroundImageUrl, ), // 主要內容 Column( children: [ // 頂部工具列 _buildTopBar(dialogueState), // 對話內容區域 Expanded( child: _buildDialogueContent(dialogueState), ), // 底部輸入區域 _buildInputArea(dialogueState), ], ), // 任務顯示面板 if (dialogueState.currentTask != null) Positioned( top: 80.h, right: 16.w, child: TaskDisplayPanel( task: dialogueState.currentTask!, ), ), // 指定詞彙面板 if (dialogueState.requiredVocabulary.isNotEmpty) Positioned( top: 80.h, left: 16.w, child: VocabularyPanel( vocabularies: dialogueState.requiredVocabulary, usedVocabularies: dialogueState.usedVocabulary, ), ), // 回覆輔助面板 if (dialogueState.showReplyAssistance) Positioned.fill( child: ReplyAssistancePanel( suggestions: dialogueState.replySuggestions, onSelectSuggestion: _onSelectSuggestion, onClose: _closeReplyAssistance, ), ), ], ), ), ); } /// 頂部工具列 Widget _buildTopBar(DialogueState state) { return Container( height: 60.h, padding: EdgeInsets.symmetric(horizontal: 16.w), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.8), Colors.transparent, ], ), ), child: Row( children: [ // 返回按鈕 IconButton( onPressed: _showExitConfirmation, icon: Icon( Icons.arrow_back_ios, color: Colors.white, size: 20.sp, ), ), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 限時挑戰計時器 if (widget.isTimeChallenge) AnimatedBuilder( animation: _timerAnimation, builder: (context, child) { final remainingSeconds = (_timerAnimation.value * 300).round(); final minutes = remainingSeconds ~/ 60; final seconds = remainingSeconds % 60; return Container( padding: EdgeInsets.symmetric( horizontal: 12.w, vertical: 6.h, ), decoration: BoxDecoration( color: remainingSeconds < 60 ? Colors.red.withOpacity(0.8) : Colors.black.withOpacity(0.6), borderRadius: BorderRadius.circular(16.r), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.timer, color: Colors.white, size: 16.sp, ), SizedBox(width: 4.w), Text( '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}', style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.bold, ), ), ], ), ); }, ), ], ), ), // 資源顯示 Row( children: [ // 命條 Row( children: [ Icon( Icons.favorite, color: Colors.red, size: 16.sp, ), SizedBox(width: 4.w), Text( '${state.lifePoints}', style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.bold, ), ), ], ), SizedBox(width: 16.w), // 鑽石 Row( children: [ Icon( Icons.diamond, color: Colors.blue, size: 16.sp, ), SizedBox(width: 4.w), Text( '${state.diamonds}', style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.bold, ), ), ], ), ], ), ], ), ); } /// 對話內容區域 Widget _buildDialogueContent(DialogueState state) { return Column( children: [ // 角色頭像和名稱 if (state.currentCharacter != null) Padding( padding: EdgeInsets.symmetric(vertical: 16.h), child: CharacterAvatar( character: state.currentCharacter!, showDetails: true, ), ), // 對話氣泡 Expanded( child: Container( margin: EdgeInsets.symmetric(horizontal: 20.w), child: state.currentDialogue != null ? DialogueBubble( dialogue: state.currentDialogue!, isUserReply: false, ) : Center( child: CircularProgressIndicator( color: Theme.of(context).primaryColor, ), ), ), ), // 用戶回覆氣泡(如果有的話) if (state.lastUserReply != null) Container( margin: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h), child: DialogueBubble( dialogue: state.lastUserReply!, isUserReply: true, ), ), ], ); } /// 底部輸入區域 Widget _buildInputArea(DialogueState state) { return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.black.withOpacity(0.9), Colors.transparent, ], ), ), child: Column( children: [ // 功能按鈕行 Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 角色詳情 _buildFunctionButton( icon: Icons.person, label: '角色', onTap: _showCharacterDetails, ), // 關鍵詞 _buildFunctionButton( icon: Icons.key, label: '關鍵詞', onTap: _showKeywords, ), // 任務提示 _buildFunctionButton( icon: Icons.lightbulb, label: '任務', onTap: _showTaskHint, disabled: state.currentTask?.isCompleted ?? true, ), // 中翻英 _buildFunctionButton( icon: Icons.translate, label: '翻譯', onTap: _showTranslation, ), // 回覆輔助 _buildFunctionButton( icon: Icons.help, label: '輔助', onTap: _showReplyAssistance, cost: 30, disabled: state.diamonds < 30, ), ], ), SizedBox(height: 16.h), // 輸入區域 Row( children: [ // 文字輸入框 Expanded( child: TextField( controller: _textController, focusNode: _textFocusNode, maxLines: 3, minLines: 1, style: TextStyle( color: Colors.white, fontSize: 16.sp, ), decoration: InputDecoration( hintText: '請輸入你的回覆...', hintStyle: TextStyle( color: Colors.grey, fontSize: 16.sp, ), filled: true, fillColor: Colors.black.withOpacity(0.6), border: OutlineInputBorder( borderRadius: BorderRadius.circular(24.r), borderSide: BorderSide.none, ), contentPadding: EdgeInsets.symmetric( horizontal: 20.w, vertical: 12.h, ), ), ), ), SizedBox(width: 8.w), // 語音輸入按鈕 VoiceInputButton( size: 48, languageId: state.currentLanguage, onResult: _onVoiceResult, onError: _onVoiceError, ), SizedBox(width: 8.w), // 發送按鈕 Container( width: 48.w, height: 48.w, decoration: BoxDecoration( shape: BoxShape.circle, color: _textController.text.trim().isNotEmpty ? Theme.of(context).primaryColor : Colors.grey, ), child: IconButton( onPressed: _textController.text.trim().isNotEmpty ? _sendReply : null, icon: Icon( Icons.send, color: Colors.white, size: 20.sp, ), ), ), ], ), ], ), ); } /// 功能按鈕 Widget _buildFunctionButton({ required IconData icon, required String label, required VoidCallback onTap, int? cost, bool disabled = false, }) { return GestureDetector( onTap: disabled ? null : onTap, child: Container( width: 60.w, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40.w, height: 40.w, decoration: BoxDecoration( shape: BoxShape.circle, color: disabled ? Colors.grey.withOpacity(0.3) : Colors.white.withOpacity(0.2), ), child: Stack( children: [ Center( child: Icon( icon, color: disabled ? Colors.grey : Colors.white, size: 20.sp, ), ), if (cost != null) Positioned( top: -2.h, right: -2.w, child: Container( padding: EdgeInsets.all(2.w), decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.blue, ), child: Icon( Icons.diamond, color: Colors.white, size: 8.sp, ), ), ), ], ), ), SizedBox(height: 4.h), Text( cost != null ? '$label($cost)' : label, style: TextStyle( color: disabled ? Colors.grey : Colors.white, fontSize: 12.sp, ), textAlign: TextAlign.center, ), ], ), ), ); } /// 語音識別結果處理 void _onVoiceResult(String text) { setState(() { _textController.text = text; }); } /// 語音識別錯誤處理 void _onVoiceError(String error) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('語音識別失敗:$error'), backgroundColor: Colors.red, ), ); } /// 發送回覆 void _sendReply() { final text = _textController.text.trim(); if (text.isEmpty) return; ref.read(dialogueProvider.notifier).sendReply(text); _textController.clear(); } /// 選擇建議回覆 void _onSelectSuggestion(String suggestion) { setState(() { _textController.text = suggestion; }); _closeReplyAssistance(); } /// 顯示角色詳情 void _showCharacterDetails() { // TODO: 導航到角色詳情頁面 } /// 顯示關鍵詞 void _showKeywords() { // TODO: 導航到關鍵詞頁面 } /// 顯示任務提示 void _showTaskHint() { // TODO: 顯示任務提示對話框 } /// 顯示翻譯 void _showTranslation() { // TODO: 顯示翻譯對話框 } /// 顯示回覆輔助 void _showReplyAssistance() { ref.read(dialogueProvider.notifier).showReplyAssistance(); } /// 關閉回覆輔助 void _closeReplyAssistance() { ref.read(dialogueProvider.notifier).hideReplyAssistance(); } /// 顯示退出確認 void _showExitConfirmation() { showDialog( context: context, builder: (context) => AlertDialog( title: Text('確認離開'), content: Text( widget.isTimeChallenge ? '離開限時挑戰將無法繼續,確定要離開嗎?' : '確定要離開對話嗎?當前進度將會保存。', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text('取消'), ), TextButton( onPressed: () { Navigator.of(context).pop(); Navigator.of(context).pop(); }, child: Text('確定'), ), ], ), ); } /// 顯示時間結束對話框 void _showTimeUpDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text('時間到!'), content: Text('限時挑戰時間已結束,正在計算成績...'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); // TODO: 跳轉到結果頁面 Navigator.of(context).pop(); }, child: Text('查看結果'), ), ], ), ); } }