stuff
Some checks failed
Web App CI / test (push) Has been cancelled
Deploy Mnemo Cards / Deploy Backend (push) Has been cancelled
Web App CI / build (push) Has been cancelled
Deploy Mnemo Cards / Deploy Web App (push) Has been cancelled
Deploy Mnemo Cards / Final Verification (push) Has been cancelled
Some checks failed
Web App CI / test (push) Has been cancelled
Deploy Mnemo Cards / Deploy Backend (push) Has been cancelled
Web App CI / build (push) Has been cancelled
Deploy Mnemo Cards / Deploy Web App (push) Has been cancelled
Deploy Mnemo Cards / Final Verification (push) Has been cancelled
This commit is contained in:
parent
e7aab1d4ba
commit
9f52aa1e1e
9 changed files with 183 additions and 183 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<ChoiceOption> 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<ChoiceOption> 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<InputLettersQuestion> 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<ChoiceOption> buttons
|
||||
String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List<ChoiceOption> 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 extends Object?>(TResult Function( String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List<ChoiceOption> buttons)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List<ChoiceOption> 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 extends Object?>(TResult Function( String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List<ChoiceOption> buttons) $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List<ChoiceOption> 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 extends Object?>(TResult? Function( String id, String template, String? image, String? audio, String correctAnswer, String word, String type, List<ChoiceOption> buttons)? $default,) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List<ChoiceOption> 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<ChoiceOption> 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<ChoiceOption> buttons = const []}): _buttons = buttons;
|
||||
factory _InputLettersQuestion.fromJson(Map<String, dynamic> 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<String, dynamic> 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<ChoiceOption> buttons
|
||||
String id, String template, String? image, String? audio, String? text, String correctAnswer, String word, String type, List<ChoiceOption> 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
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> _$InputLettersQuestionToJson(
|
|||
'template': instance.template,
|
||||
'image': instance.image,
|
||||
'audio': instance.audio,
|
||||
'text': instance.text,
|
||||
'correctAnswer': instance.correctAnswer,
|
||||
'word': instance.word,
|
||||
'type': instance.type,
|
||||
|
|
|
|||
|
|
@ -511,6 +511,7 @@ class TestsStateManager extends StateManager<TestsState> {
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -396,38 +396,36 @@ class _GamePageState extends State<GamePage> {
|
|||
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<Offset>(
|
||||
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<Offset>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -71,73 +71,73 @@ class _InputLettersWidgetState extends State<InputLettersWidget> {
|
|||
|
||||
@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<InputLettersWidget> {
|
|||
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<InputLettersWidget> {
|
|||
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<InputLettersWidget> {
|
|||
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<InputLettersWidget> {
|
|||
),
|
||||
),
|
||||
);
|
||||
} 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;
|
||||
|
|
|
|||
|
|
@ -179,63 +179,72 @@ class _MatrixWidgetState extends State<MatrixWidget>
|
|||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue