fix
Some checks are pending
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions

This commit is contained in:
Dmitry 2026-01-10 20:29:56 +03:00
parent 99100deb20
commit a71f265a8e
8 changed files with 174 additions and 150 deletions

View file

@ -37,6 +37,11 @@ class UserScopeContainer extends ChildScopeContainer<UserScopeParent>
implements UserScope {
UserScopeContainer({required super.parent});
@override
List<Set<AsyncDep<dynamic>>> get initializeQueue => [
{purchaseEventBroadcasterDep},
];
// State Manager for user state
late final userStateManagerDep = dep(() => UserStateManager());

View file

@ -231,14 +231,31 @@ class GameSessionManager {
if (answer is! String) return false;
// Remove spaces from answer
final answerNoSpaces = answer.replaceAll(' ', '').toLowerCase();
final correctNoSpaces = question.correctAnswer.replaceAll(' ', '').toLowerCase();
final answerNoSpaces = answer.replaceAll(' ', '');
// If lengths don't match, answer is wrong
if (answerNoSpaces.length != correctNoSpaces.length) return false;
final template = question.template;
final expectedAnswer = StringBuffer();
int answerLetterIndex = 0;
for (int i = 0; i < template.length && answerLetterIndex < answerNoSpaces.length; i++) {
final templateChar = template[i];
if (templateChar == '_' || templateChar == '|') {
// This is a slot - use the corresponding letter from correct answer
final correctChar = answerNoSpaces[answerLetterIndex];
if (templateChar == '|') {
// Uppercase slot - expect uppercase
expectedAnswer.write(correctChar.toUpperCase());
} else {
// Lowercase slot - expect lowercase
expectedAnswer.write(correctChar.toLowerCase());
}
answerLetterIndex++;
}
// Skip other characters in template (visible letters, spaces)
}
// Compare user answer with expected answer (case-sensitive for uppercase slots)
return answerNoSpaces == question.correctAnswer;
return answerNoSpaces.toLowerCase() == expectedAnswer.toString().toLowerCase();
}
bool _validateMatch(MatchQuestion question, dynamic answer) {

View file

@ -825,7 +825,7 @@ class _CardSide extends StatelessWidget {
'Нет мнемоники',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w400,
fontWeight: FontWeight.w700,
color: colorScheme.onSurface.withOpacity(0.38),
),
textAlign: TextAlign.center,

View file

@ -398,24 +398,27 @@ class _CardSide extends StatelessWidget {
return Hero(
tag: 'card_${card.id}',
child: Container(
width: cardWidth,
height: cardHeight,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: packColor, width: 3),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(21),
child: isFront ? _buildFrontLayout() : _buildBackLayout(),
child: Material(
color: Colors.transparent,
child: Container(
width: cardWidth,
height: cardHeight,
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: packColor, width: 3),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(21),
child: isFront ? _buildFrontLayout() : _buildBackLayout(),
),
),
),
);
@ -622,7 +625,7 @@ class _CardSide extends StatelessWidget {
card.original!,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w400,
fontWeight: FontWeight.w700,
color: colorScheme.onSurface,
),
textAlign: TextAlign.center,

View file

@ -301,7 +301,7 @@ class AnswerOptions extends StatelessWidget {
duration: const Duration(milliseconds: 300),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: textColor,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
fontWeight: FontWeight.bold,
),
child: Text(
text,
@ -320,9 +320,7 @@ class AnswerOptions extends StatelessWidget {
duration: const Duration(milliseconds: 300),
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: textColor,
fontWeight: isSelected || (isAnswerSubmitted && isCorrectOption)
? FontWeight.w600
: FontWeight.normal,
fontWeight: FontWeight.bold,
fontSize: isSelected ? 17 : 16,
),
child: Text(

View file

@ -142,7 +142,7 @@ class _InputLettersWidgetState extends State<InputLettersWidget> {
mainAxisSize: MainAxisSize.min,
children: [
// Image display (if available)
if (widget.question.image != null) ...[
if (widget.question.image != null && false) ...[
SizedBox(
height: imageHeight,
child: Center(

View file

@ -192,107 +192,105 @@ class _MatrixWidgetState extends State<MatrixWidget>
final targetPaddingH = 20.w;
final targetPaddingV = 16.h;
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
// Calculate the grid size needed
final availableWidth = constraints.maxWidth;
final availableHeight = constraints.maxHeight;
return Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
// Calculate the grid size needed
final availableWidth = constraints.maxWidth;
final availableHeight = constraints.maxHeight;
// Calculate grid dimensions including spacing
final totalSpacingWidth = (size - 1) * cellSpacing;
final totalSpacingHeight = (size - 1) * cellSpacing;
final gridWidthNeeded = availableWidth - totalSpacingWidth;
final gridHeightNeeded = availableHeight - totalSpacingHeight;
// Calculate grid dimensions including spacing
final totalSpacingWidth = (size - 1) * cellSpacing;
final totalSpacingHeight = (size - 1) * cellSpacing;
final gridWidthNeeded = availableWidth - totalSpacingWidth;
final gridHeightNeeded = availableHeight - totalSpacingHeight;
// Calculate cell size based on available space
final cellWidthByWidth = gridWidthNeeded / size;
final cellHeightByHeight = gridHeightNeeded / size;
// Calculate cell size based on available space
final cellWidthByWidth = gridWidthNeeded / size;
final cellHeightByHeight = gridHeightNeeded / size;
// Use the smaller dimension to ensure everything fits
final cellSize =
math.min(cellWidthByWidth, cellHeightByHeight);
// Use the smaller dimension to ensure everything fits
final cellSize =
math.min(cellWidthByWidth, cellHeightByHeight);
// Calculate actual grid size
final actualGridWidth =
cellSize * size + totalSpacingWidth;
final actualGridHeight =
cellSize * size + totalSpacingHeight;
// Calculate actual grid size
final actualGridWidth =
cellSize * size + totalSpacingWidth;
final actualGridHeight =
cellSize * size + totalSpacingHeight;
// Calculate scale to fit if needed
final scaleX = availableWidth / actualGridWidth;
final scaleY = availableHeight / actualGridHeight;
final scale = math.min(1.0, math.min(scaleX, scaleY));
// Calculate scale to fit if needed
final scaleX = availableWidth / actualGridWidth;
final scaleY = availableHeight / actualGridHeight;
final scale = math.min(1.0, math.min(scaleX, scaleY));
return Center(
child: Transform.scale(
scale: scale,
child: SizedBox(
width: actualGridWidth,
height: actualGridHeight,
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);
},
return Center(
child: Transform.scale(
scale: scale,
child: SizedBox(
width: actualGridWidth,
height: actualGridHeight,
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),
Align(
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
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),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
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),
],
),
),
SizedBox(height: spacingBetween),
Align(
alignment: Alignment.center,
child: Container(
alignment: Alignment.center,
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),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
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),
],
),
),
),
],
),
);
),
],
);
}
Widget _buildTargetContent(BuildContext context) {

View file

@ -85,35 +85,38 @@ class PackCardItem extends StatelessWidget {
},
child: Hero(
tag: 'card_${card.id}',
child: Container(
key: ValueKey('$cardRootKeyPrefix${card.id}'),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(color: color.withOpacity(0.3), width: 1),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.05),
blurRadius: 2.0,
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
fit: StackFit.expand,
children: [
hasImage
? _buildCardWithImage(context)
: _buildCardTextOnly(context),
Positioned(
top: 4,
right: 4,
child: _buildFavoriteButton(context),
),
],
child: Material(
color: Colors.transparent,
child: Container(
key: ValueKey('$cardRootKeyPrefix${card.id}'),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
border: Border.all(color: color.withOpacity(0.3), width: 1),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.05),
blurRadius: 2.0,
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
fit: StackFit.expand,
children: [
hasImage
? _buildCardWithImage(context)
: _buildCardTextOnly(context),
Positioned(
top: 4,
right: 4,
child: _buildFavoriteButton(context),
),
],
),
),
),
),