Browse Source

fix: question feature

pull/33/head
AmirrezaChegini 5 hours ago
parent
commit
741dbbc130
  1. BIN
      assets/images/behind_diamond.png
  2. 30
      assets/svg/icon_notif.svg
  3. 8
      lib/common_ui/resources/my_animations.dart
  4. 2
      lib/common_ui/resources/my_assets.dart
  5. 9
      lib/common_ui/resources/my_audios.dart
  6. 7
      lib/core/routers/hero_dialog_route.dart
  7. 8
      lib/core/routers/my_routes.dart
  8. 50
      lib/core/widgets/animations/fade_anim.dart
  9. 51
      lib/core/widgets/animations/scale_anim.dart
  10. 56
      lib/core/widgets/animations/ship_anim.dart
  11. 61
      lib/core/widgets/animations/slide_anim.dart
  12. 77
      lib/core/widgets/answer_box/answer_box.dart
  13. 56
      lib/core/widgets/answer_box/answer_box_show.dart
  14. 267
      lib/core/widgets/answer_box/styles/picture_box.dart
  15. 10
      lib/core/widgets/answer_box/styles/text_box.dart
  16. 18
      lib/core/widgets/button/enum/button_type.dart
  17. 50
      lib/core/widgets/button/my_button.dart
  18. 52
      lib/core/widgets/button/my_white_button.dart
  19. 6
      lib/features/home/presentation/bloc/home_bloc.dart
  20. 2
      lib/features/intro/presentation/bloc/intro_bloc.dart
  21. 22
      lib/features/level/presentation/bloc/level_bloc.dart
  22. 4
      lib/features/level/presentation/ui/level_page.dart
  23. 6
      lib/features/level/presentation/ui/widgets/diamond_level.dart
  24. 152
      lib/features/question/presentation/bloc/question_bloc.dart
  25. 3
      lib/features/question/presentation/bloc/question_event.dart
  26. 124
      lib/features/question/presentation/ui/question_page.dart
  27. 53
      lib/features/question/presentation/ui/screens/answer_screen.dart
  28. 234
      lib/features/question/presentation/ui/screens/diamond_screen.dart
  29. 203
      lib/features/question/presentation/ui/screens/question_screen.dart
  30. 22
      lib/features/question/presentation/ui/widgets/black_white_effect.dart
  31. 7
      lib/features/question/presentation/ui/widgets/glassy_button.dart
  32. 22
      lib/features/question/presentation/ui/widgets/left_blob.dart
  33. 42
      lib/features/question/presentation/ui/widgets/question_title.dart
  34. 32
      lib/features/question/presentation/ui/widgets/refresh_button.dart
  35. 22
      lib/features/question/presentation/ui/widgets/right_blob.dart
  36. 4
      lib/l10n/app_en.arb
  37. 12
      lib/l10n/app_localizations.dart
  38. 4
      lib/l10n/app_localizations_en.dart

BIN
assets/images/behind_diamond.png

After

Width: 812  |  Height: 759  |  Size: 291 KiB

30
assets/svg/icon_notif.svg

@ -0,0 +1,30 @@
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16.5" cy="16.5" r="16" fill="url(#paint0_linear_156_15297)" stroke="url(#paint1_linear_156_15297)"/>
<g filter="url(#filter0_d_156_15297)">
<path d="M21.708 13.8128V18.401C22.983 18.401 23.958 17.4069 23.958 16.1069C23.958 14.8069 22.983 13.8128 21.708 13.8128ZM15.708 13.0481H10.458C9.63301 13.0481 8.95801 13.7363 8.95801 14.5775V17.6363C8.95801 18.4775 9.63301 19.1657 10.458 19.1657H11.208V21.4598C11.208 22.301 11.883 22.9893 12.708 22.9893H14.208V19.1657H15.708L18.708 22.2246H20.208V9.98926H18.708L15.708 13.0481Z" fill="#EA4F1B" shape-rendering="crispEdges"/>
<path d="M20.4082 9.78906V22.4248H18.624L18.5654 22.3643L15.625 19.3662H14.4082V23.1895H12.708C11.7689 23.1895 11.0079 22.4079 11.0078 21.46V19.3662H10.458C9.51904 19.3662 8.75803 18.5845 8.75781 17.6367V14.5771C8.758 13.6293 9.51902 12.8477 10.458 12.8477H15.625L18.5654 9.84961L18.624 9.78906H20.4082ZM21.708 13.6123C23.097 13.6123 24.158 14.6999 24.1582 16.1064C24.1582 17.5132 23.0971 18.6006 21.708 18.6006H21.5078V13.6123H21.708Z" stroke="url(#paint2_linear_156_15297)" stroke-width="0.4" shape-rendering="crispEdges"/>
</g>
<defs>
<filter id="filter0_d_156_15297" x="8.55762" y="9.58923" width="16.3008" height="14.3" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="0.5" dy="0.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.85098 0 0 0 0 0.266667 0 0 0 1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_156_15297"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_156_15297" result="shape"/>
</filter>
<linearGradient id="paint0_linear_156_15297" x1="16.5" y1="0" x2="16.5" y2="33" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDD19"/>
<stop offset="1" stop-color="#FFB519"/>
</linearGradient>
<linearGradient id="paint1_linear_156_15297" x1="10.5" y1="2" x2="25.5" y2="30" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.5"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_156_15297" x1="13.4998" y1="11.5" x2="17.9998" y2="24.5" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.7"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

8
lib/common_ui/resources/my_animations.dart

@ -0,0 +1,8 @@
class MyAnimations {
static const MyAnimations _i = MyAnimations._internal();
const MyAnimations._internal();
factory MyAnimations() => _i;
static const String confetti = 'assets/animations/confetti.json';
static const String lightPurple = 'assets/animations/lights_purple.json';
}

2
lib/common_ui/resources/my_assets.dart

@ -38,6 +38,7 @@ class MyAssets {
static const String planet10 = 'assets/images/planet_10.png';
static const String satellite = 'assets/images/satellite.png';
static const String planetFinal = 'assets/images/planet_final.png';
static const String behindDiamond = 'assets/images/behind_diamond.png';
/// SVG
static const String closeBtn = 'assets/svg/close_btn.svg';
@ -72,6 +73,7 @@ class MyAssets {
static const String homeButton = 'assets/svg/home_button.svg';
static const String diamondContainer = 'assets/svg/diamond_container.svg';
static const String iconPlay = 'assets/svg/icon_play.svg';
static const String iconNotif = 'assets/svg/icon_notif.svg';
static final List<String> images = [

9
lib/common_ui/resources/my_audios.dart

@ -3,7 +3,12 @@ class MyAudios {
const MyAudios._internal();
factory MyAudios() => _i;
static const String homeMusic = 'assets/audios/home.mp3';
static const String clickButton = 'assets/audios/click_button.mp3';
static const String back = 'assets/audios/back.mp3';
static const String clickButton = 'assets/audios/click_button.mp3';
static const String diamondEnd = 'assets/audios/diamond_end.mp3';
static const String diamondIncrease = 'assets/audios/diamond_increase.mp3';
static const String home = 'assets/audios/home.mp3';
static const String incorrectAnswer = 'assets/audios/incorrect_answer.mp3';
static const String question = 'assets/audios/question.mp3';
static const String rightAnswer = 'assets/audios/right_answer.mp3';
}

7
lib/core/routers/hero_dialog_route.dart

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
class HeroDialogRoute<T> extends PageRoute<T> {
HeroDialogRoute({
@ -17,16 +16,16 @@ class HeroDialogRoute<T> extends PageRoute<T> {
bool get fullscreenDialog => false;
@override
bool get barrierDismissible => false;
bool get barrierDismissible => true;
@override
Duration get transitionDuration => const Duration(milliseconds: 300); // Adjust as needed
Duration get transitionDuration => const Duration(seconds: 1); // Adjust as needed
@override
bool get maintainState => true;
@override
Color get barrierColor => MyColors.transparent; // Or your desired barrier color
Color get barrierColor => Color(0XFF322386).withValues(alpha: 0.3); // Or your desired barrier color
@override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {

8
lib/core/routers/my_routes.dart

@ -120,8 +120,12 @@ GoRouter get appPages => GoRouter(
path: '${Routes.questionPage}/:id',
builder: (context, state) => BlocProvider(
create: (context) =>
QuestionBloc(locator(), locator(), locator())
..add(GetLevelEvent(state.pathParameters['id'], context)),
QuestionBloc(
locator(),
locator(),
locator(instanceName: MyConstants.mainAudioService),
locator(instanceName: MyConstants.effectAudioService),
)..add(GetLevelEvent(state.pathParameters['id'], context)),
child: const QuestionPage(),
),
),

50
lib/core/widgets/animations/fade_anim.dart

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
class FadeAnim extends StatefulWidget {
const FadeAnim({super.key, required this.child});
final Widget child;
@override
State<FadeAnim> createState() => _FadeAnimState();
}
class _FadeAnimState extends State<FadeAnim>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(seconds: 500),
);
_animation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: widget.child,
builder: (context, child) => FadeTransition(
opacity: _animation,
child: child,
),
);
}
}

51
lib/core/widgets/animations/scale_anim.dart

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
class ScaleAnim extends StatefulWidget {
const ScaleAnim({super.key, required this.child, this.state = false});
final Widget child;
final bool state;
@override
State<ScaleAnim> createState() => _ScaleAnimState();
}
class _ScaleAnimState extends State<ScaleAnim>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 200),
reverseDuration: Duration(milliseconds: 200),
);
_animation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
widget.state ? _controller.forward() : _controller.reverse();
return AnimatedBuilder(
animation: _controller,
child: widget.child,
builder: (context, child) => ScaleTransition(
scale: _animation,
alignment: Alignment.center,
child: child,
),
);
}
}

56
lib/core/widgets/animations/ship_anim.dart

@ -0,0 +1,56 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
class ShipAnim extends StatefulWidget {
const ShipAnim({super.key, required this.child});
final Widget child;
@override
State<ShipAnim> createState() => _ShipAnimState();
}
class _ShipAnimState extends State<ShipAnim>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 15),
reverseDuration: Duration(seconds: 15),
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
// The angle of the ship in its circular path
final angle = _controller.value * 2 * math.pi;
// The radius of the circular path
final radius = context.widthScreen * 0.05;
// Calculate the x and y coordinates for the ship
final x = radius * math.cos(angle) + 60;
final y = radius * math.sin(angle) - 70; // -80 to lift it up
return Transform.translate(
offset: Offset(x, y),
child: child,
);
},
child: widget.child,
);
}
}

61
lib/core/widgets/animations/slide_anim.dart

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
class SlideAnim extends StatefulWidget {
const SlideAnim({
super.key,
required this.child,
required this.index,
});
final Widget child;
final int index;
@override
State<SlideAnim> createState() => _SlideAnimState();
}
class _SlideAnimState extends State<SlideAnim>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation;
final List<Offset> offsetList = [
Offset(-2, -2),
Offset(2, -2),
Offset(-2, 2),
Offset(2, 2),
];
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 500),
);
_animation = Tween<Offset>(
begin: offsetList[widget.index],
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: widget.child,
builder: (context, child) => SlideTransition(
position: _animation,
child: child,
),
);
}
}

77
lib/core/widgets/answer_box/answer_box.dart

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBox extends StatefulWidget {
@ -11,49 +13,72 @@ class AnswerBox extends StatefulWidget {
required this.correctAnswer,
required this.index,
this.onTap,
this.onNotifTap,
});
final AnswerEntity answer;
final int correctAnswer;
final void Function(bool isCorrect, int correctAnswer)? onTap;
final int index;
final Function(AnswerEntity answer)? onNotifTap;
@override
State<AnswerBox> createState() => _AnswerBoxState();
}
class _AnswerBoxState extends State<AnswerBox> {
bool selected = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: !selected ? () {
setState(() {
selected = true;
});
widget.onTap?.call(widget.index == widget.correctAnswer, widget.correctAnswer);
} : null,
child: Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s26,
child: AnswerTextBox(
text: widget.answer.title ?? '',
),
return Hero(
tag: 'Hero_answer_${widget.answer.id}',
child: Material(
type: MaterialType.transparency,
child: GestureDetector(
onTap: !selected
? () {
setState(() {
selected = true;
});
widget.onTap?.call(
widget.index == widget.correctAnswer,
widget.correctAnswer,
);
}
: null,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: selected,
index: widget.index,
image: widget.answer.image ?? '',
correctAnswer: widget.correctAnswer,
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
),
Positioned(
left: 0,
right: 0,
bottom: -60,
child: AnswerTextBox(text: widget.answer.title ?? ''),
),
PositionedDirectional(
top: MySpaces.s12,
end: MySpaces.s8,
child: GestureDetector(
onTap: () {
widget.onNotifTap?.call(widget.answer);
},
child: MyImage(image: MyAssets.iconNotif),
),
),
],
),
],
),
),
);
}

56
lib/core/widgets/answer_box/answer_box_show.dart

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/picture_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/styles/text_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
class AnswerBoxShow extends StatelessWidget {
@ -9,34 +11,48 @@ class AnswerBoxShow extends StatelessWidget {
super.key,
required this.answer,
required this.index,
this.correct,
this.onNotifTap,
});
final AnswerEntity answer;
final int index;
final bool? correct;
final Function(AnswerEntity answer)? onNotifTap;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: correct ?? false,
index: index,
image: answer.image ?? '',
correctAnswer: index,
return Hero(
tag: 'Hero_answer_${answer.id}',
child: Material(
type: MaterialType.transparency,
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
AnswerPictureBox(
selected: false,
index: index,
image: answer.image ?? '',
correctAnswer: 0,
),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s40,
child: AnswerTextBox(text: answer.title ?? ''),
),
PositionedDirectional(
top: MySpaces.s30,
end: MySpaces.s20,
child: GestureDetector(
onTap: () {
onNotifTap?.call(answer);
},
child: MyImage(image: MyAssets.iconNotif, size: MySpaces.s40),
),
),
],
),
Positioned(
left: 0,
right: 0,
bottom: -MySpaces.s26,
child: AnswerTextBox(
text: answer.title ?? '',
),
),
],
),
);
}
}

267
lib/core/widgets/answer_box/styles/picture_box.dart

