import 'dart:async'; 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'; 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'; import 'package:hadi_hoda_flutter/core/utils/local_storage.dart'; import 'package:hadi_hoda_flutter/core/utils/my_context.dart'; import 'package:hadi_hoda_flutter/core/widgets/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 { /// ------------constructor------------ QuestionBloc( this._getLevelUseCase, this._getNextLevelUseCase, this._mainAudioService, this._effectAudioService, ) : super(QuestionState()) { volumeStream = _mainAudioService.volumeStream(); playingStream = _mainAudioService.playingStream(); initAudios(); registerShowCase(); on(_getLevelEvent); on(_chooseAnswerEvent); } @override Future close() { unRegisterShowCase(); if (_mainAudioService.audioVolume != 0) { _mainAudioService.setVolume(volume: MyConstants.musicAudioVolume); } _backgroundAudioService.dispose(); answerAnimationController.dispose(); imageAnimationController.dispose(); titleController.dispose(); return super.close(); } /// ------------UseCases------------ final GetLevelUseCase _getLevelUseCase; final GetNextLevelUseCase _getNextLevelUseCase; /// ------------Variables------------ final Map showCaseKey = { 'answer_key_0': GlobalKey(), 'answer_key_1': GlobalKey(), 'answer_key_2': GlobalKey(), 'answer_key_3': GlobalKey(), 'notif_key_0': GlobalKey(), 'notif_key_1': GlobalKey(), 'notif_key_2': GlobalKey(), 'notif_key_3': GlobalKey(), 'stepper_key': GlobalKey(), 'hadith_key': GlobalKey(), 'guide_key': GlobalKey(), }; late final Stream volumeStream; late final Stream playingStream; bool showAnswerSequence = true; /// ------------Controllers------------ final AudioService _mainAudioService; final AudioService _backgroundAudioService = AudioService(volume: 0); final AudioService _effectAudioService; late final AnimationController answerAnimationController; late final AnimationController imageAnimationController; final ScrollController titleController = ScrollController(); /// ------------Functions------------ void registerShowCase() { try { ShowcaseView.register( onStart: (showcaseIndex, key) { LocalStorage.saveData(key: MyConstants.firstShowcase, value: 'true'); }, onDismiss: (onDismiss) async { await playQuestionAudio(); }, onFinish: () async { await playQuestionAudio(); }, ); } catch (_) {} } void unRegisterShowCase() { try { ShowcaseView.get().unregister(); } catch (_) {} } void startShowcase() { if (LocalStorage.readData(key: MyConstants.firstShowcase) != 'true') { try { ShowcaseView.get().startShowCase([ showCaseKey['answer_key_1']!, showCaseKey['notif_key_0']!, showCaseKey['stepper_key']!, showCaseKey['hadith_key']!, showCaseKey['guide_key']!, ]); } catch (_) {} } } void startScrollTitle({Duration? audioDuration}) { if (audioDuration == null || audioDuration == Duration.zero) return; titleController.animateTo( titleController.position.maxScrollExtent, duration: audioDuration, curve: Curves.linear ); } void showHadith({required BuildContext context}) { showHadithDialog( context: context, hadith: state.currentQuestion?.hadiths ?? [], ); } void goToHomePage({required BuildContext context}) { context.goNamed(Routes.homePage); } void goToLevelPage({required BuildContext context}) { context.goNamed(Routes.levelPage); } Future playDiamondAudio() async { await _mainAudioService.setAudio(assetPath: MyAudios.diamondEnd); await _mainAudioService.play(); } Future initAudios() async { await Future.wait([ _mainAudioService.stop(), _mainAudioService.setLoopMode(isLoop: false), _backgroundAudioService.setAudio(assetPath: MyAudios.question), _backgroundAudioService.setLoopMode(isLoop: true), ]); if (_mainAudioService.audioVolume != 0) { await Future.wait([ _mainAudioService.setVolume(volume: MyConstants.questionAudioVolume), _backgroundAudioService.setVolume(volume: 0.1), ]); } await _backgroundAudioService.play(); } Future playWrongAudio() async { await _effectAudioService.setAudio(assetPath: MyAudios.incorrectAnswer); await _effectAudioService.play(); } Future playAnswerAudio({String? audio}) async { await _mainAudioService.setAudio(filePath: audio); await _mainAudioService.play(); } Future playQuestionAudio() async { if(titleController.hasClients){ titleController.jumpTo(0); } Duration? duration = await _mainAudioService.setAudio(filePath: state.currentQuestion?.audio); startScrollTitle(audioDuration: duration); await _mainAudioService.play(); } Future changeMute() async { await Future.wait([ _mainAudioService.changeMute(newVolume: MyConstants.questionAudioVolume), _backgroundAudioService.changeMute(newVolume: 0.1), _effectAudioService.changeMute(), ]); } Future showQueueAnswer() async { if(!showAnswerSequence) return; final List answers = state.currentQuestion?.answers ?? []; if (answers.isNotEmpty) { answers.removeWhere((e) => e.imageId == null); } for (final answer in answers) { await Future.delayed(const Duration(milliseconds: 500), () async { if (MyContext.get.mounted) { await showAnswerDialog( context: MyContext.get, answerEntity: answer, autoClose: true, ); } }); } } Future showAnswerDialog({ required BuildContext context, required AnswerEntity answerEntity, String? correctAudio, bool showConfetti = false, bool autoClose = false, }) async { await Navigator.of(context).push( HeroDialogRoute( builder: (dialogContext) { return AnswerScreen( correctAudio: correctAudio, answerEntity: answerEntity, showConfetti: showConfetti, autoClose: autoClose, ); }, ), ); } Future getNextLevelEvent({required BuildContext context}) async { await _getNextLevelUseCase(QuestionParams()).then((value) => value.fold( (data) { context.pushNamed( Routes.questionPage, pathParameters: {'id': '${data.id}'}, ); }, (error) { goToLevelPage(context: MyContext.get); }, ), ); } void showingAnswerSequence({required bool show}){ showAnswerSequence = show; } Future pausePlaying() async { await _mainAudioService.pause(); } Future pauseBackgroundPlaying() async { await _backgroundAudioService.pause(); } Future playBackgroundPlaying() async { await _backgroundAudioService.play(); } /// ------------Event Calls------------ FutureOr _getLevelEvent(GetLevelEvent event, Emitter emit) async { await _getLevelUseCase(QuestionParams(id: int.parse(event.id ?? '0'))).then( (value) { value.fold( (data) async { final LevelEntity level = LevelEntity( id: data.id, order: data.order, title: data.title, questions: [ ...?data.questions, QuestionEntity(order: (data.questions?.length ?? 0) + 1) ], ); emit(state.copyWith( getQuestionStatus: BaseComplete(''), levelEntity: level, currentQuestion: data.questions?.first, )); if(LocalStorage.readData(key: MyConstants.firstShowcase) != 'true'){ await Future.delayed(Duration(milliseconds: 500)); answerAnimationController.forward().then((value) { startShowcase(); }); } else { await playQuestionAudio(); imageAnimationController.reverse(); answerAnimationController.forward().then((value) { showQueueAnswer(); }); } }, (error) { emit(state.copyWith(getQuestionStatus: BaseError(error.errorMessage))); }, ); }, ); } FutureOr _chooseAnswerEvent(ChooseAnswerEvent event, Emitter emit,) async { emit(state.copyWith(correctAnswer: event.chooseCorrectAnswer)); if (event.chooseCorrectAnswer) { answerAnimationController.reverse(); await showAnswerDialog( context: MyContext.get, correctAudio: state.currentQuestion?.correctAudio, answerEntity: state.currentQuestion?.answers?.singleWhere((e) => e.order == event.correctAnswer) ?? AnswerEntity(), showConfetti: true, ); await Future.delayed(Duration(seconds: 1), () async { final QuestionEntity? findPreQuestion = state.currentQuestion; final int findIndex = (findPreQuestion?.order ?? 1); emit( state.copyWith( currentQuestion: state.levelEntity?.questions?[findIndex], ), ); if (state.currentQuestion?.order == state.levelEntity?.questions?.length) { playDiamondAudio(); int currentLevel = int.parse( LocalStorage.readData(key: MyConstants.currentLevel) ?? '1'); if (state.levelEntity?.order == currentLevel) { ++currentLevel; await LocalStorage.saveData( key: MyConstants.currentLevel, value: '$currentLevel', ); } } else { showingAnswerSequence(show: true); imageAnimationController.forward(); await playQuestionAudio(); imageAnimationController.reverse(); answerAnimationController.forward().then((value) { showQueueAnswer(); }); } }); } else { playWrongAudio(); } } }