import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/dialogue_models.dart'; import '../services/dialogue_service.dart'; /// 對話狀態提供者 final dialogueProvider = StateNotifierProvider((ref) { final dialogueService = ref.watch(dialogueServiceProvider); return DialogueNotifier(dialogueService); }); /// 對話服務提供者 final dialogueServiceProvider = Provider((ref) { return DialogueService(); }); /// 對話狀態管理 class DialogueNotifier extends StateNotifier { final DialogueService _dialogueService; DialogueNotifier(this._dialogueService) : super(DialogueState.initial()); /// 初始化對話 Future initializeDialogue({ required String scenarioId, required String levelId, bool isTimeChallenge = false, }) async { state = state.copyWith(isLoading: true); try { final scene = await _dialogueService.loadScene(scenarioId, levelId); final character = await _dialogueService.loadCharacter(scene.characterId); final task = await _dialogueService.loadTask(levelId); final vocabulary = await _dialogueService.loadRequiredVocabulary(levelId); // 載入開場對話 final openingDialogue = await _dialogueService.getOpeningDialogue( scenarioId, levelId, ); state = state.copyWith( isLoading: false, currentScene: scene, currentCharacter: character, currentTask: task, requiredVocabulary: vocabulary, currentDialogue: openingDialogue, scenarioId: scenarioId, levelId: levelId, isTimeChallenge: isTimeChallenge, ); } catch (e) { state = state.copyWith( isLoading: false, error: e.toString(), ); } } /// 發送用戶回覆 Future sendReply(String replyText) async { if (replyText.trim().isEmpty) return; state = state.copyWith(isProcessing: true); try { // 創建用戶回覆 final userReply = DialogueMessage( id: DateTime.now().millisecondsSinceEpoch.toString(), content: replyText, isUser: true, timestamp: DateTime.now(), ); // 分析回覆 final analysis = await _dialogueService.analyzeReply( scenarioId: state.scenarioId!, levelId: state.levelId!, replyText: replyText, requiredVocabulary: state.requiredVocabulary, currentTask: state.currentTask, ); // 獲取AI回應 final aiResponse = await _dialogueService.getAIResponse( scenarioId: state.scenarioId!, levelId: state.levelId!, userReply: replyText, analysis: analysis, ); // 更新使用的詞彙 final newUsedVocabulary = Set.from(state.usedVocabulary); newUsedVocabulary.addAll(analysis.usedVocabulary); // 更新任務進度 DialogueTask? updatedTask = state.currentTask; if (analysis.taskProgress != null && updatedTask != null) { updatedTask = updatedTask.copyWith( progress: analysis.taskProgress!, isCompleted: analysis.taskProgress! >= 1.0, ); } state = state.copyWith( isProcessing: false, lastUserReply: userReply, currentDialogue: aiResponse, currentTask: updatedTask, usedVocabulary: newUsedVocabulary, lastAnalysis: analysis, conversationHistory: [...state.conversationHistory, userReply, aiResponse], ); // 檢查對話是否完成 if (analysis.isDialogueComplete || (updatedTask?.isCompleted ?? false)) { _completeDialogue(); } } catch (e) { state = state.copyWith( isProcessing: false, error: e.toString(), ); } } /// 顯示回覆輔助 Future showReplyAssistance() async { if (state.diamonds < 30) return; state = state.copyWith(isProcessing: true); try { final suggestions = await _dialogueService.getReplyAssistance( scenarioId: state.scenarioId!, levelId: state.levelId!, currentDialogue: state.currentDialogue?.content ?? '', currentTask: state.currentTask, ); state = state.copyWith( isProcessing: false, showReplyAssistance: true, replySuggestions: suggestions, diamonds: state.diamonds - 30, // 扣除鑽石 ); } catch (e) { state = state.copyWith( isProcessing: false, error: e.toString(), ); } } /// 隱藏回覆輔助 void hideReplyAssistance() { state = state.copyWith( showReplyAssistance: false, replySuggestions: [], ); } /// 使用時光卷道具 Future useTimeWarpCard() async { // TODO: 實現時光卷功能 } /// 完成對話 void _completeDialogue() { final finalScore = _calculateFinalScore(); state = state.copyWith( isCompleted: true, finalScore: finalScore, ); } /// 計算最終得分 DialogueScore _calculateFinalScore() { // 計算三維度得分 double grammarScore = 0.0; double semanticsScore = 0.0; double fluencyScore = 0.0; int totalReplies = 0; for (final analysis in state.analysisHistory) { grammarScore += analysis.grammarScore; semanticsScore += analysis.semanticsScore; fluencyScore += analysis.fluencyScore; totalReplies++; } if (totalReplies > 0) { grammarScore /= totalReplies; semanticsScore /= totalReplies; fluencyScore /= totalReplies; } // 計算任務完成度獎勵 double taskBonus = 0.0; if (state.currentTask?.isCompleted ?? false) { taskBonus = 20.0; } // 計算詞彙使用獎勵 double vocabularyBonus = 0.0; if (state.requiredVocabulary.isNotEmpty) { vocabularyBonus = (state.usedVocabulary.length / state.requiredVocabulary.length) * 10.0; } // 計算時間獎勵(限時挑戰) double timeBonus = 0.0; if (state.isTimeChallenge) { // TODO: 根據剩餘時間計算獎勵 timeBonus = 5.0; } final totalScore = grammarScore + semanticsScore + fluencyScore + taskBonus + vocabularyBonus + timeBonus; return DialogueScore( grammarScore: grammarScore, semanticsScore: semanticsScore, fluencyScore: fluencyScore, taskBonus: taskBonus, vocabularyBonus: vocabularyBonus, timeBonus: timeBonus, totalScore: totalScore, starRating: _calculateStarRating(totalScore), ); } /// 計算星級評價 int _calculateStarRating(double totalScore) { if (totalScore >= 90) return 3; if (totalScore >= 70) return 2; if (totalScore >= 50) return 1; return 0; } /// 重置對話狀態 void reset() { state = DialogueState.initial(); } } /// 對話狀態 class DialogueState { final bool isLoading; final bool isProcessing; final bool isCompleted; final String? error; // 場景信息 final String? scenarioId; final String? levelId; final bool isTimeChallenge; final DialogueScene? currentScene; final DialogueCharacter? currentCharacter; // 對話內容 final DialogueMessage? currentDialogue; final DialogueMessage? lastUserReply; final List conversationHistory; // 任務和詞彙 final DialogueTask? currentTask; final List requiredVocabulary; final Set usedVocabulary; // AI分析 final DialogueAnalysis? lastAnalysis; final List analysisHistory; // 回覆輔助 final bool showReplyAssistance; final List replySuggestions; // 資源 final int lifePoints; final int diamonds; // 設置 final String currentLanguage; // 最終結果 final DialogueScore? finalScore; DialogueState({ required this.isLoading, required this.isProcessing, required this.isCompleted, this.error, this.scenarioId, this.levelId, required this.isTimeChallenge, this.currentScene, this.currentCharacter, this.currentDialogue, this.lastUserReply, required this.conversationHistory, this.currentTask, required this.requiredVocabulary, required this.usedVocabulary, this.lastAnalysis, required this.analysisHistory, required this.showReplyAssistance, required this.replySuggestions, required this.lifePoints, required this.diamonds, required this.currentLanguage, this.finalScore, }); factory DialogueState.initial() { return DialogueState( isLoading: false, isProcessing: false, isCompleted: false, isTimeChallenge: false, conversationHistory: [], requiredVocabulary: [], usedVocabulary: {}, analysisHistory: [], showReplyAssistance: false, replySuggestions: [], lifePoints: 5, diamonds: 100, currentLanguage: 'zh-TW', ); } DialogueState copyWith({ bool? isLoading, bool? isProcessing, bool? isCompleted, String? error, String? scenarioId, String? levelId, bool? isTimeChallenge, DialogueScene? currentScene, DialogueCharacter? currentCharacter, DialogueMessage? currentDialogue, DialogueMessage? lastUserReply, List? conversationHistory, DialogueTask? currentTask, List? requiredVocabulary, Set? usedVocabulary, DialogueAnalysis? lastAnalysis, List? analysisHistory, bool? showReplyAssistance, List? replySuggestions, int? lifePoints, int? diamonds, String? currentLanguage, DialogueScore? finalScore, }) { return DialogueState( isLoading: isLoading ?? this.isLoading, isProcessing: isProcessing ?? this.isProcessing, isCompleted: isCompleted ?? this.isCompleted, error: error ?? this.error, scenarioId: scenarioId ?? this.scenarioId, levelId: levelId ?? this.levelId, isTimeChallenge: isTimeChallenge ?? this.isTimeChallenge, currentScene: currentScene ?? this.currentScene, currentCharacter: currentCharacter ?? this.currentCharacter, currentDialogue: currentDialogue ?? this.currentDialogue, lastUserReply: lastUserReply ?? this.lastUserReply, conversationHistory: conversationHistory ?? this.conversationHistory, currentTask: currentTask ?? this.currentTask, requiredVocabulary: requiredVocabulary ?? this.requiredVocabulary, usedVocabulary: usedVocabulary ?? this.usedVocabulary, lastAnalysis: lastAnalysis ?? this.lastAnalysis, analysisHistory: analysisHistory ?? List.from(this.analysisHistory), showReplyAssistance: showReplyAssistance ?? this.showReplyAssistance, replySuggestions: replySuggestions ?? this.replySuggestions, lifePoints: lifePoints ?? this.lifePoints, diamonds: diamonds ?? this.diamonds, currentLanguage: currentLanguage ?? this.currentLanguage, finalScore: finalScore ?? this.finalScore, ); } @override String toString() { return 'DialogueState(loading: $isLoading, processing: $isProcessing, completed: $isCompleted, scenarioId: $scenarioId, levelId: $levelId)'; } }