@ -2,9 +2,10 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/black_white_effect.dart';
class AnswerPictureBox extends StatelessWidget {
const AnswerPictureBox({
@ -13,77 +14,74 @@ class AnswerPictureBox extends StatelessWidget {
required this.image,
required this.index,
required this.correctAnswer,
this.onTap,
});
final bool selected;
final String image;
final int index;
final int correctAnswer;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(170, 170),
foregroundPainter: _SvgCustomPainter(false),
painter: _CustomShapePainter(),
child: ClipPath(
clipper: _SvgCustomClipper(),
clipper: _CustomShapeClipper(),
child: Stack(
alignment: Alignment.center,
children: [
Builder(
builder: (context) {
if (selected &&
(index != correctAnswer)) {
return BlackWhiteEffect(
child: Image.file(
File(image),
fit: BoxFit.cover,
height: 170,
width: 170,
),
);
} else {
return Image.file(
File(image),
fit: BoxFit.cover,
height: 170,
width: 170,
);
}
},
AnimatedSwitcher(
duration: Duration(milliseconds: 150),
reverseDuration: Duration(milliseconds: 150),
switchInCurve: Curves.linear,
switchOutCurve: Curves.linear,
child: selected && (index != correctAnswer) ?
Image.file(
key: Key('1'),
File(image),
fit: BoxFit.cover,
color: MyColors.black,
colorBlendMode: BlendMode.color,
) :
Image.file(
key: Key('2'),
File(image),
fit: BoxFit.cover,
),
transitionBuilder: (child, animation) =>
FadeTransition(
opacity: animation,
child: child,
),
),
PositionedDirectional(
top: MySpaces.s12,
start: MySpaces.s12,
child: ClipPath(
clipper: _CountClipper(),
child: Container(
height: MySpaces.s32,
width: MySpaces.s32,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0XFF5732CB),
Color(0XFF322386),
],
),
top: 0,
child: Container(
width: MySpaces.s34,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Color(0XFFF2F7FF),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(10),
),
child: Text(
'$index',
),
child: Text(
'$index',
style: MYTextStyle.titr1.copyWith(
color: Color(0XFF9B85D8),
),
),
),
),
if(selected)
PositionedDirectional(
top: MySpaces.s14,
end: MySpaces.s12,
top: MySpaces.s8,
start: MySpaces.s8,
child: MyImage(
image: index == correctAnswer ? MyAssets.correct : MyAssets
.wrong,
size: MySpaces.s40,
),
),
],
@ -93,141 +91,72 @@ class AnswerPictureBox extends StatelessWidget {
}
}
class _SvgCustomClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double scaleFactor = 170.0 / 480.0;
Path path = Path()
..moveTo(242.081 * scaleFactor, 4.12988 * scaleFactor)
..cubicTo(189.733 * scaleFactor, 1.72935 * scaleFactor, 137.532 * scaleFactor, 3.16507 * scaleFactor, 96.9766 * scaleFactor, 8.33887 * scaleFactor)
..cubicTo(76.6855 * scaleFactor, 10.9275 * scaleFactor, 59.4247 * scaleFactor, 14.4382 * scaleFactor, 46.5547 * scaleFactor, 18.8203 * scaleFactor)
..cubicTo(40.1188 * scaleFactor, 21.0117 * scaleFactor, 34.878 * scaleFactor, 23.3892 * scaleFactor, 30.9326 * scaleFactor, 25.916 * scaleFactor)
..cubicTo(26.9611 * scaleFactor, 28.4595 * scaleFactor, 24.5168 * scaleFactor, 31.0171 * scaleFactor, 23.3438 * scaleFactor, 33.4795 * scaleFactor)
..cubicTo(20.5038 * scaleFactor, 39.4409 * scaleFactor, 17.8387 * scaleFactor, 49.1133 * scaleFactor, 15.4463 * scaleFactor, 61.8105 * scaleFactor)
..cubicTo(13.066 * scaleFactor, 74.4434 * scaleFactor, 10.9937 * scaleFactor, 89.8529 * scaleFactor, 9.25879 * scaleFactor, 107.15 * scaleFactor)
..cubicTo(5.78933 * scaleFactor, 141.742 * scaleFactor, 3.68374 * scaleFactor, 183.74 * scaleFactor, 3.14062 * scaleFactor, 225.896 * scaleFactor)
..cubicTo(2.5975 * scaleFactor, 268.052 * scaleFactor, 3.61795 * scaleFactor, 310.308 * scaleFactor, 6.3877 * scaleFactor, 345.416 * scaleFactor)
..cubicTo(7.77271 * scaleFactor, 362.972 * scaleFactor, 9.59224 * scaleFactor, 378.701 * scaleFactor, 11.8633 * scaleFactor, 391.718 * scaleFactor)
..cubicTo(14.1445 * scaleFactor, 404.793 * scaleFactor, 16.8465 * scaleFactor, 414.918 * scaleFactor, 19.9121 * scaleFactor, 421.396 * scaleFactor)
..cubicTo(21.2125 * scaleFactor, 424.143 * scaleFactor, 23.8655 * scaleFactor, 426.967 * scaleFactor, 28.083 * scaleFactor, 429.773 * scaleFactor)
..cubicTo(32.2774 * scaleFactor, 432.565 * scaleFactor, 37.8229 * scaleFactor, 435.209 * scaleFactor, 44.6045 * scaleFactor, 437.676 * scaleFactor)
..cubicTo(58.1675 * scaleFactor, 442.609 * scaleFactor, 76.2991 * scaleFactor, 446.701 * scaleFactor, 97.542 * scaleFactor, 449.934 * scaleFactor)
..cubicTo(140.002 * scaleFactor, 456.395 * scaleFactor, 194.428 * scaleFactor, 459.359 * scaleFactor, 248.533 * scaleFactor, 458.966 * scaleFactor)
..cubicTo(302.639 * scaleFactor, 458.572 * scaleFactor, 356.282 * scaleFactor, 454.822 * scaleFactor, 397.177 * scaleFactor, 447.904 * scaleFactor)
..cubicTo(417.642 * scaleFactor, 444.443 * scaleFactor, 434.779 * scaleFactor, 440.209 * scaleFactor, 447.149 * scaleFactor, 435.27 * scaleFactor)
..cubicTo(453.338 * scaleFactor, 432.798 * scaleFactor, 458.209 * scaleFactor, 430.199 * scaleFactor, 461.686 * scaleFactor, 427.518 * scaleFactor)
..cubicTo(465.181 * scaleFactor, 424.821 * scaleFactor, 467.024 * scaleFactor, 422.232 * scaleFactor, 467.636 * scaleFactor, 419.835 * scaleFactor)
..cubicTo(471.161 * scaleFactor, 405.976 * scaleFactor, 473.704 * scaleFactor, 379.536 * scaleFactor, 475.24 * scaleFactor, 346.71 * scaleFactor)
..cubicTo(476.772 * scaleFactor, 313.978 * scaleFactor, 477.293 * scaleFactor, 275.164 * scaleFactor, 476.847 * scaleFactor, 236.729 * scaleFactor)
..cubicTo(476.401 * scaleFactor, 198.293 * scaleFactor, 474.989 * scaleFactor, 160.274 * scaleFactor, 472.661 * scaleFactor, 129.129 * scaleFactor)
..cubicTo(471.497 * scaleFactor, 113.555 * scaleFactor, 470.106 * scaleFactor, 99.729 * scaleFactor, 468.498 * scaleFactor, 88.4443 * scaleFactor)
..cubicTo(466.881 * scaleFactor, 77.0977 * scaleFactor, 465.07 * scaleFactor, 68.5213 * scaleFactor, 463.128 * scaleFactor, 63.3262 * scaleFactor)
..cubicTo(460.15 * scaleFactor, 55.3421 * scaleFactor, 451.863 * scaleFactor, 47.7494 * scaleFactor, 438.653 * scaleFactor, 40.8027 * scaleFactor)
..cubicTo(425.546 * scaleFactor, 33.9095 * scaleFactor, 408.099 * scaleFactor, 27.9176 * scaleFactor, 387.654 * scaleFactor, 22.8643 * scaleFactor)
..cubicTo(346.784 * scaleFactor, 12.7625 * scaleFactor, 294.436 * scaleFactor, 6.53075 * scaleFactor, 242.081 * scaleFactor, 4.12988 * scaleFactor)
..close();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
class _SvgCustomPainter extends CustomPainter {
_SvgCustomPainter(this.selected);
final bool selected;
class _CustomShapePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
double scaleFactor = 170.0 / 480.0;
Path path = Path()
..moveTo(242.081 * scaleFactor, 4.12988 * scaleFactor)
..cubicTo(189.733 * scaleFactor, 1.72935 * scaleFactor, 137.532 * scaleFactor, 3.16507 * scaleFactor, 96.9766 * scaleFactor, 8.33887 * scaleFactor)
..cubicTo(76.6855 * scaleFactor, 10.9275 * scaleFactor, 59.4247 * scaleFactor, 14.4382 * scaleFactor, 46.5547 * scaleFactor, 18.8203 * scaleFactor)
..cubicTo(40.1188 * scaleFactor, 21.0117 * scaleFactor, 34.878 * scaleFactor, 23.3892 * scaleFactor, 30.9326 * scaleFactor, 25.916 * scaleFactor)
..cubicTo(26.9611 * scaleFactor, 28.4595 * scaleFactor, 24.5168 * scaleFactor, 31.0171 * scaleFactor, 23.3438 * scaleFactor, 33.4795 * scaleFactor)
..cubicTo(20.5038 * scaleFactor, 39.4409 * scaleFactor, 17.8387 * scaleFactor, 49.1133 * scaleFactor, 15.4463 * scaleFactor, 61.8105 * scaleFactor)
..cubicTo(13.066 * scaleFactor, 74.4434 * scaleFactor, 10.9937 * scaleFactor, 89.8529 * scaleFactor, 9.25879 * scaleFactor, 107.15 * scaleFactor)
..cubicTo(5.78933 * scaleFactor, 141.742 * scaleFactor, 3.68374 * scaleFactor, 183.74 * scaleFactor, 3.14062 * scaleFactor, 225.896 * scaleFactor)
..cubicTo(2.5975 * scaleFactor, 268.052 * scaleFactor, 3.61795 * scaleFactor, 310.308 * scaleFactor, 6.3877 * scaleFactor, 345.416 * scaleFactor)
..cubicTo(7.77271 * scaleFactor, 362.972 * scaleFactor, 9.59224 * scaleFactor, 378.701 * scaleFactor, 11.8633 * scaleFactor, 391.718 * scaleFactor)
..cubicTo(14.1445 * scaleFactor, 404.793 * scaleFactor, 16.8465 * scaleFactor, 414.918 * scaleFactor, 19.9121 * scaleFactor, 421.396 * scaleFactor)
..cubicTo(21.2125 * scaleFactor, 424.143 * scaleFactor, 23.8655 * scaleFactor, 426.967 * scaleFactor, 28.083 * scaleFactor, 429.773 * scaleFactor)
..cubicTo(32.2774 * scaleFactor, 432.565 * scaleFactor, 37.8229 * scaleFactor, 435.209 * scaleFactor, 44.6045 * scaleFactor, 437.676 * scaleFactor)
..cubicTo(58.1675 * scaleFactor, 442.609 * scaleFactor, 76.2991 * scaleFactor, 446.701 * scaleFactor, 97.542 * scaleFactor, 449.934 * scaleFactor)
..cubicTo(140.002 * scaleFactor, 456.395 * scaleFactor, 194.428 * scaleFactor, 459.359 * scaleFactor, 248.533 * scaleFactor, 458.966 * scaleFactor)
..cubicTo(302.639 * scaleFactor, 458.572 * scaleFactor, 356.282 * scaleFactor, 454.822 * scaleFactor, 397.177 * scaleFactor, 447.904 * scaleFactor)
..cubicTo(417.642 * scaleFactor, 444.443 * scaleFactor, 434.779 * scaleFactor, 440.209 * scaleFactor, 447.149 * scaleFactor, 435.27 * scaleFactor)
..cubicTo(453.338 * scaleFactor, 432.798 * scaleFactor, 458.209 * scaleFactor, 430.199 * scaleFactor, 461.686 * scaleFactor, 427.518 * scaleFactor)
..cubicTo(465.181 * scaleFactor, 424.821 * scaleFactor, 467.024 * scaleFactor, 422.232 * scaleFactor, 467.636 * scaleFactor, 419.835 * scaleFactor)
..cubicTo(471.161 * scaleFactor, 405.976 * scaleFactor, 473.704 * scaleFactor, 379.536 * scaleFactor, 475.24 * scaleFactor, 346.71 * scaleFactor)
..cubicTo(476.772 * scaleFactor, 313.978 * scaleFactor, 477.293 * scaleFactor, 275.164 * scaleFactor, 476.847 * scaleFactor, 236.729 * scaleFactor)
..cubicTo(476.401 * scaleFactor, 198.293 * scaleFactor, 474.989 * scaleFactor, 160.274 * scaleFactor, 472.661 * scaleFactor, 129.129 * scaleFactor)
..cubicTo(471.497 * scaleFactor, 113.555 * scaleFactor, 470.106 * scaleFactor, 99.729 * scaleFactor, 468.498 * scaleFactor, 88.4443 * scaleFactor)
..cubicTo(466.881 * scaleFactor, 77.0977 * scaleFactor, 465.07 * scaleFactor, 68.5213 * scaleFactor, 463.128 * scaleFactor, 63.3262 * scaleFactor)
..cubicTo(460.15 * scaleFactor, 55.3421 * scaleFactor, 451.863 * scaleFactor, 47.7494 * scaleFactor, 438.653 * scaleFactor, 40.8027 * scaleFactor)
..cubicTo(425.546 * scaleFactor, 33.9095 * scaleFactor, 408.099 * scaleFactor, 27.9176 * scaleFactor, 387.654 * scaleFactor, 22.8643 * scaleFactor)
..cubicTo(346.784 * scaleFactor, 12.7625 * scaleFactor, 294.436 * scaleFactor, 6.53075 * scaleFactor, 242.081 * scaleFactor, 4.12988 * scaleFactor)
..close();
Paint shadowPaint = Paint()
..color = selected ? Colors.green.withValues(alpha: 0.5) : Colors
.transparent
..style = PaintingStyle.fill
..maskFilter = MaskFilter.blur(BlurStyle.outer, 10);
canvas.drawPath(path, shadowPaint);
canvas.drawPath(path, shadowPaint);
canvas.clipPath(path);
Paint strokePaint = Paint()
..color = selected ? Colors.green : Colors.white
final Paint strokePaint = Paint()
..color = Color(0XFFF2F7FF)
..style = PaintingStyle.stroke
..strokeWidth = 5;
..strokeWidth = 4;
final Path path = _CustomShapeClipper().getClip(size);
canvas.drawPath(path, strokePaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return this != oldDelegate;
}
}
class _CountClipper extends CustomClipper<Path> {
class _CustomShapeClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
// Original SVG viewBox: width=34, height=33
final sx = size.width / 34.0;
final sy = size.height / 33.0;
// Original SVG dimensions to calculate the scaling factors.
final double originalWidth = 193.0;
final double originalHeight = 189.0;
// Scaling factors to make the path responsive.
final double scaleX = size.width / originalWidth;
final double scaleY = size.height / originalHeight;
// The path is defined using the scaled coordinates from the SVG.
final Path path = Path()
..moveTo(148.483 * scaleX, 4.10254 * scaleY)
..cubicTo(131.624 * scaleX, 1.93333 * scaleY, 111.221 * scaleX, 1.00169 * scaleY, 91.2451 * scaleX, 1.2666 * scaleY)
..cubicTo(71.2667 * scaleX, 1.53156 * scaleY, 51.7626 * scaleX, 2.99274 * scaleY, 36.6973 * scaleX, 5.59668 * scaleY)
..cubicTo(29.1597 * scaleX, 6.8995 * scaleY, 22.7796 * scaleX, 8.48114 * scaleY, 18.0205 * scaleX, 10.3203 * scaleY)
..cubicTo(15.641 * scaleX, 11.2399 * scaleY, 13.7026 * scaleX, 12.2101 * scaleY, 12.2383 * scaleX, 13.2188 * scaleY)
..cubicTo(10.7653 * scaleX, 14.2333 * scaleY, 9.84633 * scaleX, 15.2359 * scaleY, 9.3916 * scaleX, 16.1904 * scaleY)
..cubicTo(8.252 * scaleX, 18.5828 * scaleY, 7.18153 * scaleX, 22.466 * scaleY, 6.2207 * scaleX, 27.5654 * scaleY)
..cubicTo(5.26481 * scaleX, 32.6387 * scaleY, 4.43215 * scaleX, 38.8273 * scaleY, 3.73535 * scaleX, 45.7744 * scaleY)
..cubicTo(2.34189 * scaleX, 59.6675 * scaleY, 1.49647 * scaleX, 76.5363 * scaleY, 1.27832 * scaleX, 93.4678 * scaleY)
..cubicTo(1.06017 * scaleX, 110.4 * scaleY, 1.47057 * scaleX, 127.372 * scaleY, 2.58301 * scaleX, 141.473 * scaleY)
..cubicTo(3.13928 * scaleX, 148.524 * scaleY, 3.86921 * scaleX, 154.841 * scaleY, 4.78125 * scaleX, 160.068 * scaleY)
..cubicTo(5.69748 * scaleX, 165.32 * scaleY, 6.78334 * scaleX, 169.385 * scaleY, 8.01367 * scaleX, 171.984 * scaleY)
..cubicTo(8.53417 * scaleX, 173.084 * scaleY, 9.59654 * scaleX, 174.216 * scaleY, 11.2891 * scaleX, 175.343 * scaleY)
..cubicTo(12.9722 * scaleX, 176.463 * scaleY, 15.1988 * scaleX, 177.524 * scaleY, 17.9219 * scaleX, 178.515 * scaleY)
..cubicTo(23.3679 * scaleX, 180.496 * scaleY, 30.6491 * scaleX, 182.138 * scaleY, 39.1807 * scaleX, 183.437 * scaleY)
..cubicTo(56.2336 * scaleX, 186.032 * scaleY, 78.0934 * scaleX, 187.222 * scaleY, 99.8242 * scaleX, 187.064 * scaleY)
..cubicTo(121.556 * scaleX, 186.906 * scaleY, 143.101 * scaleX, 185.4 * scaleY, 159.525 * scaleX, 182.622 * scaleY)
..cubicTo(167.745 * scaleX, 181.232 * scaleY, 174.627 * scaleX, 179.531 * scaleY, 179.594 * scaleX, 177.548 * scaleY)
..cubicTo(182.079 * scaleX, 176.556 * scaleY, 184.034 * scaleX, 175.512 * scaleY, 185.429 * scaleX, 174.437 * scaleY)
..cubicTo(186.83 * scaleX, 173.355 * scaleY, 187.568 * scaleX, 172.319 * scaleY, 187.812 * scaleX, 171.361 * scaleY)
..lineTo(187.812 * scaleX, 171.361 * scaleY) // In SVG, this was H (horizontal line), equivalent to lineTo in Flutter
..cubicTo(189.156 * scaleX, 166.074 * scaleY, 190.148 * scaleX, 155.525 * scaleY, 190.773 * scaleX, 142.157 * scaleY)
..cubicTo(191.396 * scaleX, 128.832 * scaleY, 191.651 * scaleX, 112.822 * scaleY, 191.552 * scaleX, 96.6875 * scaleY)
..cubicTo(191.453 * scaleX, 80.5539 * scaleY, 191.001 * scaleX, 64.3091 * scaleY, 190.213 * scaleX, 50.5156 * scaleY)
..cubicTo(189.423 * scaleX, 36.6928 * scaleY, 188.299 * scaleX, 25.4153 * scaleY, 186.876 * scaleX, 19.167 * scaleY)
..cubicTo(186.404 * scaleX, 17.0929 * scaleY, 185.566 * scaleX, 15.3424 * scaleY, 184.087 * scaleX, 14.1582 * scaleY)
..cubicTo(181.343 * scaleX, 11.9613 * scaleY, 176.72 * scaleX, 9.98089 * scaleY, 170.561 * scaleX, 8.27539 * scaleY)
..cubicTo(164.434 * scaleX, 6.579 * scaleY, 156.914 * scaleX, 5.18731 * scaleY, 148.483 * scaleX, 4.10254 * scaleY)
..close(); // Closes the path to form a complete shape.
final p = Path()
..moveTo(33.3479 * sx, 14.8127 * sy)
..cubicTo(
33.3479 * sx, 23.7042 * sy,
27.2015 * sx, 32.9501 * sy,
17.8599 * sx, 32.9501 * sy,
)..cubicTo(
8.51818 * sx, 32.9501 * sy,
0.945251 * sx, 25.7421 * sy,
0.945251 * sx, 16.8507 * sy,
)..cubicTo(
0.945251 * sx, 7.95917 * sy,
8.51818 * sx, 0.751205 * sy,
17.8599 * sx, 0.751205 * sy,
)..cubicTo(
27.2015 * sx, 0.751205 * sy,
33.3479 * sx, 5.92127 * sy,
33.3479 * sx, 14.8127 * sy,
)..close();
return p;
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return this != oldClipper;
}
}

10
lib/core/widgets/answer_box/styles/text_box.dart

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
class AnswerTextBox extends StatelessWidget {
const AnswerTextBox({super.key, required this.text});
@ -11,7 +12,9 @@ class AnswerTextBox extends StatelessWidget {
return ClipPath(
clipper: WavyBannerClipper(),
child: Container(
padding: EdgeInsets.all(MySpaces.s10),
height: 90,
padding: EdgeInsets.symmetric(horizontal: MySpaces.s10),
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
@ -25,6 +28,11 @@ class AnswerTextBox extends StatelessWidget {
child: Text(
text,
textAlign: TextAlign.center,
style: MYTextStyle.matn2.copyWith(
color: Color(0XFF322386),
height: 1.2,
),
maxLines: 5,
),
),
);

18
lib/core/widgets/button/enum/button_type.dart

@ -1,18 +0,0 @@
import 'dart:ui';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
enum ButtonType {
type1,
type2;
static Map<ButtonType, String> get image => {
type1: MyAssets.button,
type2: MyAssets.button2,
};
static Map<ButtonType, Color> get textColor => {
type1: Color(0XFF1D6EFF),
type2: Color(0XFFD93D16),
};
}

50
lib/core/widgets/button/my_button.dart

@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/enum/button_type.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
class MyButton extends StatelessWidget {
const MyButton({
super.key,
this.onTap,
this.type = ButtonType.type1,
this.title,
});
final VoidCallback? onTap;
final ButtonType? type;
final String? title;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 84,
width: 194,
child: InkWell(
onTap: onTap,
highlightColor: MyColors.transparent,
splashColor: MyColors.transparent,
child: Stack(
alignment: Alignment.center,
children: [
MyImage(
image: ButtonType.image[type] ?? MyAssets.button,
),
PositionedDirectional(
top: MySpaces.s2,
child: Text(
title ?? '',
style: MYTextStyle.button1.copyWith(
color: ButtonType.textColor[type],
),
),
),
],
),
),
);
}
}

52
lib/core/widgets/button/my_white_button.dart

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.dart';
class MyWhiteButton extends StatelessWidget {
const MyWhiteButton({
super.key,
this.onTap,
this.title,
this.top,
});
final VoidCallback? onTap;
final String? title;
final double? top;
@override
Widget build(BuildContext context) {
return MyInkwell(
onTap: onTap,
highlightColor: MyColors.transparent,
splashColor: MyColors.transparent,
child: Stack(
alignment: Alignment.center,
children: [
MyImage(
image: setSize(
context: context,
mobile: MyAssets.button3,
tablet: MyAssets.button2Tablet,
) ?? '',
),
PositionedDirectional(
top: top ?? setSize(context: context, mobile: MySpaces.s6, tablet: MySpaces.s22),
child: Text(
title ?? '',
style: MYTextStyle.button1.copyWith(
color: Color(0XFFD93D16),
fontSize: setSize(context: context, tablet: 60),
),
),
),
],
),
);
}
}

6
lib/features/home/presentation/bloc/home_bloc.dart

@ -45,7 +45,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
orElse: () => TotalDataEntity(),
);
if (findData.levels?.isNotEmpty ?? false) {
context.pushNamed(Routes.introPage);
context.goNamed(Routes.introPage);
} else {
context.goNamed(Routes.downloadPage);
}
@ -67,8 +67,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}
Future<void> playMusic() async {
Future.wait([
_mainAudioService.setAudio(assetPath: MyAudios.homeMusic),
await Future.wait([
_mainAudioService.setAudio(assetPath: MyAudios.home),
_mainAudioService.setLoopMode(isLoop: true),
]);
await _mainAudioService.play();

2
lib/features/intro/presentation/bloc/intro_bloc.dart

@ -38,7 +38,7 @@ class IntroBloc extends Bloc<IntroEvent, IntroState> {
Future<void> goToLevelPage() async {
await LocalStorage.saveData(key: MyConstants.firstIntro, value: 'true');
if (ContextProvider.context.mounted) {
ContextProvider.context.replaceNamed(Routes.levelPage);
ContextProvider.context.goNamed(Routes.levelPage);
}
}

22
lib/features/level/presentation/bloc/level_bloc.dart

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_audios.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/params/level_params.dart';
import 'package:hadi_hoda_flutter/core/routers/my_routes.dart';
@ -25,6 +26,7 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
this._effectAudioService,
) : super(const LevelState()) {
volumeStream = _mainAudioService.volumeStream();
playMusic();
on<GetLevelListEvent>(_getLevelListEvent);
on<SetCurrentLevelEvent>(_setCurrentLevelEvent);
on<StartScrollEvent>(_startScrollEvent);
@ -81,6 +83,14 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
final AudioService _effectAudioService;
/// ------------Functions------------
Future<void> playMusic() async {
await Future.wait([
_mainAudioService.setAudio(assetPath: MyAudios.question),
_mainAudioService.setLoopMode(isLoop: true),
]);
await _mainAudioService.play();
}
void goToQuestionPage(BuildContext context, LevelEntity level){
context.pushReplacementNamed(
Routes.questionPage,
@ -90,8 +100,8 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
);
}
void goToHomePage(BuildContext context){
context.pop();
void goToHomePage(BuildContext context) {
context.goNamed(Routes.homePage);
}
LevelType getLevelType(int index) {
@ -115,6 +125,13 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
]);
}
int get diamonds {
int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1',
);
return currentLevel - 1;
}
/// ------------Api Calls------------
FutureOr<void> _getLevelListEvent(GetLevelListEvent event,
Emitter<LevelState> emit) async {
@ -185,4 +202,5 @@ class LevelBloc extends Bloc<LevelEvent, LevelState> {
emit(state.copyWith(chooseLevel: event.level));
}
}
}

4
lib/features/level/presentation/ui/level_page.dart

@ -196,7 +196,9 @@ class LevelPage extends StatelessWidget {
),
),
Spacer(),
DiamondLevel(),
DiamondLevel(
diamonds: context.read<LevelBloc>().diamonds,
),
StreamBuilder<double>(
initialData: 1,
stream: context.read<LevelBloc>().volumeStream,

6
lib/features/level/presentation/ui/widgets/diamond_level.dart

@ -7,7 +7,9 @@ import 'package:hadi_hoda_flutter/core/utils/gap.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
class DiamondLevel extends StatelessWidget {
const DiamondLevel({super.key});
const DiamondLevel({super.key, this.diamonds});
final int? diamonds;
@override
Widget build(BuildContext context) {
@ -29,7 +31,7 @@ class DiamondLevel extends StatelessWidget {
colors: [Color(0XFF4BA5EA), Color(0XFF0C4EE9)],
).createShader(bounds),
child: Text(
'0',
'$diamonds',
maxLines: 1,
style: MYTextStyle.button1.copyWith(
shadows: [

152
lib/features/question/presentation/bloc/question_bloc.dart

@ -1,9 +1,9 @@
import 'dart:async';
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_audios.dart';
import 'package:hadi_hoda_flutter/core/constants/my_constants.dart';
import 'package:hadi_hoda_flutter/core/params/question_params.dart';
import 'package:hadi_hoda_flutter/core/routers/hero_dialog_route.dart';
@ -28,18 +28,13 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
QuestionBloc(
this._getLevelUseCase,
this._getNextLevelUseCase,
this._audioService,
this._mainAudioService,
this._effectAudioService,
) : super(QuestionState()) {
volumeStream = _audioService.volumeStream();
volumeStream = _mainAudioService.volumeStream();
stopMusic();
on<GetLevelEvent>(_getLevelEvent);
on<ChooseAnswerEvent>(_chooseAnswerEvent);
on<GetNextLevelEvent>(_getNextLevelEvent);
}
@override
Future<void> close() {
confettiController.dispose();
return super.close();
}
/// ------------UseCases------------
@ -57,10 +52,8 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
bool isPlaying = false;
/// ------------Controllers------------
final AudioService _audioService;
final ConfettiController confettiController = ConfettiController(
duration: Duration(seconds: 1),
);
final AudioService _mainAudioService;
final AudioService _effectAudioService;
/// ------------Functions------------
void startShowCase({required BuildContext context}) {
@ -75,46 +68,84 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
);
}
void goToHomePage({required BuildContext context}) {
context.goNamed(Routes.homePage);
}
void goToLevelPage({required BuildContext context}) {
context.pushReplacement(Routes.levelPage);
context.goNamed(Routes.levelPage);
}
Future<void> playVoice() async {
await _audioService.setAudio(filePath: state.currentQuestion?.audio);
await _audioService.play();
Future<void> playDiamondAudio() async {
await _effectAudioService.setAudio(assetPath: MyAudios.diamondEnd);
await _effectAudioService.play();
}
Future<void> stopMusic() async {
await _mainAudioService.stop();
await _mainAudioService.setLoopMode(isLoop: false);
}
Future<void> playCorrectAudio() async {
await _effectAudioService.setAudio(assetPath: MyAudios.rightAnswer);
await _effectAudioService.play();
}
Future<void> playWrongAudio() async {
await _effectAudioService.setAudio(assetPath: MyAudios.incorrectAnswer);
await _effectAudioService.play();
}
Future<void> playAnswerAudio({String? audio}) async {
await _mainAudioService.setAudio(filePath: audio);
await _mainAudioService.play();
}
Future<void> playQuestionAudio() async {
await _mainAudioService.setAudio(filePath: state.currentQuestion?.audio);
await _mainAudioService.play();
}
Future<void> changeMute() async {
await _audioService.changeMute();
await Future.wait([
_mainAudioService.changeMute(),
_effectAudioService.changeMute(),
]);
}
Future<void> showAnswerDialog({
required BuildContext context,
required AnswerEntity answerEntity,
bool? correct,
}) async {
await Navigator.of(context).push(
Navigator.of(context).push(
HeroDialogRoute(
builder: (dialogContext) {
return AnswerScreen(answerEntity: answerEntity, correct: correct);
return AnswerScreen(
answerEntity: answerEntity,
onNotifTap: (answer) => playAnswerAudio(audio: answer.audio),
);
},
),
);
playAnswerAudio(audio: answerEntity.audio);
}
Future<void> playback(BuildContext context) async {
if (isPlaying) return;
for (int i = 0; i < 4; i++) {
await Future.delayed(Duration(seconds: 1));
if (context.mounted) {
await showAnswerDialog(
context: context,
answerEntity: state.currentQuestion?.answers?[i] ?? AnswerEntity(),
);
isPlaying = true;
}
}
isPlaying = false;
Future<void> getNextLevelEvent() async {
await _getNextLevelUseCase(QuestionParams()).then((value) =>
value.fold(
(data) {
ContextProvider.context.pushReplacementNamed(
Routes.questionPage,
pathParameters: {
'id': '${data.id}'
},
);
},
(error) {
goToLevelPage(context: ContextProvider.context);
},
),
);
}
/// ------------Event Calls------------
@ -138,10 +169,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
levelEntity: level,
currentQuestion: data.questions?.first,
));
await playVoice();
if(event.context.mounted){
playback(event.context);
}
await playQuestionAudio();
},
(error) {
emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage)));
@ -156,14 +184,19 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer));
if (event.chooseCorrectAnswer) {
confettiController.play();
await showAnswerDialog(
answerEntity: state.currentQuestion?.answers?.singleWhere((e) =>
e.order == event.correctAnswer) ?? AnswerEntity(),
context: ContextProvider.context,
correct: true,
playCorrectAudio();
await Navigator.of(ContextProvider.context).push(
HeroDialogRoute(
builder: (dialogContext) {
return AnswerScreen(
answerEntity: state.currentQuestion?.answers?.singleWhere((e) =>
e.order == event.correctAnswer) ?? AnswerEntity(),
showConfetti: true,
);
},
),
);
await Future.delayed(Duration(seconds: 2), () async {
await Future.delayed(Duration(seconds: 1), () async {
final QuestionEntity? findPreQuestion = state.currentQuestion;
final int findIndex = (findPreQuestion?.order ?? 1);
emit(
@ -173,6 +206,7 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
);
if (state.currentQuestion?.order ==
state.levelEntity?.questions?.length) {
playDiamondAudio();
int currentLevel = int.parse(
LocalStorage.readData(key: MyConstants.currentLevel) ?? '1');
if (state.levelEntity?.order == currentLevel) {
@ -183,31 +217,11 @@ class QuestionBloc extends Bloc<QuestionEvent, QuestionState> {
);
}
} else {
await playVoice();
if(event.context.mounted){
playback(event.context);
}
playQuestionAudio();
}
});
} else {
playWrongAudio();
}
}
FutureOr<void> _getNextLevelEvent(GetNextLevelEvent event,
Emitter<QuestionState> emit) async {
await _getNextLevelUseCase(QuestionParams()).then((value) =>
value.fold(
(data) {
ContextProvider.context.pushReplacementNamed(
Routes.questionPage,
pathParameters: {
'id': '${data.id}'
},
);
},
(error) {
goToLevelPage(context: ContextProvider.context);
},
),
);
}
}

3
lib/features/question/presentation/bloc/question_event.dart

@ -17,6 +17,3 @@ class ChooseAnswerEvent extends QuestionEvent {
const ChooseAnswerEvent(this.chooseCorrectAnswer, this.correctAnswer, this.context);
}
class GetNextLevelEvent extends QuestionEvent {
const GetNextLevelEvent();
}

124
lib/features/question/presentation/ui/question_page.dart

@ -1,17 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_audios.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/gap.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/confetti/my_confetti.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_down_fade.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/diamond_screen.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/screens/question_screen.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_title.dart';
import 'package:showcaseview/showcaseview.dart';
class QuestionPage extends StatelessWidget {
@ -41,36 +41,31 @@ class QuestionPage extends StatelessWidget {
),
),
),
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: MySpaces.s16),
child: Column(
children: [
MyConfetti(
controller: context.read<QuestionBloc>().confettiController,
),
MySpaces.s4.gapHeight,
_topButtons(context),
MySpaces.s10.gapHeight,
_stepper(),
Expanded(
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
(previous.currentQuestion?.order !=
current.currentQuestion?.order),
builder: (context, state) {
if (state.currentQuestion?.order ==
state.levelEntity?.questions?.length) {
return DiamondScreen();
} else {
return QuestionScreen();
}
},
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: MySpaces.s16,
vertical: MySpaces.s22,
),
child: Column(
children: [
_topButtons(context),
MySpaces.s10.gapHeight,
Expanded(
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
(previous.currentQuestion?.order !=
current.currentQuestion?.order),
builder: (context, state) {
if (state.currentQuestion?.order ==
state.levelEntity?.questions?.length) {
return DiamondScreen();
} else {
return QuestionScreen();
}
},
),
],
),
),
],
),
),
),
@ -79,49 +74,36 @@ class QuestionPage extends StatelessWidget {
);
}
Widget _topButtons(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GlassyButton(
image: MyAssets.home,
onTap: () => context.read<QuestionBloc>().goToLevelPage(context: context),
),
Spacer(),
BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.levelEntity?.id != current.levelEntity?.id,
builder: (context, state) => Text(
'${context.translate.step} ${state.levelEntity?.order ?? 1}',
return SlideDownFade(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GlassyButton(
image: MyAssets.home,
audio: MyAudios.back,
onTap: () =>
context.read<QuestionBloc>().goToHomePage(context: context),
),
BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => QuestionTitle(
step: state.levelEntity?.order,
currentQuestion: state.currentQuestion?.order,
questionLength: state.levelEntity?.questions?.length,
),
),
),
Spacer(),
GlassyButton(
image: MyAssets.leaf,
onTap: () => context.read<QuestionBloc>().showHadith(context: context),
),
MySpaces.s10.gapWidth,
StreamBuilder<double>(
initialData: 1,
stream: context.read<QuestionBloc>().volumeStream,
builder: (context, snapshot) => GlassyButton(
image: snapshot.data == 1 ? MyAssets.music : MyAssets.unMusic,
StreamBuilder<double>(
initialData: 1,
stream: context.read<QuestionBloc>().volumeStream,
builder: (context, snapshot) => GlassyButton(
image: snapshot.data == 0 ? MyAssets.unMusic : MyAssets.music,
onTap: () => context.read<QuestionBloc>().changeMute(),
),
),
),
],
);
}
Widget _stepper() {
return BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => QuestionStepper(
length: state.levelEntity?.questions?.length ?? 0,
currentStep: state.currentQuestion?.order ?? 1,
],
),
);
}

53
lib/features/question/presentation/ui/screens/answer_screen.dart

@ -1,13 +1,24 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_animations.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/context_provider.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box_show.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:lottie/lottie.dart';
class AnswerScreen extends StatefulWidget {
const AnswerScreen({super.key, required this.answerEntity, this.correct});
const AnswerScreen({
super.key,
required this.answerEntity,
this.onNotifTap,
this.showConfetti = false,
});
final AnswerEntity answerEntity;
final bool? correct;
final Function(AnswerEntity answer)? onNotifTap;
final bool showConfetti;
@override
State<AnswerScreen> createState() => _AnswerScreenState();
@ -21,33 +32,37 @@ class _AnswerScreenState extends State<AnswerScreen> {
}
Future<void> back() async {
await Future.delayed(Duration(seconds: 2), () {
if (context.mounted) {
Navigator.pop(ContextProvider.context);
}
});
if (widget.showConfetti) {
await Future.delayed(Duration(seconds: 3), () {
if (ContextProvider.context.mounted) {
ContextProvider.context.pop();
}
});
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Hero(
tag: 'Hero_answer_${widget.answerEntity.id}',
createRectTween: (begin, end) => MaterialRectArcTween(begin: begin, end: end),
flightShuttleBuilder: (flightContext, animation, flightDirection,
fromHeroContext, toHeroContext) => toHeroContext.widget,
child: Transform.scale(
scale: 2,
child: Material(
type: MaterialType.transparency,
return Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: MySpaces.s16),
child: AnswerBoxShow(
answer: widget.answerEntity,
index: widget.answerEntity.order ?? 0,
correct: widget.correct,
onNotifTap: widget.onNotifTap,
),
),
),
),
if (widget.showConfetti) ...{
Lottie.asset(
MyAnimations.confetti,
height: context.heightScreen,
fit: BoxFit.cover,
),
},
],
);
}
}

234
lib/features/question/presentation/ui/screens/diamond_screen.dart

@ -2,17 +2,19 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_animations.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/utils/gap.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/enum/button_type.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/my_button.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/ship_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/my_white_button.dart';
import 'package:hadi_hoda_flutter/core/widgets/button/my_yellow_button.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart';
import 'package:lottie/lottie.dart';
class DiamondScreen extends StatelessWidget {
const DiamondScreen({super.key});
@ -20,144 +22,146 @@ class DiamondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_title(context),
80.0.gapHeight,
Column(
children: [
_diamonds(context),
_mainText(context),
],
),
Spacer(),
Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
_ship(context),
_btns(context),
],
),
],
);
}
Text _title(BuildContext context) {
return Text(
context.translate.you_win,
);
}
SizedBox _diamonds(BuildContext context) {
return SizedBox(
width: context.widthScreen,
height: context.heightScreen / 3,
child: Stack(
alignment: Alignment.center,
children: [
PositionedDirectional(
start: 20,
top: 0,
child: SizedBox(
height: 50,
width: 50,
child: Transform.rotate(
angle: -0.5,
child: Stack(
children: [
MyImage(image: MyAssets.diamondBig),
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
child: SizedBox(width: 50, height: 50),
Stack(
alignment: Alignment.center,
children: [
MyImage(
image: MyAssets.behindDiamond,
size: context.widthScreen * 1.5,
fit: BoxFit.cover,
),
Lottie.asset(
MyAnimations.lightPurple,
),
Transform.rotate(
angle: 0.2,
child: MyImage(
image: MyAssets.diamondBig,
size: 200,
),
),
Padding(
padding: EdgeInsets.only(
top: 250,
),
child: Column(
children: [
Text(
context.translate.you_win,
style: MYTextStyle.titr0,
),
),
],
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
MyColors.white,
Color(0XFF63D4F9),
],
).createShader(bounds),
child: Text(
context.translate.you_got_diamond,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
color: MyColors.black.withValues(alpha: 0.25),
offset: Offset(0, 1.22),
blurRadius: 0.82,
),
],
),
),
),
],
),
),
),
],
),
),
PositionedDirectional(
end: 0,
top: 30,
child: Transform.rotate(
angle: 0.5,
Positioned(
top: 120,
right: MySpaces.s16,
child: Stack(
children: [
MyImage(image: MyAssets.diamondBig, size: 60),
Transform.rotate(
angle: 0.4,
child: MyImage(
image: MyAssets.diamondBig,
size: 80,
),
),
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: SizedBox(width: 100, height: 100),
child: SizedBox(width: 80, height: 80,),
),
),
],
),
),
),
Positioned(top: 100, child: MyImage(image: MyAssets.diamondBig)),
],
),
Positioned(
top: 100,
left: MySpaces.s16,
child: Stack(
children: [
Transform.rotate(
angle: -0.6,
child: MyImage(
image: MyAssets.diamondBig,
size: 60,
),
),
ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: SizedBox(width: 80, height: 80,),
),
),
],
),
),
],
),
Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
_ship(context),
_buttons(context),
],
),
],
);
}
Widget _mainText(BuildContext context){
return ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [MyColors.white, Color(0XFF63D4F9)],
).createShader(bounds),
child: Text(
context.translate.you_got_diamond,
),
);
}
PositionedDirectional _ship(BuildContext context) {
return PositionedDirectional(
end: context.widthScreen / 10,
top: -80,
Widget _ship(BuildContext context) {
return ShipAnim(
child: MyImage(image: MyAssets.ship),
);
}
Widget _btns(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.viewPaddingOf(context).bottom + MySpaces.s16,
),
child: Row(
children: [
Expanded(
child: InkWell(
onTap: () => context.read<QuestionBloc>().goToLevelPage(context: context),
child: Stack(
alignment: Alignment.center,
children: [
MyImage(image: MyAssets.button3, size: 84),
Positioned(
top: 10,
child: Text(
context.translate.view_map,
),
),
],
),
),
Widget _buttons(BuildContext context) {
return Row(
spacing: MySpaces.s12,
children: [
Expanded(
child: MyWhiteButton(
onTap: () => context.read<QuestionBloc>().getNextLevelEvent(),
title: context.translate.map,
),
Expanded(
child: MyButton(
onTap: () => context.read<QuestionBloc>().add(GetNextLevelEvent()),
title: context.translate.go_next,
type: ButtonType.type2,
),
),
Expanded(
child: MyYellowButton(
onTap: () => context.read<QuestionBloc>().getNextLevelEvent(),
title: context.translate.next,
),
],
),
),
],
);
}
}

203
lib/features/question/presentation/ui/screens/question_screen.dart

@ -1,19 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/gap.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/utils/screen_size.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/fade_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_anim.dart';
import 'package:hadi_hoda_flutter/core/widgets/animations/slide_up_fade.dart';
import 'package:hadi_hoda_flutter/core/widgets/answer_box/answer_box.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/showcase/question_showcase.dart';
import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_bloc.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_event.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/bloc/question_state.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/left_blob.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/refresh_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/right_blob.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/glassy_button.dart';
import 'package:hadi_hoda_flutter/features/question/presentation/ui/widgets/question_stepper.dart';
class QuestionScreen extends StatelessWidget {
const QuestionScreen({super.key});
@ -22,122 +25,130 @@ class QuestionScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
_stepper(),
_titles(),
MySpaces.s14.gapHeight,
MySpaces.s20.gapHeight,
_answers(),
_bottomDetail(context),
_bottom(context),
],
);
}
Column _titles() {
return Column(
spacing: MySpaces.s4,
children: [
BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => Text(
'${context.translate.question} ${state.currentQuestion?.order ?? 1} / ${(state.levelEntity?.questions?.length ?? 0) - 1}',
),
Widget _stepper() {
return BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => FadeAnim(
child: QuestionStepper(
length: state.levelEntity?.questions?.length ?? 0,
currentStep: state.currentQuestion?.order ?? 1,
),
BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
),
),
],
),
);
}
Expanded _answers() {
Widget _titles() {
return Expanded(
child: GridView.builder(
itemCount: 4,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: MySpaces.s20,
mainAxisSpacing: 50,
),
itemBuilder: (context, index) => QuestionShowcase(
globalKey: context.read<QuestionBloc>().keys[index],
description: context.translate.tap_to_select,
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => Hero(
key: Key('${state.currentQuestion?.id}'),
tag: 'Hero_answer_${state.currentQuestion?.answers?[index].id}',
child: AnswerBox(
index: state.currentQuestion?.answers?[index].order ?? 1,
answer: state.currentQuestion?.answers?[index] ?? AnswerEntity(),
correctAnswer: state.currentQuestion?.correctAnswer ?? 0,
onTap: (isCorrect, correctAnswer) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect, correctAnswer, context),
flex: 15,
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) =>
FadeAnim(
child: Text(
state.currentQuestion?.title ?? '',
textAlign: TextAlign.center,
maxLines: 3,
style: MYTextStyle.titr1.copyWith(
shadows: [
BoxShadow(
offset: Offset(0, 2),
color: MyColors.black.withValues(alpha: 0.25),
),
],
),
),
),
),
);
}
Expanded _answers() {
return Expanded(
flex: 85,
child: BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.currentQuestion?.id != current.currentQuestion?.id,
builder: (context, state) => GridView.builder(
itemCount: state.currentQuestion?.answers?.length ?? 0,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: MySpaces.s20,
mainAxisSpacing: 80,
),
itemBuilder: (context, index) =>
state.currentQuestion?.answers?[index].imageId == null
? SizedBox.shrink()
: SlideAnim(
key: Key('${state.currentQuestion?.id}'),
index: index,
child: AnswerBox(
index: state.currentQuestion?.answers?[index].order ?? 1,
answer:
state.currentQuestion?.answers?[index] ??
AnswerEntity(),
correctAnswer: state.currentQuestion?.correctAnswer ?? 0,
onNotifTap: (AnswerEntity answer) {
context.read<QuestionBloc>().showAnswerDialog(
context: context,
answerEntity: answer,
);
},
onTap: (isCorrect, correctAnswer) =>
context.read<QuestionBloc>().add(
ChooseAnswerEvent(isCorrect, correctAnswer, context),
),
),
),
),
),
);
}
Widget _bottomDetail(BuildContext context) {
return Row(
children: [
Spacer(),
BlocBuilder<QuestionBloc, QuestionState>(
buildWhen: (previous, current) =>
previous.correctAnswer != current.correctAnswer,
builder: (context, state) => Stack(
clipBehavior: Clip.none,
children: [
PositionedDirectional(
start: -100,
top: -10,
child: AnimatedOpacity(
opacity: state.correctAnswer == false ? 1 : 0,
duration: Duration(milliseconds: 200),
child: LeftBlob(),
Widget _bottom(BuildContext context) {
return SlideUpFade(
child: SizedBox(
width: context.widthScreen,
child: Stack(
alignment: Alignment.center,
children: [
Container(
padding: EdgeInsets.all(MySpaces.s4),
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [
Color(0XFFDFCD00),
Color(0XFFDFCD00).withValues(alpha: 0.35),
Color(0XFFDFCD00).withValues(alpha: 0),
],
center: Alignment.center,
),
),
AnimatedCrossFade(
duration: Duration(milliseconds: 200),
reverseDuration: Duration(milliseconds: 200),
crossFadeState: state.correctAnswer == true ? CrossFadeState
.showSecond : CrossFadeState.showFirst,
firstChild: MyImage(
image: MyAssets.persons,
fit: BoxFit.contain,
),
secondChild: MyImage(
image: MyAssets.happyPersons,
fit: BoxFit.contain,
size: 110,
),
child: MyImage(
image: MyAssets.globe,
),
PositionedDirectional(
top: -30,
end: -90,
child: AnimatedOpacity(
opacity: state.correctAnswer == false ? 1 : 0,
duration: Duration(milliseconds: 200),
child: RightBlob(),
),
),
PositionedDirectional(
end: 0,
child: GlassyButton(
image: MyAssets.leaf,
onTap: () =>
context.read<QuestionBloc>().showHadith(context: context),
),
],
),
),
Spacer(),
RefreshButton(
onTap: () => context.read<QuestionBloc>().playback(context),
),
],
),
],
),
);
}
}

22
lib/features/question/presentation/ui/widgets/black_white_effect.dart

@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
class BlackWhiteEffect extends StatelessWidget {
const BlackWhiteEffect({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: ColorFilter.matrix(
<double>[
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0.2126, 0.7152, 0.0722, 0, 0,
0, 0, 0, 1, 0,
],
),
child: child,
);
}
}

7
lib/features/question/presentation/ui/widgets/glassy_button.dart

@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_spaces.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
import 'package:hadi_hoda_flutter/core/widgets/inkwell/my_inkwell.dart';
class GlassyButton extends StatelessWidget {
const GlassyButton({super.key, required this.image, this.onTap});
const GlassyButton({super.key, required this.image, this.onTap, this.audio});
final String image;
final VoidCallback? onTap;
final String? audio;
@override
Widget build(BuildContext context) {
@ -27,8 +29,9 @@ class GlassyButton extends StatelessWidget {
),
border: Border.all(color: Colors.white.withValues(alpha: 0.3)),
),
child: InkWell(
child: MyInkwell(
onTap: onTap,
audio: audio,
borderRadius: BorderRadius.all(Radius.circular(100)),
child: Padding(
padding: const EdgeInsets.all(MySpaces.s12),

22
lib/features/question/presentation/ui/widgets/left_blob.dart

@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
class LeftBlob extends StatelessWidget {
const LeftBlob({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
MyImage(image: MyAssets.bubbleChatLeft),
Text(
context.translate.wrong_answer,
textAlign: TextAlign.center,
),
],
);
}
}

42
lib/features/question/presentation/ui/widgets/question_title.dart

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_text_style.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
class QuestionTitle extends StatelessWidget {
const QuestionTitle({
super.key,
this.step,
this.currentQuestion,
this.questionLength,
});
final int? step;
final int? currentQuestion;
final int? questionLength;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'${context.translate.step} ${step ?? 0}',
style: MYTextStyle.titr3,
),
Text(
'${context.translate.question} ${currentQuestion ?? 0}/${(questionLength ?? 0) - 1}',
style: MYTextStyle.matn3.copyWith(
color: MyColors.white.withValues(alpha: 0.5),
shadows: [
BoxShadow(
color: MyColors.black.withValues(alpha: 0.25),
blurRadius: 0.82,
offset: Offset(0, 1.22),
),
],
),
),
],
);
}
}

32
lib/features/question/presentation/ui/widgets/refresh_button.dart

@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart';
class RefreshButton extends StatelessWidget {
const RefreshButton({super.key, this.onTap,});
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
return Material(
color: MyColors.transparent,
child: Ink(
height: 48,
width: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0XFFA393FF), Color(0XFFC6BCFB)],
),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.all(Radius.circular(100)),
child: Icon(Icons.refresh, size: 40, color: Color(0XFF263AA1)),
),
),
);
}
}

22
lib/features/question/presentation/ui/widgets/right_blob.dart

@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hadi_hoda_flutter/common_ui/resources/my_assets.dart';
import 'package:hadi_hoda_flutter/core/utils/my_localization.dart';
import 'package:hadi_hoda_flutter/core/widgets/images/my_image.dart';
class RightBlob extends StatelessWidget {
const RightBlob({super.key});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
MyImage(image: MyAssets.bubbleChatRight),
Text(
context.translate.be_cureful,
textAlign: TextAlign.center,
),
],
);
}
}

4
lib/l10n/app_en.arb

@ -15,8 +15,8 @@
"be_cureful": "Be more\ncareful.",
"wrong_answer": "Your answer\nwas not correct.",
"you_got_diamond": "You got the diamond",
"view_map": "View Map",
"go_next": "Go Next",
"map": "Map",
"next": "Next",
"you_win": "You Win!",
"skip": "Skip",
"intro_1_1": "Dinner is ready! Come quickly and wash your beautiful hands!",

12
lib/l10n/app_localizations.dart

@ -190,17 +190,17 @@ abstract class AppLocalizations {
/// **'You got the diamond'**
String get you_got_diamond;
/// No description provided for @view_map.
/// No description provided for @map.
///
/// In en, this message translates to:
/// **'View Map'**
String get view_map;
/// **'Map'**
String get map;
/// No description provided for @go_next.
/// No description provided for @next.
///
/// In en, this message translates to:
/// **'Go Next'**
String get go_next;
/// **'Next'**
String get next;
/// No description provided for @you_win.
///

4
lib/l10n/app_localizations_en.dart

@ -59,10 +59,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get you_got_diamond => 'You got the diamond';
@override
String get view_map => 'View Map';
String get map => 'Map';
@override
String get go_next => 'Go Next';
String get next => 'Next';
@override
String get you_win => 'You Win!';

Loading…
Cancel
Save