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
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:
parent
99100deb20
commit
a71f265a8e
8 changed files with 174 additions and 150 deletions
|
|
@ -37,6 +37,11 @@ class UserScopeContainer extends ChildScopeContainer<UserScopeParent>
|
||||||
implements UserScope {
|
implements UserScope {
|
||||||
UserScopeContainer({required super.parent});
|
UserScopeContainer({required super.parent});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Set<AsyncDep<dynamic>>> get initializeQueue => [
|
||||||
|
{purchaseEventBroadcasterDep},
|
||||||
|
];
|
||||||
|
|
||||||
// State Manager for user state
|
// State Manager for user state
|
||||||
late final userStateManagerDep = dep(() => UserStateManager());
|
late final userStateManagerDep = dep(() => UserStateManager());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,14 +231,31 @@ class GameSessionManager {
|
||||||
if (answer is! String) return false;
|
if (answer is! String) return false;
|
||||||
|
|
||||||
// Remove spaces from answer
|
// Remove spaces from answer
|
||||||
final answerNoSpaces = answer.replaceAll(' ', '').toLowerCase();
|
final answerNoSpaces = answer.replaceAll(' ', '');
|
||||||
final correctNoSpaces = question.correctAnswer.replaceAll(' ', '').toLowerCase();
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// If lengths don't match, answer is wrong
|
|
||||||
if (answerNoSpaces.length != correctNoSpaces.length) return false;
|
|
||||||
|
|
||||||
// Compare user answer with expected answer (case-sensitive for uppercase slots)
|
// 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) {
|
bool _validateMatch(MatchQuestion question, dynamic answer) {
|
||||||
|
|
|
||||||
|
|
@ -825,7 +825,7 @@ class _CardSide extends StatelessWidget {
|
||||||
'Нет мнемоники',
|
'Нет мнемоники',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w700,
|
||||||
color: colorScheme.onSurface.withOpacity(0.38),
|
color: colorScheme.onSurface.withOpacity(0.38),
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
|
||||||
|
|
@ -398,24 +398,27 @@ class _CardSide extends StatelessWidget {
|
||||||
|
|
||||||
return Hero(
|
return Hero(
|
||||||
tag: 'card_${card.id}',
|
tag: 'card_${card.id}',
|
||||||
child: Container(
|
child: Material(
|
||||||
width: cardWidth,
|
color: Colors.transparent,
|
||||||
height: cardHeight,
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
width: cardWidth,
|
||||||
color: colorScheme.surface,
|
height: cardHeight,
|
||||||
borderRadius: BorderRadius.circular(24),
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: packColor, width: 3),
|
color: colorScheme.surface,
|
||||||
boxShadow: [
|
borderRadius: BorderRadius.circular(24),
|
||||||
BoxShadow(
|
border: Border.all(color: packColor, width: 3),
|
||||||
color: colorScheme.shadow.withOpacity(0.3),
|
boxShadow: [
|
||||||
blurRadius: 20,
|
BoxShadow(
|
||||||
offset: const Offset(0, 10),
|
color: colorScheme.shadow.withOpacity(0.3),
|
||||||
),
|
blurRadius: 20,
|
||||||
],
|
offset: const Offset(0, 10),
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
],
|
||||||
borderRadius: BorderRadius.circular(21),
|
),
|
||||||
child: isFront ? _buildFrontLayout() : _buildBackLayout(),
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(21),
|
||||||
|
child: isFront ? _buildFrontLayout() : _buildBackLayout(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -622,7 +625,7 @@ class _CardSide extends StatelessWidget {
|
||||||
card.original!,
|
card.original!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w700,
|
||||||
color: colorScheme.onSurface,
|
color: colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,7 @@ class AnswerOptions extends StatelessWidget {
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
|
|
@ -320,9 +320,7 @@ class AnswerOptions extends StatelessWidget {
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontWeight: isSelected || (isAnswerSubmitted && isCorrectOption)
|
fontWeight: FontWeight.bold,
|
||||||
? FontWeight.w600
|
|
||||||
: FontWeight.normal,
|
|
||||||
fontSize: isSelected ? 17 : 16,
|
fontSize: isSelected ? 17 : 16,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ class _InputLettersWidgetState extends State<InputLettersWidget> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Image display (if available)
|
// Image display (if available)
|
||||||
if (widget.question.image != null) ...[
|
if (widget.question.image != null && false) ...[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: imageHeight,
|
height: imageHeight,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
|
||||||
|
|
@ -192,107 +192,105 @@ class _MatrixWidgetState extends State<MatrixWidget>
|
||||||
final targetPaddingH = 20.w;
|
final targetPaddingH = 20.w;
|
||||||
final targetPaddingV = 16.h;
|
final targetPaddingV = 16.h;
|
||||||
|
|
||||||
return Expanded(
|
return Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
// Calculate the grid size needed
|
// Calculate the grid size needed
|
||||||
final availableWidth = constraints.maxWidth;
|
final availableWidth = constraints.maxWidth;
|
||||||
final availableHeight = constraints.maxHeight;
|
final availableHeight = constraints.maxHeight;
|
||||||
|
|
||||||
// Calculate grid dimensions including spacing
|
// Calculate grid dimensions including spacing
|
||||||
final totalSpacingWidth = (size - 1) * cellSpacing;
|
final totalSpacingWidth = (size - 1) * cellSpacing;
|
||||||
final totalSpacingHeight = (size - 1) * cellSpacing;
|
final totalSpacingHeight = (size - 1) * cellSpacing;
|
||||||
final gridWidthNeeded = availableWidth - totalSpacingWidth;
|
final gridWidthNeeded = availableWidth - totalSpacingWidth;
|
||||||
final gridHeightNeeded = availableHeight - totalSpacingHeight;
|
final gridHeightNeeded = availableHeight - totalSpacingHeight;
|
||||||
|
|
||||||
// Calculate cell size based on available space
|
// Calculate cell size based on available space
|
||||||
final cellWidthByWidth = gridWidthNeeded / size;
|
final cellWidthByWidth = gridWidthNeeded / size;
|
||||||
final cellHeightByHeight = gridHeightNeeded / size;
|
final cellHeightByHeight = gridHeightNeeded / size;
|
||||||
|
|
||||||
// Use the smaller dimension to ensure everything fits
|
// Use the smaller dimension to ensure everything fits
|
||||||
final cellSize =
|
final cellSize =
|
||||||
math.min(cellWidthByWidth, cellHeightByHeight);
|
math.min(cellWidthByWidth, cellHeightByHeight);
|
||||||
|
|
||||||
// Calculate actual grid size
|
// Calculate actual grid size
|
||||||
final actualGridWidth =
|
final actualGridWidth =
|
||||||
cellSize * size + totalSpacingWidth;
|
cellSize * size + totalSpacingWidth;
|
||||||
final actualGridHeight =
|
final actualGridHeight =
|
||||||
cellSize * size + totalSpacingHeight;
|
cellSize * size + totalSpacingHeight;
|
||||||
|
|
||||||
// Calculate scale to fit if needed
|
// Calculate scale to fit if needed
|
||||||
final scaleX = availableWidth / actualGridWidth;
|
final scaleX = availableWidth / actualGridWidth;
|
||||||
final scaleY = availableHeight / actualGridHeight;
|
final scaleY = availableHeight / actualGridHeight;
|
||||||
final scale = math.min(1.0, math.min(scaleX, scaleY));
|
final scale = math.min(1.0, math.min(scaleX, scaleY));
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Transform.scale(
|
child: Transform.scale(
|
||||||
scale: scale,
|
scale: scale,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: actualGridWidth,
|
width: actualGridWidth,
|
||||||
height: actualGridHeight,
|
height: actualGridHeight,
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: size,
|
crossAxisCount: size,
|
||||||
crossAxisSpacing: cellSpacing,
|
crossAxisSpacing: cellSpacing,
|
||||||
mainAxisSpacing: cellSpacing,
|
mainAxisSpacing: cellSpacing,
|
||||||
childAspectRatio: 1.0,
|
childAspectRatio: 1.0,
|
||||||
),
|
|
||||||
itemCount: total,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final card = slots[index];
|
|
||||||
return _buildSlot(context, card);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
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) {
|
Widget _buildTargetContent(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -85,35 +85,38 @@ class PackCardItem extends StatelessWidget {
|
||||||
},
|
},
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: 'card_${card.id}',
|
tag: 'card_${card.id}',
|
||||||
child: Container(
|
child: Material(
|
||||||
key: ValueKey('$cardRootKeyPrefix${card.id}'),
|
color: Colors.transparent,
|
||||||
alignment: Alignment.center,
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
key: ValueKey('$cardRootKeyPrefix${card.id}'),
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
alignment: Alignment.center,
|
||||||
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
boxShadow: [
|
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
||||||
BoxShadow(
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(
|
boxShadow: [
|
||||||
context,
|
BoxShadow(
|
||||||
).colorScheme.primary.withOpacity(0.05),
|
color: Theme.of(
|
||||||
blurRadius: 2.0,
|
context,
|
||||||
),
|
).colorScheme.primary.withOpacity(0.05),
|
||||||
],
|
blurRadius: 2.0,
|
||||||
),
|
),
|
||||||
clipBehavior: Clip.antiAlias,
|
],
|
||||||
child: Stack(
|
),
|
||||||
fit: StackFit.expand,
|
clipBehavior: Clip.antiAlias,
|
||||||
children: [
|
child: Stack(
|
||||||
hasImage
|
fit: StackFit.expand,
|
||||||
? _buildCardWithImage(context)
|
children: [
|
||||||
: _buildCardTextOnly(context),
|
hasImage
|
||||||
Positioned(
|
? _buildCardWithImage(context)
|
||||||
top: 4,
|
: _buildCardTextOnly(context),
|
||||||
right: 4,
|
Positioned(
|
||||||
child: _buildFavoriteButton(context),
|
top: 4,
|
||||||
),
|
right: 4,
|
||||||
],
|
child: _buildFavoriteButton(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue