From 38d64a177c950790579c1ded0b3285a842e81c2d Mon Sep 17 00:00:00 2001 From: AmirrezaChegini Date: Sun, 28 Dec 2025 15:20:03 +0330 Subject: [PATCH] change somethings --- lib/core/routers/my_routes.dart | 122 ++++--- .../answer_box/styles/picture_box.dart | 4 +- lib/core/widgets/dialog/exit_dialog.dart | 26 +- lib/core/widgets/dialog/reward_dialog.dart | 8 +- .../page_transition/my_page_transition.dart | 17 + lib/core/widgets/pop_scope/my_pop_scope.dart | 10 +- .../home/presentation/ui/home_page.dart | 4 +- .../presentation/bloc/question_bloc.dart | 2 +- .../presentation/ui/question_page.dart | 2 +- .../ui/screens/question_screen.dart | 330 +++++++++++------- 10 files changed, 333 insertions(+), 192 deletions(-) create mode 100644 lib/core/widgets/page_transition/my_page_transition.dart diff --git a/lib/core/routers/my_routes.dart b/lib/core/routers/my_routes.dart index 07b7cd7..0a7b151 100644 --- a/lib/core/routers/my_routes.dart +++ b/lib/core/routers/my_routes.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:hadi_hoda_flutter/core/constants/my_constants.dart'; import 'package:hadi_hoda_flutter/core/middlewares/my_middlewares.dart'; import 'package:hadi_hoda_flutter/core/utils/my_context.dart'; +import 'package:hadi_hoda_flutter/core/widgets/page_transition/my_page_transition.dart'; import 'package:hadi_hoda_flutter/core/widgets/video/my_video_player.dart'; import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_bloc.dart'; import 'package:hadi_hoda_flutter/features/download/presentation/bloc/download_event.dart'; @@ -52,15 +53,6 @@ GoRouter _appPages() => GoRouter( initialLocation: Routes.splashPage, navigatorKey: MyContext.rootNavigatorKey, routes: [ - GoRoute( - name: Routes.introPage, - path: Routes.introPage, - redirect: MyMiddlewares.intro, - builder: (context, state) => BlocProvider( - create: (context) => IntroBloc(), - child: const IntroPage(), - ), - ), GoRoute( name: Routes.samplePage, path: Routes.samplePage, @@ -81,79 +73,105 @@ GoRouter _appPages() => GoRouter( GoRoute( name: Routes.downloadPage, path: Routes.downloadPage, - builder: (context, state) => BlocProvider( - create: (context) => DownloadBloc( - locator(), - locator(), - locator(), - locator(), - )..add(GetImagesEvent()), - child: const DownloadPage(), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => + DownloadBloc(locator(), locator(), locator(), locator()) + ..add(GetImagesEvent()), + child: const DownloadPage(), + ), + ), + ), + GoRoute( + name: Routes.introPage, + path: Routes.introPage, + redirect: MyMiddlewares.intro, + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => IntroBloc(), + child: const IntroPage(), + ), ), ), GoRoute( name: Routes.languagePage, path: Routes.languagePage, - builder: (context, state) => BlocProvider( - create: (context) => LanguageBloc()..add(const InitLanguageEvent()), - child: const LanguagePage(), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => LanguageBloc()..add(const InitLanguageEvent()), + child: const LanguagePage(), + ), ), ), GoRoute( name: Routes.homePage, path: Routes.homePage, - builder: (context, state) => BlocProvider( - create: (context) => - HomeBloc( - locator(instanceName: MyConstants.mainAudioService), - locator(instanceName: MyConstants.effectAudioService), - ), - child: const HomePage(), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => HomeBloc( + locator(instanceName: MyConstants.mainAudioService), + locator(instanceName: MyConstants.effectAudioService), + ), + child: const HomePage(), + ), ), ), GoRoute( name: Routes.levelPage, path: Routes.levelPage, - builder: (context, state) => BlocProvider( - create: (context) => - LevelBloc( - locator(), - locator(instanceName: MyConstants.mainAudioService), - locator(instanceName: MyConstants.effectAudioService), - )..add(SetCurrentLevelEvent()), - child: const LevelPage(), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => LevelBloc( + locator(), + locator(instanceName: MyConstants.mainAudioService), + locator(instanceName: MyConstants.effectAudioService), + )..add(SetCurrentLevelEvent()), + child: const LevelPage(), + ), ), ), GoRoute( name: Routes.guiderPage, path: '${Routes.guiderPage}/:id', - builder: (context, state) => BlocProvider( - create: (context) => GuiderBloc(locator())..add(GetFirstLevelEvent( - id: state.pathParameters['id'], - )), - child: const GuiderPage(), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => + GuiderBloc(locator()) + ..add(GetFirstLevelEvent(id: state.pathParameters['id'])), + child: const GuiderPage(), + ), ), ), GoRoute( name: Routes.questionPage, path: '${Routes.questionPage}/:id', redirect: MyMiddlewares.question, - builder: (context, state) => BlocProvider( - create: (context) => - QuestionBloc( - locator(), - locator(), - locator(instanceName: MyConstants.mainAudioService), - locator(instanceName: MyConstants.effectAudioService), - )..add(GetLevelEvent(state.pathParameters['id'], context)), - child: const QuestionPage(), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: BlocProvider( + create: (context) => QuestionBloc( + locator(), + locator(), + locator(instanceName: MyConstants.mainAudioService), + locator(instanceName: MyConstants.effectAudioService), + )..add(GetLevelEvent(state.pathParameters['id'], context)), + child: const QuestionPage(), + ), ), ), GoRoute( name: Routes.videoPage, path: Routes.videoPage, - builder: (context, state) => - MyVideoPlayer(videoURL: state.extra as String), + pageBuilder: (context, state) => myPageTransition( + key: state.pageKey, + child: MyVideoPlayer(videoURL: state.extra as String), + ), ), ], -); \ No newline at end of file +); diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index 3b8b647..bec195b 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -52,7 +52,7 @@ class AnswerPictureBox extends StatelessWidget { width: context.widthScreen, height: context.heightScreen, image: FileImage(File(image)), - fps: 15, + fps: 20, autostart: autostart, fit: BoxFit.cover, color: MyColors.black, @@ -63,7 +63,7 @@ class AnswerPictureBox extends StatelessWidget { width: context.widthScreen, height: context.heightScreen, image: FileImage(File(image)), - fps: 15, + fps: 20, autostart: autostart, fit: BoxFit.cover, ), diff --git a/lib/core/widgets/dialog/exit_dialog.dart b/lib/core/widgets/dialog/exit_dialog.dart index db15902..b4a9c6a 100644 --- a/lib/core/widgets/dialog/exit_dialog.dart +++ b/lib/core/widgets/dialog/exit_dialog.dart @@ -12,19 +12,24 @@ import 'package:hadi_hoda_flutter/core/utils/set_platform_size.dart'; import 'package:hadi_hoda_flutter/core/widgets/dialog/styles/dialog_background.dart'; import 'package:hadi_hoda_flutter/core/widgets/dialog/styles/dialog_button.dart'; -Future showExitDialog({required BuildContext context, bool? backHome}) async { +Future showExitDialog({ + required BuildContext context, + bool? backHome, + bool? backLevel, +}) async { await showDialog( context: context, - builder: (context) => ExitDialog(backHome: backHome), + builder: (context) => ExitDialog(backHome: backHome, backLevel: backLevel), barrierColor: MyColors.purple.withValues(alpha: 0.82), useSafeArea: false, ); } class ExitDialog extends StatelessWidget { - const ExitDialog({super.key, this.backHome}); + const ExitDialog({super.key, this.backHome, this.backLevel}); final bool? backHome; + final bool? backLevel; @override Widget build(BuildContext context) { @@ -35,7 +40,8 @@ class ExitDialog extends StatelessWidget { child: Center( child: Padding( padding: EdgeInsets.symmetric( - horizontal: setSize(context: context, mobile: 18, tablet: 160) ?? 0, + horizontal: + setSize(context: context, mobile: 18, tablet: 160) ?? 0, ), child: DialogBackground( height: 260, @@ -44,11 +50,15 @@ class ExitDialog extends StatelessWidget { children: [ Text( context.translate.want_to_exit, - style: MYTextStyle.titr0.copyWith(color: const Color(0XFF322386)), + style: MYTextStyle.titr0.copyWith( + color: const Color(0XFF322386), + ), ), Text( context.translate.exit_dialog_desc, - style: MYTextStyle.titr3.copyWith(color: const Color(0XFF6272A9)), + style: MYTextStyle.titr3.copyWith( + color: const Color(0XFF6272A9), + ), textAlign: TextAlign.center, ), Row( @@ -77,8 +87,10 @@ class ExitDialog extends StatelessWidget { Expanded( child: DialogButton( onTap: () { - if(backHome ?? false){ + if (backHome ?? false) { context.goNamed(Routes.homePage); + } else if (backLevel ?? false) { + context.goNamed(Routes.levelPage); } else { SystemNavigator.pop(); } diff --git a/lib/core/widgets/dialog/reward_dialog.dart b/lib/core/widgets/dialog/reward_dialog.dart index 7d25500..e38afd5 100644 --- a/lib/core/widgets/dialog/reward_dialog.dart +++ b/lib/core/widgets/dialog/reward_dialog.dart @@ -89,9 +89,15 @@ class RewardDialog extends StatelessWidget { children: [ Image.network( prize.imageURL ?? '', - loadingBuilder: (context, child, loadingProgress) => SizedBox( + errorBuilder: (context, error, stackTrace) => Container( height: 300, width: 300, + color: const Color(0XFFE0E0E0), + ), + loadingBuilder: (context, child, loadingProgress) => Container( + height: 300, + width: 300, + color: const Color(0XFFE0E0E0), child: child, ), fit: BoxFit.cover, diff --git a/lib/core/widgets/page_transition/my_page_transition.dart b/lib/core/widgets/page_transition/my_page_transition.dart new file mode 100644 index 0000000..00703ae --- /dev/null +++ b/lib/core/widgets/page_transition/my_page_transition.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:go_router/go_router.dart'; + +CustomTransitionPage myPageTransition({ + required Widget child, + required LocalKey key, +}) { + return CustomTransitionPage( + key: key, + child: child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition( + opacity: animation, + child: child, + ), + ); +} diff --git a/lib/core/widgets/pop_scope/my_pop_scope.dart b/lib/core/widgets/pop_scope/my_pop_scope.dart index 8340b4b..00596c8 100644 --- a/lib/core/widgets/pop_scope/my_pop_scope.dart +++ b/lib/core/widgets/pop_scope/my_pop_scope.dart @@ -2,17 +2,23 @@ import 'package:flutter/material.dart'; import 'package:hadi_hoda_flutter/core/widgets/dialog/exit_dialog.dart'; class MyPopScope extends StatelessWidget { - const MyPopScope({super.key, required this.child, this.backHome}); + const MyPopScope({ + super.key, + required this.child, + this.backHome, + this.backLevel, + }); final Widget child; final bool? backHome; + final bool? backLevel; void onPopInvokedWithResult( bool didPop, dynamic result, BuildContext context, ) { - showExitDialog(context: context, backHome: backHome); + showExitDialog(context: context, backHome: backHome, backLevel: backLevel); } @override diff --git a/lib/features/home/presentation/ui/home_page.dart b/lib/features/home/presentation/ui/home_page.dart index 431473f..4d6fe87 100644 --- a/lib/features/home/presentation/ui/home_page.dart +++ b/lib/features/home/presentation/ui/home_page.dart @@ -47,7 +47,7 @@ class HomePage extends StatelessWidget { top: setPlatform(android: MySpaces.s36, iOS: 50), end: MySpaces.s16, child: SlideDownFade( - delay: const Duration(milliseconds: 200), + delay: const Duration(milliseconds: 100), child: StreamBuilder( initialData: 1, stream: context.read().volumeStream, @@ -91,7 +91,7 @@ class HomePage extends StatelessWidget { right: MySpaces.s16, child: SafeArea( child: SlideUpFade( - delay: const Duration(milliseconds: 200), + delay: const Duration(milliseconds: 100), child: Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index ced5212..343b7d5 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -170,7 +170,7 @@ class QuestionBloc extends Bloc { required AnswerEntity answerEntity, String? correctAudio, bool showConfetti = false, - bool autoClose = false, + bool autoClose = true, }) async { await Navigator.of(context).push( HeroDialogRoute( diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index f4ce8e5..20b7379 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -22,7 +22,7 @@ class QuestionPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: MyPopScope( - backHome: true, + backLevel: true, child: Directionality( textDirection: TextDirection.ltr, child: Container( diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart index dd7104b..a08b345 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -39,7 +39,9 @@ class _QuestionScreenState extends State void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - context.read().answerAnimationController = AnimationController( + context + .read() + .answerAnimationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), reverseDuration: const Duration(milliseconds: 500), @@ -78,12 +80,7 @@ class _QuestionScreenState extends State children: [ _stepper(), Expanded( - child: Stack( - children: [ - _questionImage(context), - _answers(context), - ], - ), + child: Stack(children: [_questionImage(context), _answers(context)]), ), _bottom(context), ], @@ -166,110 +163,64 @@ class _QuestionScreenState extends State children: [ Row( key: Key('${state.currentQuestion?.id}answer0'), - mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 20, children: [ - Builder( - key: Key('${state.currentQuestion?.id}0'), - builder: (context) { - if (state.currentQuestion?.answers?[0].imageId == - null) { - return const SizedBox.shrink(); - } else { - return SizedBox( - width: 180, - height: 250, - child: SlideAnim( - controller: context - .read() - .answerAnimationController!, - index: 0, - child: AnswerBox( - index: - state.currentQuestion?.answers?[0].order ?? - 1, - answer: - state.currentQuestion?.answers?[0] ?? - AnswerEntity(), - correctAnswer: - state.currentQuestion?.correctAnswer ?? 0, - onNotifTap: (AnswerEntity answer) { - context.read().showAnswerDialog( - context: context, - answerEntity: answer, - ); - }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent( - isCorrect, - correctAnswer, - context, - ), - ), - ), - ), - ); - } - }, - ), - Builder( - key: Key('${state.currentQuestion?.id}1'), - builder: (context) { - if (state.currentQuestion?.answers?[1].imageId == - null) { - return const SizedBox.shrink(); - } else { - return SizedBox( - width: 180, - height: 250, - child: SlideAnim( - controller: context - .read() - .answerAnimationController!, - index: 1, - child: AnswerBox( - index: - state.currentQuestion?.answers?[1].order ?? - 1, - answer: - state.currentQuestion?.answers?[1] ?? - AnswerEntity(), - correctAnswer: - state.currentQuestion?.correctAnswer ?? 0, - onNotifTap: (AnswerEntity answer) { - context.read().showAnswerDialog( - context: context, - answerEntity: answer, - ); - }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent( - isCorrect, - correctAnswer, - context, + Expanded( + child: Builder( + key: Key('${state.currentQuestion?.id}0'), + builder: (context) { + if (state.currentQuestion?.answers?[0].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 0, + child: AnswerBox( + index: + state + .currentQuestion + ?.answers?[0] + .order ?? + 1, + answer: + state.currentQuestion?.answers?[0] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? 0, + onNotifTap: (AnswerEntity answer) { + context + .read() + .showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), ), - ), + ), ), - ), - ); - } - }, + ); + } + }, + ), ), - ], - ), - Row( - key: Key('${state.currentQuestion?.id}answer1'), - mainAxisAlignment: - (state.currentQuestion?.answers?.length ?? 0) > 3 - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, - children: [ - if ((state.currentQuestion?.answers?.length ?? 0) > 2) - Builder( - key: Key('${state.currentQuestion?.id}2'), + Expanded( + child: Builder( + key: Key('${state.currentQuestion?.id}1'), builder: (context) { - if (state.currentQuestion?.answers?[2].imageId == + if (state.currentQuestion?.answers?[1].imageId == null) { return const SizedBox.shrink(); } else { @@ -280,16 +231,16 @@ class _QuestionScreenState extends State controller: context .read() .answerAnimationController!, - index: 2, + index: 1, child: AnswerBox( index: state .currentQuestion - ?.answers?[2] + ?.answers?[1] .order ?? 1, answer: - state.currentQuestion?.answers?[2] ?? + state.currentQuestion?.answers?[1] ?? AnswerEntity(), correctAnswer: state.currentQuestion?.correctAnswer ?? 0, @@ -315,11 +266,18 @@ class _QuestionScreenState extends State } }, ), - if ((state.currentQuestion?.answers?.length ?? 0) > 3) + ), + ], + ), + if ((state.currentQuestion?.answers?.length ?? 0) == 3) + Row( + key: Key('${state.currentQuestion?.id}answer1'), + mainAxisAlignment: MainAxisAlignment.center, + children: [ Builder( - key: Key('${state.currentQuestion?.id}1'), + key: Key('${state.currentQuestion?.id}2'), builder: (context) { - if (state.currentQuestion?.answers?[3].imageId == + if (state.currentQuestion?.answers?[2].imageId == null) { return const SizedBox.shrink(); } else { @@ -330,16 +288,16 @@ class _QuestionScreenState extends State controller: context .read() .answerAnimationController!, - index: 3, + index: 2, child: AnswerBox( index: state .currentQuestion - ?.answers?[3] + ?.answers?[2] .order ?? 1, answer: - state.currentQuestion?.answers?[3] ?? + state.currentQuestion?.answers?[2] ?? AnswerEntity(), correctAnswer: state.currentQuestion?.correctAnswer ?? 0, @@ -365,8 +323,119 @@ class _QuestionScreenState extends State } }, ), - ], - ), + ], + ), + if ((state.currentQuestion?.answers?.length ?? 0) == 4) + Row( + key: Key('${state.currentQuestion?.id}answer2'), + spacing: 20, + children: [ + Expanded( + child: Builder( + key: Key('${state.currentQuestion?.id}2'), + builder: (context) { + if (state.currentQuestion?.answers?[2].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 2, + child: AnswerBox( + index: + state + .currentQuestion + ?.answers?[2] + .order ?? + 1, + answer: + state.currentQuestion?.answers?[2] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? + 0, + onNotifTap: (AnswerEntity answer) { + context + .read() + .showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), + ), + ), + ), + ); + } + }, + ), + ), + Expanded( + child: Builder( + key: Key('${state.currentQuestion?.id}3'), + builder: (context) { + if (state.currentQuestion?.answers?[3].imageId == + null) { + return const SizedBox.shrink(); + } else { + return SizedBox( + width: 180, + height: 250, + child: SlideAnim( + controller: context + .read() + .answerAnimationController!, + index: 3, + child: AnswerBox( + index: + state + .currentQuestion + ?.answers?[3] + .order ?? + 1, + answer: + state.currentQuestion?.answers?[3] ?? + AnswerEntity(), + correctAnswer: + state.currentQuestion?.correctAnswer ?? + 0, + onNotifTap: (AnswerEntity answer) { + context + .read() + .showAnswerDialog( + context: context, + answerEntity: answer, + ); + }, + onTap: (isCorrect, correctAnswer) => + context.read().add( + ChooseAnswerEvent( + isCorrect, + correctAnswer, + context, + ), + ), + ), + ), + ); + } + }, + ), + ), + ], + ), ], ), ), @@ -393,13 +462,27 @@ class _QuestionScreenState extends State left: 90, right: 90, child: FadeAnimController( - controller: context.read().imageAnimationController!, + controller: context + .read() + .imageAnimationController!, child: TextButton( onPressed: () async { - if(context.read().imageAnimationController?.isForwardOrCompleted ?? false){ - context.read().imageAnimationController?.reverse(); - context.read().answerAnimationController?.forward(); - context.read().showingAnswerSequence(show: false); + if (context + .read() + .imageAnimationController + ?.isForwardOrCompleted ?? + false) { + context + .read() + .imageAnimationController + ?.reverse(); + context + .read() + .answerAnimationController + ?.forward(); + context.read().showingAnswerSequence( + show: false, + ); context.read().pausePlaying(); } }, @@ -412,7 +495,7 @@ class _QuestionScreenState extends State style: MYTextStyle.button2, ), ), - ) + ), ), ), PositionedDirectional( @@ -429,4 +512,3 @@ class _QuestionScreenState extends State ); } } -