diff --git a/lib/core/routers/hero_dialog_route.dart b/lib/core/routers/hero_dialog_route.dart new file mode 100644 index 0000000..0431187 --- /dev/null +++ b/lib/core/routers/hero_dialog_route.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/common_ui/resources/my_colors.dart'; + +class HeroDialogRoute extends PageRoute { + HeroDialogRoute({ + required WidgetBuilder builder, + super.settings, + super.fullscreenDialog, + }) : _builder = builder; + + final WidgetBuilder _builder; + + @override + bool get opaque => false; + + @override + bool get fullscreenDialog => false; + + @override + bool get barrierDismissible => false; + + @override + Duration get transitionDuration => const Duration(milliseconds: 300); // Adjust as needed + + @override + bool get maintainState => true; + + @override + Color get barrierColor => MyColors.transparent; // Or your desired barrier color + + @override + Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { + return _builder(context); + } + + @override + String get barrierLabel => ''; +} diff --git a/lib/core/widgets/answer_box/answer_box.dart b/lib/core/widgets/answer_box/answer_box.dart index 947e77c..bb6f470 100644 --- a/lib/core/widgets/answer_box/answer_box.dart +++ b/lib/core/widgets/answer_box/answer_box.dart @@ -15,7 +15,7 @@ class AnswerBox extends StatefulWidget { final AnswerEntity answer; final int correctAnswer; - final void Function(bool isCorrect)? onTap; + final void Function(bool isCorrect, int correctAnswer)? onTap; final int index; @override @@ -33,29 +33,27 @@ class _AnswerBoxState extends State { setState(() { selected = true; }); - widget.onTap?.call(widget.index == widget.correctAnswer); + widget.onTap?.call(widget.index == widget.correctAnswer, widget.correctAnswer); } : null, - child: SizedBox( - child: Stack( - alignment: Alignment.bottomCenter, - clipBehavior: Clip.none, - children: [ - AnswerPictureBox( - selected: selected, - index: widget.index, - image: widget.answer.image ?? '', - correctAnswer: widget.correctAnswer, + 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 ?? '', ), - Positioned( - left: 0, - right: 0, - bottom: -MySpaces.s26, - child: AnswerTextBox( - text: widget.answer.title ?? '', - ), - ), - ], - ), + ), + ], ), ); } diff --git a/lib/core/widgets/answer_box/answer_box_show.dart b/lib/core/widgets/answer_box/answer_box_show.dart new file mode 100644 index 0000000..5811b2b --- /dev/null +++ b/lib/core/widgets/answer_box/answer_box_show.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.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/features/question/domain/entity/answer_entity.dart'; + +class AnswerBoxShow extends StatelessWidget { + const AnswerBoxShow({ + super.key, + required this.answer, + required this.index, + this.correct, + }); + + final AnswerEntity answer; + final int index; + final bool? correct; + + @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, + ), + Positioned( + left: 0, + right: 0, + bottom: -MySpaces.s26, + child: AnswerTextBox( + text: answer.title ?? '', + ), + ), + ], + ); + } +} diff --git a/lib/core/widgets/answer_box/styles/picture_box.dart b/lib/core/widgets/answer_box/styles/picture_box.dart index b1b1976..b7ee0c6 100644 --- a/lib/core/widgets/answer_box/styles/picture_box.dart +++ b/lib/core/widgets/answer_box/styles/picture_box.dart @@ -1,8 +1,6 @@ import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.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'; @@ -10,7 +8,7 @@ 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 StatefulWidget { +class AnswerPictureBox extends StatelessWidget { const AnswerPictureBox({ super.key, required this.selected, @@ -24,19 +22,6 @@ class AnswerPictureBox extends StatefulWidget { final int index; final int correctAnswer; - @override - State createState() => _AnswerPictureBoxState(); -} - -class _AnswerPictureBoxState extends State { - late Future _imageFuture; - - @override - void initState() { - super.initState(); - _imageFuture = File(widget.image).readAsBytes(); - } - @override Widget build(BuildContext context) { return CustomPaint( @@ -46,47 +31,27 @@ class _AnswerPictureBoxState extends State { clipper: _SvgCustomClipper(), child: Stack( children: [ - FutureBuilder( - future: _imageFuture, - builder: (context, snapshot) { - return AnimatedCrossFade( - crossFadeState: snapshot.hasData - ? CrossFadeState.showSecond - : CrossFadeState.showFirst, - duration: Duration(milliseconds: 300), - firstChild: SizedBox( + 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, - child: Center( - child: CupertinoActivityIndicator( - color: MyColors.white, - ), - ), - ), - secondChild: Builder( - builder: (context) { - if (widget.selected && - (widget.index != widget.correctAnswer)) { - return BlackWhiteEffect( - child: Image.memory( - snapshot.data ?? Uint8List(0), - fit: BoxFit.cover, - height: 170, - width: 170, - ), - ); - } else { - return Image.memory( - snapshot.data ?? Uint8List(0), - fit: BoxFit.cover, - height: 170, - width: 170, - ); - } - }, - ) - ); - } + ); + } + }, ), PositionedDirectional( top: MySpaces.s12, @@ -108,7 +73,7 @@ class _AnswerPictureBoxState extends State { ), ), child: Text( - '${widget.index}', + '$index', style: Marhey.semiBold17.copyWith( color: MyColors.white, ), @@ -116,12 +81,12 @@ class _AnswerPictureBoxState extends State { ), ), ), - if(widget.selected) + if(selected) PositionedDirectional( top: MySpaces.s14, end: MySpaces.s12, child: MyImage( - image: widget.index == widget.correctAnswer ? MyAssets.correct : MyAssets + image: index == correctAnswer ? MyAssets.correct : MyAssets .wrong, size: MySpaces.s40, ), diff --git a/lib/features/level/presentation/bloc/level_bloc.dart b/lib/features/level/presentation/bloc/level_bloc.dart index 7ec2351..b72a7f6 100644 --- a/lib/features/level/presentation/bloc/level_bloc.dart +++ b/lib/features/level/presentation/bloc/level_bloc.dart @@ -149,7 +149,7 @@ class LevelBloc extends Bloc { await Future.delayed(const Duration(seconds: 1)); if (scrollController.hasClients) { - if(currentLevel > 14){ + if(currentLevel >= 14){ scrollController.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 500), // Note: 500 seconds is very long. diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index eda94c4..975a973 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -1,11 +1,12 @@ import 'dart:async'; -import 'package:bloc/bloc.dart'; import 'package:confetti/confetti.dart'; -import 'package:flutter/cupertino.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/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'; import 'package:hadi_hoda_flutter/core/routers/my_routes.dart'; import 'package:hadi_hoda_flutter/core/services/audio_service.dart'; import 'package:hadi_hoda_flutter/core/status/base_status.dart'; @@ -13,11 +14,13 @@ import 'package:hadi_hoda_flutter/core/utils/context_provider.dart'; import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; import 'package:hadi_hoda_flutter/core/widgets/hadith_dialog/hadith_dialog.dart'; import 'package:hadi_hoda_flutter/features/level/domain/entity/level_entity.dart'; +import 'package:hadi_hoda_flutter/features/question/domain/entity/answer_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/entity/question_entity.dart'; import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_level_usecase.dart'; import 'package:hadi_hoda_flutter/features/question/domain/usecases/get_next_level_usecase.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/screens/answer_screen.dart'; import 'package:showcaseview/showcaseview.dart'; class QuestionBloc extends Bloc { @@ -51,6 +54,7 @@ class QuestionBloc extends Bloc { GlobalKey(), ]; late final Stream volumeStream; + bool isPlaying = false; /// ------------Controllers------------ final AudioService _audioService; @@ -80,12 +84,41 @@ class QuestionBloc extends Bloc { await _audioService.changeMute(); } + Future showAnswerDialog({ + required BuildContext context, + required AnswerEntity answerEntity, + bool? correct, + }) async { + await Navigator.of(context).push( + HeroDialogRoute( + builder: (dialogContext) { + return AnswerScreen(answerEntity: answerEntity, correct: correct); + }, + ), + ); + } + + Future playback() async { + if (isPlaying) return; + for (int i = 0; i < 4; i++) { + await Future.delayed(Duration(seconds: 1)); + if (ContextProvider.context.mounted) { + await showAnswerDialog( + context: ContextProvider.context, + answerEntity: state.currentQuestion?.answers?[i] ?? AnswerEntity(), + ); + isPlaying = true; + } + } + isPlaying = false; + } + /// ------------Event Calls------------ FutureOr _getLevelEvent(GetLevelEvent event, Emitter emit) async { await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( (value) { value.fold( - (data) { + (data) async { final LevelEntity level = LevelEntity( id: data.id, order: data.order, @@ -100,7 +133,8 @@ class QuestionBloc extends Bloc { levelEntity: level, currentQuestion: data.questions?.first, )); - playVoice(); + await playVoice(); + playback(); }, (error) { emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage))); @@ -116,6 +150,12 @@ class QuestionBloc extends Bloc { if (event.chooseCorrectAnswer) { confettiController.play(); + await showAnswerDialog( + answerEntity: state.currentQuestion?.answers?.singleWhere((e) => + e.order == event.correctAnswer) ?? AnswerEntity(), + context: ContextProvider.context, + correct: true, + ); await Future.delayed(Duration(seconds: 2), () async { final QuestionEntity? findPreQuestion = state.currentQuestion; final int findIndex = (findPreQuestion?.order ?? 1); @@ -137,6 +177,7 @@ class QuestionBloc extends Bloc { } } else { await playVoice(); + playback(); } }); } diff --git a/lib/features/question/presentation/bloc/question_event.dart b/lib/features/question/presentation/bloc/question_event.dart index e16f525..57570dc 100644 --- a/lib/features/question/presentation/bloc/question_event.dart +++ b/lib/features/question/presentation/bloc/question_event.dart @@ -9,7 +9,8 @@ class GetLevelEvent extends QuestionEvent { class ChooseAnswerEvent extends QuestionEvent { final bool chooseCorrectAnswer; - const ChooseAnswerEvent(this.chooseCorrectAnswer); + final int correctAnswer; + const ChooseAnswerEvent(this.chooseCorrectAnswer, this.correctAnswer); } class GetNextLevelEvent extends QuestionEvent { diff --git a/lib/features/question/presentation/ui/screens/answer_screen.dart b/lib/features/question/presentation/ui/screens/answer_screen.dart new file mode 100644 index 0000000..b54b18d --- /dev/null +++ b/lib/features/question/presentation/ui/screens/answer_screen.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:hadi_hoda_flutter/core/utils/context_provider.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'; + +class AnswerScreen extends StatefulWidget { + const AnswerScreen({super.key, required this.answerEntity, this.correct}); + + final AnswerEntity answerEntity; + final bool? correct; + + @override + State createState() => _AnswerScreenState(); +} + +class _AnswerScreenState extends State { + @override + void initState() { + super.initState(); + back(); + } + + Future back() async { + await Future.delayed(Duration(seconds: 2), () { + if (ContextProvider.context.mounted) { + Navigator.pop(ContextProvider.context); + } + }); + } + + @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, + child: AnswerBoxShow( + answer: widget.answerEntity, + index: widget.answerEntity.order ?? 0, + correct: widget.correct, + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/question/presentation/ui/screens/question_screen.dart b/lib/features/question/presentation/ui/screens/question_screen.dart index acd10d9..467ca8b 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -90,15 +90,18 @@ class QuestionScreen extends StatelessWidget { child: BlocBuilder( buildWhen: (previous, current) => previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => AnswerBox( + builder: (context, state) => Hero( key: Key('${state.currentQuestion?.id}'), - index: state.currentQuestion?.answers?[index].order ?? 1, - answer: state.currentQuestion?.answers?[index] ?? AnswerEntity(), - correctAnswer: state.currentQuestion?.correctAnswer ?? 0, - onTap: (isCorrect) => - context.read().add( - ChooseAnswerEvent(isCorrect), - ), + 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().add( + ChooseAnswerEvent(isCorrect, correctAnswer), + ), + ), ), ), ), @@ -154,7 +157,7 @@ class QuestionScreen extends StatelessWidget { ), Spacer(), RefreshButton( - onTap: () {}, + onTap: () => context.read().playback(), ), ], );