You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

327 lines
11 KiB

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<QuestionEvent, QuestionState> {
/// ------------constructor------------
QuestionBloc(
this._getLevelUseCase,
this._getNextLevelUseCase,
this._mainAudioService,
this._effectAudioService,
) : super(QuestionState()) {
volumeStream = _mainAudioService.volumeStream();
playingStream = _mainAudioService.playingStream();
initAudios();
registerShowCase();
on<GetLevelEvent>(_getLevelEvent);
on<ChooseAnswerEvent>(_chooseAnswerEvent);
}
@override
Future<void> close() {
unRegisterShowCase();
if (_mainAudioService.audioVolume != 0) {
_mainAudioService.setVolume(volume: MyConstants.musicAudioVolume);
}
_backgroundAudioService.dispose();
answerAnimationController.dispose();
imageAnimationController.dispose();
return super.close();
}
/// ------------UseCases------------
final GetLevelUseCase _getLevelUseCase;
final GetNextLevelUseCase _getNextLevelUseCase;
/// ------------Variables------------
final Map<String, GlobalKey> 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<double> volumeStream;
late final Stream<bool> 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;
/// ------------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 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<void> playDiamondAudio() async {
await _mainAudioService.setAudio(assetPath: MyAudios.diamondEnd);
await _mainAudioService.play();
}
Future<void> 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<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 Future.wait([
_mainAudioService.changeMute(newVolume: MyConstants.questionAudioVolume),
_backgroundAudioService.changeMute(newVolume: 0.1),
_effectAudioService.changeMute(),
]);
}
Future<void> showQueueAnswer() async {
if(!showAnswerSequence) return;
final List<AnswerEntity> 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<void> 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<void> 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;
}
/// ------------Event Calls------------
FutureOr<void> _getLevelEvent(GetLevelEvent event, Emitter<QuestionState> 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<void> _chooseAnswerEvent(ChooseAnswerEvent event,
Emitter<QuestionState> 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();
}
}
}