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.
354 lines
12 KiB
354 lines
12 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();
|
|
titleController.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;
|
|
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<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 {
|
|
if(titleController.hasClients){
|
|
titleController.jumpTo(0);
|
|
}
|
|
Duration? duration = await _mainAudioService.setAudio(filePath: state.currentQuestion?.audio);
|
|
startScrollTitle(audioDuration: duration);
|
|
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;
|
|
}
|
|
|
|
Future<void> pausePlaying() async {
|
|
await _mainAudioService.pause();
|
|
}
|
|
|
|
Future<void> pauseBackgroundPlaying() async {
|
|
await _backgroundAudioService.pause();
|
|
}
|
|
|
|
Future<void> playBackgroundPlaying() async {
|
|
await _backgroundAudioService.play();
|
|
}
|
|
|
|
/// ------------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();
|
|
}
|
|
}
|
|
}
|