diff --git a/lib/features/question/data/datasource/question_datasource.dart b/lib/features/question/data/datasource/question_datasource.dart index 076b51a..ed5d2dc 100644 --- a/lib/features/question/data/datasource/question_datasource.dart +++ b/lib/features/question/data/datasource/question_datasource.dart @@ -48,7 +48,10 @@ class QuestionDatasourceImpl implements IQuestionDatasource { (e) => e.code == selectedLanguage, orElse: () => TotalDataEntity(), ); - if(index > (findData.nodes?.length ?? 0)){ + final List? levelList = findData.nodes?.where( + (e) => e.nodeType == NodeType.level, + ).toList(); + if(index > (levelList?.length ?? 0)){ throw const MyException(); } final NodeEntity? findLevel = findData.nodes?.singleWhere( diff --git a/lib/features/question/presentation/bloc/question_bloc.dart b/lib/features/question/presentation/bloc/question_bloc.dart index 6271771..3eeecc7 100644 --- a/lib/features/question/presentation/bloc/question_bloc.dart +++ b/lib/features/question/presentation/bloc/question_bloc.dart @@ -31,7 +31,6 @@ class QuestionBloc extends Bloc { this._effectAudioService, ) : super(const QuestionState()) { volumeStream = _mainAudioService.volumeStream(); - playingStream = _mainAudioService.playingStream(); initAudios(); on(_getLevelEvent); on(_chooseAnswerEvent); @@ -43,8 +42,8 @@ class QuestionBloc extends Bloc { _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); } _backgroundAudioService.dispose(); - answerAnimationController.dispose(); - imageAnimationController.dispose(); + answerAnimationController?.dispose(); + imageAnimationController?.dispose(); scrollController.dispose(); return super.close(); } @@ -67,16 +66,15 @@ class QuestionBloc extends Bloc { 'hadith_key': GlobalKey(), 'guide_key': GlobalKey(), }; - late final Stream volumeStream; - late final Stream playingStream; + Stream? volumeStream; bool showAnswerSequence = true; /// ------------Controllers------------ final AudioService _mainAudioService; final AudioService _backgroundAudioService = AudioService(volume: 0); final AudioService _effectAudioService; - late final AnimationController answerAnimationController; - late final AnimationController imageAnimationController; + AnimationController? answerAnimationController; + AnimationController? imageAnimationController; final ScrollController scrollController = ScrollController(); /// ------------Functions------------ @@ -89,10 +87,10 @@ class QuestionBloc extends Bloc { // ); // } - Future showImageWithDelayed() async { - await Future.delayed(const Duration(milliseconds: 500)); - imageAnimationController.forward(); - } + // Future showImageWithDelayed() async { + // await Future.delayed(const Duration(milliseconds: 500)); + // imageAnimationController?.forward(); + // } void showHadith({required BuildContext context}) { showHadithDialog( @@ -194,18 +192,13 @@ class QuestionBloc extends Bloc { } Future getNextLevelEvent({required BuildContext context}) async { - _backgroundAudioService.dispose(); - await _getNextLevelUseCase(QuestionParams()).then((value) => value.fold( - (data) { - context.pushNamed( - Routes.questionPage, - pathParameters: {'id': '${data.id}'}, - ); - }, - (error) { + await _getNextLevelUseCase(QuestionParams()).then((value) => + value.fold((data) { + add(GetLevelEvent('${data.id}', context)); + }, (error) { goToLevelPage(context: MyContext.get); }, - ), + ), ); } @@ -244,10 +237,12 @@ class QuestionBloc extends Bloc { getQuestionStatus: const BaseComplete(''), levelEntity: level, currentQuestion: data.questions?.first, + showAnswers: false, + correctAnswer: false, )); await playQuestionAudio(); - imageAnimationController.reverse(); - answerAnimationController.forward().then((value) { + imageAnimationController?.reverse(); + answerAnimationController?.forward().then((value) { showQueueAnswer(); }); }, @@ -271,6 +266,7 @@ class QuestionBloc extends Bloc { e.order == event.correctAnswer) ?? AnswerEntity(), showConfetti: true, ); + answerAnimationController?.reverse(); await Future.delayed(const Duration(seconds: 1), () async { final QuestionEntity? findPreQuestion = state.currentQuestion; final int findIndex = (findPreQuestion?.order ?? 1); @@ -293,13 +289,12 @@ class QuestionBloc extends Bloc { } } else { showingAnswerSequence(show: true); - answerAnimationController.reverse(); await Future.delayed(const Duration(seconds: 1)); - imageAnimationController.forward(); + imageAnimationController?.forward(); scrollController.jumpTo(0); await playQuestionAudio(); - imageAnimationController.reverse(); - answerAnimationController.forward().then((value) { + imageAnimationController?.reverse(); + answerAnimationController?.forward().then((value) { showQueueAnswer(); }); } diff --git a/lib/features/question/presentation/ui/question_page.dart b/lib/features/question/presentation/ui/question_page.dart index d64f6e7..f4ce8e5 100644 --- a/lib/features/question/presentation/ui/question_page.dart +++ b/lib/features/question/presentation/ui/question_page.dart @@ -22,6 +22,7 @@ class QuestionPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: MyPopScope( + backHome: 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 b192a43..66a0d7f 100644 --- a/lib/features/question/presentation/ui/screens/question_screen.dart +++ b/lib/features/question/presentation/ui/screens/question_screen.dart @@ -1,3 +1,4 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:gif/gif.dart'; @@ -33,8 +34,8 @@ class QuestionScreen extends StatefulWidget { State createState() => _QuestionScreenState(); } -class _QuestionScreenState extends State with TickerProviderStateMixin, WidgetsBindingObserver { - +class _QuestionScreenState extends State + with TickerProviderStateMixin, WidgetsBindingObserver { @override void initState() { super.initState(); @@ -50,15 +51,16 @@ class _QuestionScreenState extends State with TickerProviderStat duration: const Duration(milliseconds: 500), reverseDuration: const Duration(milliseconds: 500), ); - if(LocalStorage.readData(key: MyConstants.firstShowcase) == 'true') { - context.read().imageAnimationController.forward(); + if (LocalStorage.readData(key: MyConstants.firstShowcase) == 'true') { + context.read().imageAnimationController?.forward(); } } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); - if(state == AppLifecycleState.paused || state == AppLifecycleState.inactive){ + if (state == AppLifecycleState.paused || + state == AppLifecycleState.inactive) { context.read().pauseBackgroundPlaying(); } else if (state == AppLifecycleState.resumed) { context.read().playBackgroundPlaying(); @@ -93,12 +95,11 @@ class _QuestionScreenState extends State with TickerProviderStat return FadeAnimDelayed( duration: const Duration(seconds: 1), child: FadeAnimController( - controller: context.read().imageAnimationController, + controller: context.read().imageAnimationController!, child: Column( children: [ - 10.0.gapHeight, _titles(context), - const Spacer(), + 20.0.gapHeight, BlocBuilder( builder: (context, state) => AnswerPictureBox( key: Key('${state.currentQuestion?.image}'), @@ -110,7 +111,6 @@ class _QuestionScreenState extends State with TickerProviderStat autostart: Autostart.loop, ), ), - const Spacer(), ], ), ), @@ -120,7 +120,7 @@ class _QuestionScreenState extends State with TickerProviderStat Widget _stepper() { return BlocBuilder( buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, + previous.currentQuestion?.id != current.currentQuestion?.id, builder: (context, state) => FadeAnim( child: QuestionStepper( length: state.levelEntity?.questions?.length ?? 0, @@ -133,20 +133,22 @@ class _QuestionScreenState extends State with TickerProviderStat Widget _titles(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => - Text( - state.currentQuestion?.title ?? '', - textAlign: TextAlign.center, - style: MYTextStyle.titr1.copyWith( - shadows: [ - BoxShadow( - offset: const Offset(0, 2), - color: MyColors.black.withValues(alpha: 0.25), - ), - ], + previous.currentQuestion?.id != current.currentQuestion?.id, + builder: (context, state) => AutoSizeText( + state.currentQuestion?.title ?? '', + textAlign: TextAlign.center, + minFontSize: 16, + maxFontSize: 20, + maxLines: 8, + style: MYTextStyle.titr1.copyWith( + shadows: [ + BoxShadow( + offset: const Offset(0, 2), + color: MyColors.black.withValues(alpha: 0.25), ), - ), + ], + ), + ), ); } @@ -156,7 +158,7 @@ class _QuestionScreenState extends State with TickerProviderStat padding: const EdgeInsets.only(top: 10), children: [ FadeAnimController( - controller: context.read().answerAnimationController, + controller: context.read().answerAnimationController!, child: _titles(context), ), 50.0.gapHeight, @@ -164,43 +166,215 @@ class _QuestionScreenState extends State with TickerProviderStat width: context.widthScreen, child: BlocBuilder( buildWhen: (previous, current) => - previous.currentQuestion?.id != current.currentQuestion?.id, - builder: (context, state) => - Wrap( - alignment: WrapAlignment.center, - spacing: 10, - runSpacing: 20, - children: List.generate( - state.currentQuestion?.answers?.length ?? 0, - (index) => - state.currentQuestion?.answers?[index].imageId == null - ? const SizedBox.shrink() - : SizedBox( - key: Key('${state.currentQuestion?.id}$index'), - width: 180, - height: 250, - child: SlideAnim( - controller: context.read().answerAnimationController, - 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().showAnswerDialog( - context: context, - answerEntity: answer, + previous.currentQuestion?.id != current.currentQuestion?.id, + builder: (context, state) => Column( + spacing: 30, + children: [ + Row( + key: Key('${state.currentQuestion?.id}answer0'), + mainAxisAlignment: MainAxisAlignment.spaceBetween, + 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, + ), + ), + ), + ), + ); + } + }, + ), + ], + ), + 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'), + 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, + ), + ), + ), + ), ); - }, - onTap: (isCorrect, correctAnswer) => - context.read().add( - ChooseAnswerEvent(isCorrect, correctAnswer, context), + } + }, + ), + if ((state.currentQuestion?.answers?.length ?? 0) > 3) + Builder( + key: Key('${state.currentQuestion?.id}1'), + 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, + ), + ), + ), ), - ), + ); + } + }, ), - ), - ), + ], ), + ], + ), ), ), ], @@ -214,27 +388,23 @@ class _QuestionScreenState extends State with TickerProviderStat child: Stack( alignment: AlignmentDirectional.centerStart, children: [ - StreamBuilder( - initialData: false, - stream: context.read().playingStream, - builder: (context, snapshot) => GlobeAnimation( - state: snapshot.data ?? false, - child: MyImage( - image: MyAssets.globe, - fit: BoxFit.cover, - size: setSize(context: context, tablet: 120), - ), + GlobeAnimation( + state: true, + child: MyImage( + image: MyAssets.globe, + fit: BoxFit.cover, + size: setSize(context: context, tablet: 120), ), ), Positioned( left: 90, right: 90, child: FadeAnimController( - controller: context.read().imageAnimationController, + controller: context.read().imageAnimationController!, child: TextButton( onPressed: () async { - context.read().imageAnimationController.reverse(); - context.read().answerAnimationController.forward(); + context.read().imageAnimationController?.reverse(); + context.read().answerAnimationController?.forward(); context.read().showingAnswerSequence(show: false); context.read().pausePlaying(); }, @@ -243,11 +413,11 @@ class _QuestionScreenState extends State with TickerProviderStat ), child: FittedBox( child: Text( - context.translate.skip, - style: MYTextStyle.button2 + context.translate.skip, + style: MYTextStyle.button2, ), ), - ), + ) ), ), PositionedDirectional( diff --git a/pubspec.yaml b/pubspec.yaml index 24e6a92..310ff7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: hadi_hoda_flutter description: "A new Flutter project." publish_to: 'none' -version: 0.9.1+1 +version: 0.9.2+1 environment: sdk: ^3.9.2