diff --git a/mnemo_cards_web_v2/lib/domain/models/game_question.dart b/mnemo_cards_web_v2/lib/domain/models/game_question.dart index c03c66b..468b8cc 100644 --- a/mnemo_cards_web_v2/lib/domain/models/game_question.dart +++ b/mnemo_cards_web_v2/lib/domain/models/game_question.dart @@ -63,6 +63,7 @@ abstract class InputLettersQuestion with _$InputLettersQuestion { required String template, // e.g., "H _ _ L _" String? image, String? audio, + String? text, // Question text to display required String correctAnswer, required String word, @Default('inputLetters') String type, diff --git a/mnemo_cards_web_v2/lib/domain/models/game_question.freezed.dart b/mnemo_cards_web_v2/lib/domain/models/game_question.freezed.dart index 4e44e1a..c9fa9d2 100644 --- a/mnemo_cards_web_v2/lib/domain/models/game_question.freezed.dart +++ b/mnemo_cards_web_v2/lib/domain/models/game_question.freezed.dart @@ -1131,7 +1131,8 @@ as String, mixin _$InputLettersQuestion { String get id; String get template;// e.g., "H _ _ L _" - String? get image; String? get audio; String get correctAnswer; String get word; String get type; List get buttons; + String? get image; String? get audio; String? get text;// Question text to display + String get correctAnswer; String get word; String get type; List get buttons; /// Create a copy of InputLettersQuestion /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -1144,16 +1145,16 @@ $InputLettersQuestionCopyWith get copyWith => _$InputLette @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is InputLettersQuestion&&(identical(other.id, id) || other.id == id)&&(identical(other.template, template) || other.template == template)&&(identical(other.image, image) || other.image == image)&&(identical(other.audio, audio) || other.audio == audio)&&(identical(other.correctAnswer, correctAnswer) || other.correctAnswer == correctAnswer)&&(identical(other.word, word) || other.word == word)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.buttons, buttons)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is InputLettersQuestion&&(identical(other.id, id) || other.id == id)&&(identical(other.template, template) || other.template == template)&&(identical(other.image, image) || other.image == image)&&(identical(other.audio, audio) || other.audio == audio)&&(identical(other.text, text) || other.text == text)&&(identical(other.correctAnswer, correctAnswer) || other.correctAnswer == correctAnswer)&&(identical(other.word, word) || other.word == word)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.buttons, buttons)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,template,image,audio,correctAnswer,word,type,const DeepCollectionEquality().hash(buttons)); +int get hashCode => Object.hash(runtimeType,id,template,image,audio,text,correctAnswer,word,type,const DeepCollectionEquality().hash(buttons)); @override String toString() { - return 'InputLettersQuestion(id: $id, template: $template, image: $image, audio: $audio, correctAnswer: $correctAnswer, word: $word, type: $type, buttons: $buttons)'; + return 'InputLettersQuestion(id: $id, template: $template, image: $image, audio: $audio, text: $text, correctAnswer: $correctAnswer, word: $word, type: $type, buttons: $buttons)'; } @@ -1164,7 +1165,7 @@ abstract mixin class $InputLettersQuestionCopyWith<$Res> { factory $InputLettersQuestionCopyWith(InputLettersQuestion value, $Res Function(InputLettersQuestion) _then) = _$InputLettersQuestionCopyWithImpl; @useResult $Res call({ - String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List buttons + String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List buttons }); @@ -1181,12 +1182,13 @@ class _$InputLettersQuestionCopyWithImpl<$Res> /// Create a copy of InputLettersQuestion /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? template = null,Object? image = freezed,Object? audio = freezed,Object? correctAnswer = null,Object? word = null,Object? type = null,Object? buttons = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? template = null,Object? image = freezed,Object? audio = freezed,Object? text = freezed,Object? correctAnswer = null,Object? word = null,Object? type = null,Object? buttons = null,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,template: null == template ? _self.template : template // ignore: cast_nullable_to_non_nullable as String,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable as String?,audio: freezed == audio ? _self.audio : audio // ignore: cast_nullable_to_non_nullable +as String?,text: freezed == text ? _self.text : text // ignore: cast_nullable_to_non_nullable as String?,correctAnswer: null == correctAnswer ? _self.correctAnswer : correctAnswer // ignore: cast_nullable_to_non_nullable as String,word: null == word ? _self.word : word // ignore: cast_nullable_to_non_nullable as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable @@ -1276,10 +1278,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List buttons)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List buttons)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _InputLettersQuestion() when $default != null: -return $default(_that.id,_that.template,_that.image,_that.audio,_that.correctAnswer,_that.word,_that.type,_that.buttons);case _: +return $default(_that.id,_that.template,_that.image,_that.audio,_that.text,_that.correctAnswer,_that.word,_that.type,_that.buttons);case _: return orElse(); } @@ -1297,10 +1299,10 @@ return $default(_that.id,_that.template,_that.image,_that.audio,_that.correctAns /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List buttons) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List buttons) $default,) {final _that = this; switch (_that) { case _InputLettersQuestion(): -return $default(_that.id,_that.template,_that.image,_that.audio,_that.correctAnswer,_that.word,_that.type,_that.buttons);case _: +return $default(_that.id,_that.template,_that.image,_that.audio,_that.text,_that.correctAnswer,_that.word,_that.type,_that.buttons);case _: throw StateError('Unexpected subclass'); } @@ -1317,10 +1319,10 @@ return $default(_that.id,_that.template,_that.image,_that.audio,_that.correctAns /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List buttons)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List buttons)? $default,) {final _that = this; switch (_that) { case _InputLettersQuestion() when $default != null: -return $default(_that.id,_that.template,_that.image,_that.audio,_that.correctAnswer,_that.word,_that.type,_that.buttons);case _: +return $default(_that.id,_that.template,_that.image,_that.audio,_that.text,_that.correctAnswer,_that.word,_that.type,_that.buttons);case _: return null; } @@ -1332,7 +1334,7 @@ return $default(_that.id,_that.template,_that.image,_that.audio,_that.correctAns @JsonSerializable() class _InputLettersQuestion implements InputLettersQuestion { - const _InputLettersQuestion({required this.id, required this.template, this.image, this.audio, required this.correctAnswer, required this.word, this.type = 'inputLetters', final List buttons = const []}): _buttons = buttons; + const _InputLettersQuestion({required this.id, required this.template, this.image, this.audio, this.text, required this.correctAnswer, required this.word, this.type = 'inputLetters', final List buttons = const []}): _buttons = buttons; factory _InputLettersQuestion.fromJson(Map json) => _$InputLettersQuestionFromJson(json); @override final String id; @@ -1340,6 +1342,8 @@ class _InputLettersQuestion implements InputLettersQuestion { // e.g., "H _ _ L _" @override final String? image; @override final String? audio; +@override final String? text; +// Question text to display @override final String correctAnswer; @override final String word; @override@JsonKey() final String type; @@ -1364,16 +1368,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _InputLettersQuestion&&(identical(other.id, id) || other.id == id)&&(identical(other.template, template) || other.template == template)&&(identical(other.image, image) || other.image == image)&&(identical(other.audio, audio) || other.audio == audio)&&(identical(other.correctAnswer, correctAnswer) || other.correctAnswer == correctAnswer)&&(identical(other.word, word) || other.word == word)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._buttons, _buttons)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _InputLettersQuestion&&(identical(other.id, id) || other.id == id)&&(identical(other.template, template) || other.template == template)&&(identical(other.image, image) || other.image == image)&&(identical(other.audio, audio) || other.audio == audio)&&(identical(other.text, text) || other.text == text)&&(identical(other.correctAnswer, correctAnswer) || other.correctAnswer == correctAnswer)&&(identical(other.word, word) || other.word == word)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._buttons, _buttons)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,template,image,audio,correctAnswer,word,type,const DeepCollectionEquality().hash(_buttons)); +int get hashCode => Object.hash(runtimeType,id,template,image,audio,text,correctAnswer,word,type,const DeepCollectionEquality().hash(_buttons)); @override String toString() { - return 'InputLettersQuestion(id: $id, template: $template, image: $image, audio: $audio, correctAnswer: $correctAnswer, word: $word, type: $type, buttons: $buttons)'; + return 'InputLettersQuestion(id: $id, template: $template, image: $image, audio: $audio, text: $text, correctAnswer: $correctAnswer, word: $word, type: $type, buttons: $buttons)'; } @@ -1384,7 +1388,7 @@ abstract mixin class _$InputLettersQuestionCopyWith<$Res> implements $InputLette factory _$InputLettersQuestionCopyWith(_InputLettersQuestion value, $Res Function(_InputLettersQuestion) _then) = __$InputLettersQuestionCopyWithImpl; @override @useResult $Res call({ - String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List buttons + String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List buttons }); @@ -1401,12 +1405,13 @@ class __$InputLettersQuestionCopyWithImpl<$Res> /// Create a copy of InputLettersQuestion /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? template = null,Object? image = freezed,Object? audio = freezed,Object? correctAnswer = null,Object? word = null,Object? type = null,Object? buttons = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? template = null,Object? image = freezed,Object? audio = freezed,Object? text = freezed,Object? correctAnswer = null,Object? word = null,Object? type = null,Object? buttons = null,}) { return _then(_InputLettersQuestion( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,template: null == template ? _self.template : template // ignore: cast_nullable_to_non_nullable as String,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable as String?,audio: freezed == audio ? _self.audio : audio // ignore: cast_nullable_to_non_nullable +as String?,text: freezed == text ? _self.text : text // ignore: cast_nullable_to_non_nullable as String?,correctAnswer: null == correctAnswer ? _self.correctAnswer : correctAnswer // ignore: cast_nullable_to_non_nullable as String,word: null == word ? _self.word : word // ignore: cast_nullable_to_non_nullable as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable diff --git a/mnemo_cards_web_v2/lib/domain/models/game_question.g.dart b/mnemo_cards_web_v2/lib/domain/models/game_question.g.dart index 96f7f66..6e72111 100644 --- a/mnemo_cards_web_v2/lib/domain/models/game_question.g.dart +++ b/mnemo_cards_web_v2/lib/domain/models/game_question.g.dart @@ -113,6 +113,7 @@ _InputLettersQuestion _$InputLettersQuestionFromJson( template: json['template'] as String, image: json['image'] as String?, audio: json['audio'] as String?, + text: json['text'] as String?, correctAnswer: json['correctAnswer'] as String, word: json['word'] as String, type: json['type'] as String? ?? 'inputLetters', @@ -130,6 +131,7 @@ Map _$InputLettersQuestionToJson( 'template': instance.template, 'image': instance.image, 'audio': instance.audio, + 'text': instance.text, 'correctAnswer': instance.correctAnswer, 'word': instance.word, 'type': instance.type, diff --git a/mnemo_cards_web_v2/lib/domain/state/tests_state_manager.dart b/mnemo_cards_web_v2/lib/domain/state/tests_state_manager.dart index f8a2ec8..7460894 100644 --- a/mnemo_cards_web_v2/lib/domain/state/tests_state_manager.dart +++ b/mnemo_cards_web_v2/lib/domain/state/tests_state_manager.dart @@ -511,6 +511,7 @@ class TestsStateManager extends StateManager { question.imageUrl ?? question.image, // Use presigned URL if available audio: question.audio, + text: question.text, // Question text to display correctAnswer: question.answer, word: question.word, buttons: optionItems, diff --git a/mnemo_cards_web_v2/lib/presentation/pages/game/game_page.dart b/mnemo_cards_web_v2/lib/presentation/pages/game/game_page.dart index b081cd4..bbf24e4 100644 --- a/mnemo_cards_web_v2/lib/presentation/pages/game/game_page.dart +++ b/mnemo_cards_web_v2/lib/presentation/pages/game/game_page.dart @@ -396,38 +396,36 @@ class _GamePageState extends State { minHeight: 200.h, ), child: currentQuestion.when( - multipleChoice: (q) => SingleChildScrollView( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: SlideTransition( - position: Tween( - begin: const Offset(0.05, 0), - end: Offset.zero, - ).animate(animation), - child: child, - ), - ); - }, - child: Container( - key: ValueKey( - 'question_input_${currentQuestion.hashCode}', - ), - child: AnswerOptions( - question: q, - selectedAnswer: - _getSelectedAnswerForMultipleChoice( - q, - questionResults, - ), - onAnswerSelected: _onAnswerSelected, - isAnswerSubmitted: isAnswerSubmitted, - isCorrect: isCorrect, + multipleChoice: (q) => AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0.05, 0), + end: Offset.zero, + ).animate(animation), + child: child, ), + ); + }, + child: Container( + key: ValueKey( + 'question_input_${currentQuestion.hashCode}', + ), + child: AnswerOptions( + question: q, + selectedAnswer: + _getSelectedAnswerForMultipleChoice( + q, + questionResults, + ), + onAnswerSelected: _onAnswerSelected, + isAnswerSubmitted: isAnswerSubmitted, + isCorrect: isCorrect, ), ), ), diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/game/answer_options.dart b/mnemo_cards_web_v2/lib/presentation/widgets/game/answer_options.dart index 4bd97fe..501b137 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/game/answer_options.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/game/answer_options.dart @@ -61,18 +61,19 @@ class AnswerOptions extends StatelessWidget { // Aspect ratio = width / height final itemWidth = constraints.maxWidth / crossAxisCount - spacing; childAspectRatio = itemWidth / itemHeight; - // Clamp to reasonable values + // For images, ensure we have enough vertical space + // Clamp to reasonable values - images need more vertical space (lower aspect ratio) childAspectRatio = childAspectRatio.clamp( - hasImages ? 0.8 : 2.0, - hasImages ? 1.5 : 6.0, + hasImages ? 0.7 : 2.0, + hasImages ? 1.2 : 6.0, ); } else { // Fallback to fixed aspect ratios - childAspectRatio = hasImages ? 1.1 : 4.0; + // For images, use lower aspect ratio to give more vertical space + childAspectRatio = hasImages ? 0.9 : 4.0; } return GridView.builder( - shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/game/input_letters_widget.dart b/mnemo_cards_web_v2/lib/presentation/widgets/game/input_letters_widget.dart index 905fe0f..9f75e91 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/game/input_letters_widget.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/game/input_letters_widget.dart @@ -71,73 +71,73 @@ class _InputLettersWidgetState extends State { @override Widget build(BuildContext context) { - // Fixed spacing and padding for consistent layout - final containerPadding = 16.w; - final containerMargin = 16.h; - final spacingAfterTitle = 12.h; - final spacingAfterWord = 12.h; - final spacingAfterGrid = 16.h; + // Fixed spacing for consistent layout + final spacingAfterImage = 16.h; + final spacingAfterTemplate = 16.h; return LayoutBuilder( builder: (context, constraints) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // Display the template with current input + // Image and template display Expanded( - flex: 2, - child: SingleChildScrollView( - child: Container( - padding: EdgeInsets.all(containerPadding), - margin: EdgeInsets.only(bottom: containerMargin), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16.r), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Fill in the blanks:', - style: Theme.of(context).textTheme.titleLarge - ?.copyWith(fontWeight: FontWeight.w600), - textAlign: TextAlign.center, - ), - if (widget.question.word.isNotEmpty) - SizedBox(height: spacingAfterTitle), - if (widget.question.word.isNotEmpty) ...[ - Text( - widget.question.word, - style: Theme.of(context).textTheme.titleLarge - ?.copyWith( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, + flex: widget.question.buttons.isNotEmpty ? 2 : 4, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Image display (if available) + if (widget.question.image != null) ...[ + Expanded( + flex: 3, + child: Center( + child: Image.network( + widget.question.image!, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 120.h, + width: 120.w, + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8.r), ), - textAlign: TextAlign.center, + child: Icon( + Icons.image_not_supported, + size: 48.sp, + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ); + }, ), - ], - SizedBox(height: spacingAfterWord), - _buildTemplateDisplay(), - ], + ), + ), + SizedBox(height: spacingAfterImage), + ], + + // Template display (only blanks, no letters) + Expanded( + flex: widget.question.image != null ? 2 : 3, + child: Center( + child: _buildTemplateDisplay(), + ), ), - ), + ], ), ), + SizedBox(height: spacingAfterTemplate), + // Buttons with images or text (if available) if (widget.question.buttons.isNotEmpty) ...[ Expanded( - flex: 3, + flex: 5, child: _buildButtonsGrid(constraints), ), - SizedBox(height: spacingAfterGrid), ] else ...[ // Input field (only if no buttons) Container( @@ -169,9 +169,10 @@ class _InputLettersWidgetState extends State { onSubmitted: _submitAnswer, ), ), - SizedBox(height: spacingAfterGrid), ], + SizedBox(height: 16.h), + // Submit button ElevatedButton.icon( onPressed: _currentAnswer.isNotEmpty ? _submitAnswer : null, @@ -204,11 +205,12 @@ class _InputLettersWidgetState extends State { int inputIndex = 0; // Fixed cell sizes for consistent layout - final cellWidth = 28.w; final cellHeight = 48.h; final cellFontSize = 20.sp; final blankCellWidth = 32.w; + final spacingBetweenBlanks = 8.w; + // Only show blanks (squares for input), ignore other characters for (int i = 0; i < template.length; i++) { final char = template[i]; @@ -223,7 +225,7 @@ class _InputLettersWidgetState extends State { Container( width: blankCellWidth, height: cellHeight, - margin: EdgeInsets.symmetric(horizontal: 2.w), + margin: EdgeInsets.symmetric(horizontal: spacingBetweenBlanks / 2), decoration: BoxDecoration( border: Border.all( color: Theme.of(context).colorScheme.primary, @@ -246,29 +248,8 @@ class _InputLettersWidgetState extends State { ), ), ); - } else if (char == ' ') { - // Space character - add small spacing - parts.add(SizedBox(width: 8.w)); - } else { - // This is a regular character - parts.add( - Container( - width: cellWidth, - height: cellHeight, - margin: EdgeInsets.symmetric(horizontal: 2.w), - alignment: Alignment.center, - child: Text( - char, - style: TextStyle( - fontSize: cellFontSize, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, - fontFeatures: const [FontFeature.tabularFigures()], - ), - ), - ), - ); } + // Ignore other characters (letters and spaces) - don't display them } return parts; diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/game/matrix_widget.dart b/mnemo_cards_web_v2/lib/presentation/widgets/game/matrix_widget.dart index 5ee3c43..b1bd66b 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/game/matrix_widget.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/game/matrix_widget.dart @@ -179,63 +179,72 @@ class _MatrixWidgetState extends State final targetPaddingH = 20.w; final targetPaddingV = 16.h; - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: GridView.builder( - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: size, - crossAxisSpacing: cellSpacing, - mainAxisSpacing: cellSpacing, - childAspectRatio: 1.0, - ), - itemCount: total, - itemBuilder: (context, index) { - final card = slots[index]; - return _buildSlot(context, card); - }, - ), - ), - SizedBox(height: spacingBetween), - Container( - padding: EdgeInsets.symmetric( - horizontal: targetPaddingH, - vertical: targetPaddingV, - ), - decoration: BoxDecoration( - color: colorScheme.surface, - borderRadius: BorderRadius.circular(24.r), - border: Border.all(color: colorScheme.primary, width: 3), - boxShadow: [ - BoxShadow( - color: colorScheme.shadow.withOpacity(0.3), - blurRadius: 20, - offset: const Offset(0, 10), + return LayoutBuilder( + builder: (context, constraints) { + return SizedBox( + height: constraints.maxHeight.isFinite && constraints.maxHeight > 0 + ? constraints.maxHeight + : null, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: GridView.builder( + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: size, + crossAxisSpacing: cellSpacing, + mainAxisSpacing: cellSpacing, + childAspectRatio: 1.0, + ), + itemCount: total, + itemBuilder: (context, index) { + final card = slots[index]; + return _buildSlot(context, card); + }, + ), + ), + SizedBox(height: spacingBetween), + Container( + padding: EdgeInsets.symmetric( + horizontal: targetPaddingH, + vertical: targetPaddingV, + ), + decoration: BoxDecoration( + color: colorScheme.surface, + borderRadius: BorderRadius.circular(24.r), + border: Border.all(color: colorScheme.primary, width: 3), + boxShadow: [ + BoxShadow( + color: colorScheme.shadow.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.question.text != null && + widget.question.text!.isNotEmpty) + Text( + widget.question.text!, + style: textTheme.labelLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + if (widget.question.text != null && + widget.question.text!.isNotEmpty) + SizedBox(height: 8.h), + _buildTargetContent(context), + ], + ), ), ], ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.question.text != null && - widget.question.text!.isNotEmpty) - Text( - widget.question.text!, - style: textTheme.labelLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - textAlign: TextAlign.center, - ), - if (widget.question.text != null && - widget.question.text!.isNotEmpty) - SizedBox(height: 8.h), - _buildTargetContent(context), - ], - ), - ), - ], + ); + }, ); } diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/game/question_display.dart b/mnemo_cards_web_v2/lib/presentation/widgets/game/question_display.dart index 7e867cc..b68774f 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/game/question_display.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/game/question_display.dart @@ -42,9 +42,11 @@ class QuestionDisplay extends StatelessWidget { InputLettersQuestion question, BuildContext context, ) { + // For input letters questions, show text (if available), image and audio + // Template is displayed in InputLettersWidget itself return _buildQuestionContent( context: context, - text: question.template, // Display the template with blanks + text: question.text ?? '', // Display question text if available image: question.image, audio: question.audio, );