import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../theme/app_theme.dart'; import '../../widgets/furi_text.dart'; import '../../widgets/tts_button.dart'; class DialogueOption { DialogueOption({ required this.text, this.textSegments, required this.isCorrect, required this.feedback, }); final String text; final List? textSegments; final bool isCorrect; final String feedback; factory DialogueOption.fromJson(Map j) => DialogueOption( text: (j['text'] ?? '') as String, textSegments: parseFuriSegments(j['textSegments']), isCorrect: (j['isCorrect'] ?? false) as bool, feedback: (j['feedback'] ?? '') as String, ); } class DialogueTurn { DialogueTurn({ required this.type, required this.text, this.textSegments, this.translation, this.speakerName, this.options, }); final String type; final String text; final List? textSegments; final String? translation; final String? speakerName; final List? options; factory DialogueTurn.fromJson(Map j) => DialogueTurn( type: (j['type'] ?? 'narration') as String, text: (j['text'] ?? '') as String, textSegments: parseFuriSegments(j['textSegments']), translation: j['translation'] as String?, speakerName: j['speakerName'] as String?, options: j['options'] is List ? (j['options'] as List).map((o) => DialogueOption.fromJson(o as Map)).toList() : null, ); } class DialogueFull { DialogueFull({ required this.id, required this.title, required this.scenario, required this.level, required this.turns, }); final String id; final String title; final String scenario; final String level; final List turns; factory DialogueFull.fromJson(Map j) => DialogueFull( id: (j['id'] ?? '') as String, title: (j['title'] ?? '') as String, scenario: (j['scenario'] ?? '') as String, level: (j['level'] ?? '') as String, turns: ((j['turns'] as List?) ?? []).map((t) => DialogueTurn.fromJson(t as Map)).toList(), ); } class DialogueLessonView extends StatefulWidget { const DialogueLessonView({super.key, required this.dialogue, required this.lang, this.onMoreDialogues}); final DialogueFull dialogue; final String lang; final VoidCallback? onMoreDialogues; @override State createState() => _DialogueLessonViewState(); } class _DialogueLessonViewState extends State { bool _started = false; int _turnIndex = 0; int _score = 0; final Map _chosen = {}; final Map _showTranslation = {}; bool _completed = false; List get _turns => widget.dialogue.turns; int get _totalChoices => _turns.where((turn) => turn.type == 'user_choice').length; void _advance() { if (_turnIndex + 1 >= _turns.length) { setState(() => _completed = true); } else { setState(() => _turnIndex++); } } void _handleChoice(int turnIdx, int optIdx) { if (_chosen.containsKey(turnIdx)) return; final correct = _turns[turnIdx].options![optIdx].isCorrect; setState(() { _chosen[turnIdx] = optIdx; if (correct) _score++; }); Future.delayed(const Duration(milliseconds: 1200), _advance); } void _restart() { setState(() { _started = false; _turnIndex = 0; _score = 0; _chosen.clear(); _showTranslation.clear(); _completed = false; }); } @override Widget build(BuildContext context) { return SafeArea( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 760), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: !_started ? _buildStart() : _completed ? _buildCompletion() : _buildConversation(), ), ), ), ); } Widget _buildStart() { return ListView( key: const ValueKey('start'), padding: const EdgeInsets.all(20), children: [ Container( padding: const EdgeInsets.all(20), decoration: brutalCard(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _Eyebrow('SCENARIO', color: kBrutalTeal), const SizedBox(height: 8), Text( widget.dialogue.scenario, style: GoogleFonts.almarai(color: kBrutalSlate, fontSize: 20, fontWeight: FontWeight.w900, height: 1.35), ), const SizedBox(height: 18), Wrap( spacing: 8, runSpacing: 8, children: [ _Chip('${_turns.length} turns'), _Chip('$_totalChoices choices'), _Chip(widget.dialogue.level), ], ), ], ), ), const SizedBox(height: 24), FilledButton.icon( onPressed: () => setState(() => _started = true), icon: const Icon(Icons.play_arrow_rounded), label: const Text('START DIALOGUE'), ), ], ); } Widget _buildCompletion() { final pct = _totalChoices > 0 ? (_score / _totalChoices * 100).round() : 100; return ListView( key: const ValueKey('done'), padding: const EdgeInsets.all(24), children: [ Container( padding: const EdgeInsets.all(28), decoration: brutalCard(), child: Column( children: [ Container( width: 78, height: 78, decoration: BoxDecoration( color: kBrutalYellow, borderRadius: BorderRadius.circular(kBrutalRadius), border: Border.all(color: kBrutalBlack, width: 2), boxShadow: const [ BoxShadow(color: kBrutalBlack, offset: Offset(3, 3), blurRadius: 0), ], ), child: const Icon(Icons.emoji_events_rounded, color: kBrutalBlack, size: 44), ), const SizedBox(height: 18), Text('$pct%', style: GoogleFonts.almarai(color: kBrutalTeal, fontSize: 48, fontWeight: FontWeight.w900)), Text('$_score of $_totalChoices correct', style: GoogleFonts.almarai(color: kBrutalMuted, fontWeight: FontWeight.w900)), const SizedBox(height: 24), FilledButton.icon(onPressed: _restart, icon: const Icon(Icons.refresh_rounded), label: const Text('TRY AGAIN')), const SizedBox(height: 12), OutlinedButton( onPressed: widget.onMoreDialogues ?? () => Navigator.of(context).maybePop(), child: const Text('MORE DIALOGUES'), ), ], ), ), ], ); } Widget _buildConversation() { final progress = (_turnIndex + 1) / _turns.length; return Column( key: const ValueKey('chat'), children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), child: Container( padding: const EdgeInsets.all(12), decoration: brutalCard(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(kBrutalRadius), child: LinearProgressIndicator( value: progress, minHeight: 12, backgroundColor: const Color(0xFFE5E5E5), color: kBrutalTeal, ), ), const SizedBox(height: 8), _Eyebrow('STEP ${_turnIndex + 1} OF ${_turns.length}'), ], ), ), ), Expanded( child: ListView.builder( padding: const EdgeInsets.all(16), itemCount: _turnIndex + 1, itemBuilder: (context, idx) { final turn = _turns[idx]; return Padding( padding: const EdgeInsets.only(bottom: 14), child: _buildTurn(idx, turn), ); }, ), ), ], ); } Widget _buildTurn(int idx, DialogueTurn turn) { if (turn.type == 'narration') { final isCurrent = idx == _turnIndex; return Container( padding: const EdgeInsets.all(16), decoration: brutalCard(color: kBrutalCream), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( turn.text, textAlign: TextAlign.center, style: GoogleFonts.almarai(color: kBrutalSlate, fontSize: 14, fontWeight: FontWeight.w800, fontStyle: FontStyle.italic), ), if (isCurrent) ...[ const SizedBox(height: 12), FilledButton(onPressed: _advance, child: const Text('CONTINUE')), ], ], ), ); } if (turn.type == 'character') { final isCurrent = idx == _turnIndex; final initial = (turn.speakerName?.isNotEmpty ?? false) ? turn.speakerName![0].toUpperCase() : '?'; return Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( width: 54, height: 54, decoration: BoxDecoration( color: kBrutalBlue, borderRadius: BorderRadius.circular(kBrutalRadius), border: Border.all(color: kBrutalBlack, width: 2), boxShadow: const [ BoxShadow(color: kBrutalBlack, offset: Offset(3, 3), blurRadius: 0), ], ), child: Center( child: Text(initial, style: GoogleFonts.almarai(color: kBrutalWhite, fontWeight: FontWeight.w900, fontSize: 22)), ), ), const SizedBox(width: 10), Expanded( child: Container( padding: const EdgeInsets.all(16), decoration: brutalCard(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (turn.speakerName != null) _Eyebrow(turn.speakerName!.toUpperCase(), color: kBrutalBlue), if (turn.speakerName != null) const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: FuriText( text: turn.text, segments: turn.textSegments, fontSize: 17, fontWeight: FontWeight.w900, ), ), const SizedBox(width: 8), TtsButton(text: turn.text, lang: widget.lang, size: 32), ], ), if (turn.translation != null) ...[ const SizedBox(height: 12), GestureDetector( onTap: () => setState(() => _showTranslation[idx] = !(_showTranslation[idx] ?? false)), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon((_showTranslation[idx] ?? false) ? Icons.visibility_off : Icons.visibility, color: kBrutalBlue, size: 16), const SizedBox(width: 6), Text( (_showTranslation[idx] ?? false) ? turn.translation! : 'Show translation', style: GoogleFonts.almarai(color: kBrutalBlue, fontWeight: FontWeight.w900, fontSize: 13), ), ], ), ), ], if (isCurrent && !_chosen.containsKey(idx)) ...[ const SizedBox(height: 14), FilledButton(onPressed: _advance, child: const Text('CONTINUE')), ], ], ), ), ), ], ); } if (turn.type == 'user_choice') { return Container( padding: const EdgeInsets.all(16), decoration: brutalCard(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( turn.text.isNotEmpty ? turn.text : 'What do you say?', style: GoogleFonts.almarai(color: kBrutalSlate, fontSize: 18, fontWeight: FontWeight.w900), ), const SizedBox(height: 12), ...List.generate(turn.options?.length ?? 0, (optionIndex) { final option = turn.options![optionIndex]; final made = _chosen[idx]; final isChosen = made == optionIndex; final isCorrect = option.isCorrect; Color borderColor = kBrutalBlack; Color bg = kBrutalWhite; Color textColor = kBrutalSlate; if (made != null) { if (isCorrect) { bg = kBrutalGreen; textColor = kBrutalWhite; } else if (isChosen) { bg = kBrutalRed; textColor = kBrutalWhite; } else { textColor = kBrutalMuted; } } return Padding( padding: const EdgeInsets.only(bottom: 10), child: GestureDetector( onTap: made == null ? () => _handleChoice(idx, optionIndex) : null, child: Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(kBrutalRadius), border: Border.all(color: borderColor, width: 2), boxShadow: const [BoxShadow(color: kBrutalBlack, offset: Offset(3, 3), blurRadius: 0)], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ FuriText( text: option.text, segments: option.textSegments, fontSize: 15, color: textColor, fontWeight: FontWeight.w900, ), if (made != null && isChosen && option.feedback.isNotEmpty) ...[ const SizedBox(height: 6), Text(option.feedback, style: GoogleFonts.almarai(color: textColor, fontSize: 12, fontWeight: FontWeight.w900)), ], ], ), ), ), ); }), ], ), ); } return const SizedBox.shrink(); } } class _Chip extends StatelessWidget { const _Chip(this.label); final String label; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: kBrutalWhite, borderRadius: BorderRadius.circular(99), border: Border.all(color: kBrutalBlack, width: 2), ), child: Text(label, style: GoogleFonts.almarai(color: kBrutalSlate, fontSize: 12, fontWeight: FontWeight.w900)), ); } } class _Eyebrow extends StatelessWidget { const _Eyebrow(this.text, {this.color = kBrutalMuted}); final String text; final Color color; @override Widget build(BuildContext context) { return Text( text, style: GoogleFonts.almarai(color: color, fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 1.2), ); } }