import 'dart:async'; import 'dart:developer'; import 'package:appinio_swiper/appinio_swiper.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:mohem_flutter_app/api/marathon/demo_marathon_repo.dart'; import 'package:mohem_flutter_app/api/marathon/marathon_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_generic_model.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; import 'package:mohem_flutter_app/models/marathon/question_model.dart'; import 'package:mohem_flutter_app/models/marathon/tutorial_notification_model.dart'; import 'package:mohem_flutter_app/models/marathon/winner_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; import 'package:video_player/video_player.dart'; class MarathonProvider extends ChangeNotifier { //************************************************ VARIABLES ********************************************************** final AppinioSwiperController swiperController = AppinioSwiperController(); MarathonDetailModel marathonDetailModel = MarathonDetailModel(); MarathonDetailModel demoMarathonDetailModel = MarathonDetailModel(); List cardContentList = []; QuestionModel currentQuestion = QuestionModel(); List answerStatusesList = []; QuestionCardStatus questionCardStatus = QuestionCardStatus.question; List? selectedWinners; int? selectedOptionIndex; String? selectedOptionId; int? totalQualifiers; Locale savedLocale = const Locale("en", "US"); String? gapTimeImage; String? gapTimeText; int? gapTimeType; bool iAmWinner = false; bool isGettingQualifiers = false; bool isPrivilegedWithMarathon = false; bool _isLoading = false; bool get isLoading => _isLoading; set isLoading(bool value) { _isLoading = value; notifyListeners(); } bool _isUpComingMarathon = true; bool get isUpComingMarathon => _isUpComingMarathon; set isUpComingMarathon(bool value) { _isUpComingMarathon = value; notifyListeners(); } bool _itsMarathonTime = false; bool get itsMarathonTime => _itsMarathonTime; set itsMarathonTime(bool value) { _itsMarathonTime = value; notifyListeners(); } bool _canPlayDemo = true; bool get canPlayDemo => _canPlayDemo; set canPlayDemo(bool value) { _canPlayDemo = value; notifyListeners(); } bool _isButtonEnabled = false; bool get isButtonEnabled => _isButtonEnabled; set isButtonEnabled(bool value) { _isButtonEnabled = value; notifyListeners(); } bool _isUserWaiting = false; bool get isUserWaiting => _isUserWaiting; set isUserWaiting(bool value) { _isUserWaiting = value; notifyListeners(); } bool _isMarathonCompleted = false; bool get isMarathonCompleted => _isMarathonCompleted; set isMarathonCompleted(bool value) { _isMarathonCompleted = value; notifyListeners(); } bool isUserOutOfGame = false; set updateIsUserOutOfGame(bool value) { isUserOutOfGame = value; notifyListeners(); } int _currentQuestionNumber = 0; int get currentQuestionNumber => _currentQuestionNumber; set currentQuestionNumber(int value) { _currentQuestionNumber = value; notifyListeners(); } int _totalMarathoners = 0; int get totalMarathoners => _totalMarathoners; set totalMarathoners(int value) { _totalMarathoners = value; notifyListeners(); } //****************SPONSOR VIDEO PLAYER********** late VideoPlayerController videoController; Future initializeVideoPlayer() async { videoController = VideoPlayerController.network(ApiConsts.marathonBaseUrlServices + marathonDetailModel.sponsors!.first.video!); await videoController.initialize(); await videoController.play(); await videoController.setVolume(1.0); await videoController.setLooping(false); totalSponsorVideoSeconds = videoController.value.duration.inSeconds; notifyListeners(); } void disposeVideoPlayer() { videoController.dispose(); notifyListeners(); } // 9c47d281-c5b5-4b5d-a90a-08dacb8cbdb6 // MarathonI //************************************************ TIMERS ********************************************************** int sponsorsSecondsCounter = 0; int totalSponsorVideoSeconds = 0; Timer timerForSponsorVideo = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); void startTimerForSponsorVideo() { const Duration oneSec = Duration(seconds: 1); timerForSponsorVideo = Timer.periodic( oneSec, (Timer timer) async { sponsorsSecondsCounter++; if (totalSponsorVideoSeconds == 0) { timer.cancel(); notifyListeners(); return; } else { totalSponsorVideoSeconds--; } notifyListeners(); }, ); } int totalSecondsToWaitForMarathon = 30; set updateTotalSecondsToWaitForMarathon(int value) { totalSecondsToWaitForMarathon = value; notifyListeners(); } Timer timerToWaitForMarathon = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); void startTimerToWaitForMarathon() { const Duration oneSec = Duration(seconds: 1); timerToWaitForMarathon = Timer.periodic( oneSec, (Timer timer) async { if (totalSecondsToWaitForMarathon == 0) { timer.cancel(); if (isUserWaiting) { await callNextQuestionApi(); } else { isButtonEnabled = false; } return; } else { totalSecondsToWaitForMarathon--; } notifyListeners(); }, ); } int totalCurrentQuestionTime = 0; int currentGapTime = 0; Timer timerForQuestion = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); int callCountThreshold = 0; void startTimerForQuestion() { const Duration oneSec = Duration(seconds: 1); timerForQuestion = Timer.periodic( oneSec, (Timer timer) async { // This 1 is just to show the color of answer tile for 1 and then update card status if (totalCurrentQuestionTime - currentGapTime == 0) { if (callCountThreshold == 0) { getCorrectAnswerAndUpdateAnswerColor(); } } if (totalCurrentQuestionTime - currentGapTime == -2) { if (callCountThreshold == 0) { updateCardStatusToAnswer(); } // scheduleMicrotask(() async { // if (AppState().getIsDemoMarathon || isUserOutOfGame) { // await callNextQuestionApi(); // } else { // await callSubmitOptionApi().then((bool value) async { // updateIsUserOutOfGame = !value; // await callNextQuestionApi(); // }); // } // }); if (currentQuestionNumber == (AppState().getIsDemoMarathon ? demoMarathonDetailModel.totalQuestions! : marathonDetailModel.totalQuestions!)) { isGettingQualifiers = true; updateQuestionCardStatus(QuestionCardStatus.findingWinner); timer.cancel(); cancelTimer(); notifyListeners(); } totalCurrentQuestionTime--; callCountThreshold = 0; } if (totalCurrentQuestionTime == 0) { gapTimeImage = currentQuestion.gapImage; gapTimeText = currentQuestion.gapText; gapTimeType = currentQuestion.gapType; updateCardData(); return; } else { if (totalCurrentQuestionTime - currentGapTime != -2) { totalCurrentQuestionTime--; callCountThreshold = 0; } } notifyListeners(); }, ); } int totalSecondsToWaitForWinner = 30; Timer timerForWinnerSelection = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); void startTimerForWinnerSelection() { const Duration oneSec = Duration(seconds: 1); timerForWinnerSelection = Timer.periodic( oneSec, (Timer timer) async { if (totalSecondsToWaitForWinner == 1) { timer.cancel(); await callGetSelectedWinnersApi().whenComplete(() => updateQuestionCardStatus(QuestionCardStatus.winnerFound)); return; } else if (totalSecondsToWaitForWinner == 15) { totalSecondsToWaitForWinner--; await callGetQualifiersApi(); } else { totalSecondsToWaitForWinner--; } notifyListeners(); }, ); } //************************************************ FUNCTIONS ********************************************************** void updateLanguageAsPerMarathon(BuildContext context, MarathonDetailModel detailModel) { savedLocale = context.locale; if (detailModel.selectedLanguage == 1) { context.setLocale(const Locale("en", "US")); } else if (detailModel.selectedLanguage == 2) { context.setLocale(const Locale("ar", "SA")); } else if (detailModel.selectedLanguage == 3) { } else { context.setLocale(const Locale("en", "US")); } } Future callSubmitOptionApi() async { return await MarathonApiClient().submitSelectedOption(marathonId: marathonDetailModel.id!, questionId: currentQuestion.id, selectedAnswerId: selectedOptionId); } Future callGetQualifiersApi() async { if (AppState().getIsDemoMarathon) { totalQualifiers = isUserOutOfGame ? 0 : 1; } else { totalQualifiers = await MarathonApiClient().getQualifiers(marathonId: marathonDetailModel.id!); } isGettingQualifiers = false; notifyListeners(); } Future callGetSelectedWinnersApi() async { if (AppState().getIsDemoMarathon) { return; } selectedWinners = await MarathonApiClient().getSelectedWinner(marathonId: marathonDetailModel.id!); if (selectedWinners != null) { selectedWinners!.removeWhere((WinnerModel element) { if (element.employeeId == AppState().memberInformationList!.eMPLOYEENUMBER) { iAmWinner = true; return true; } else { return false; } }); } notifyListeners(); } Future callNextQuestionApi() async { if (currentQuestionNumber < (AppState().getIsDemoMarathon ? demoMarathonDetailModel.totalQuestions! : marathonDetailModel.totalQuestions!)) { if (currentQuestionNumber == 0) { if (!AppState().getIsDemoMarathon) { Utils.showLoading(AppRoutes.navigatorKey.currentContext!); } currentQuestion = AppState().getIsDemoMarathon ? await DemoMarathonRepo().getDemoNextQuestion(currentQuestionNumber: currentQuestionNumber) : (await MarathonApiClient().getNextQuestion(questionId: null, marathonId: marathonDetailModel.id!)); gapTimeImage = currentQuestion.gapImage; gapTimeText = currentQuestion.gapText; gapTimeType = currentQuestion.gapType; startTimerForQuestion(); updateCardData(); if (Utils.isLoading) { Utils.hideLoading(AppRoutes.navigatorKey.currentContext!); } if (!AppState().getIsDemoMarathon) { totalMarathoners = await MarathonApiClient().getMarathonersCount(marathonId: marathonDetailModel.id!); } Navigator.pushReplacementNamed(AppRoutes.navigatorKey.currentContext!, AppRoutes.marathonScreen); } else { currentQuestion = AppState().getIsDemoMarathon ? await DemoMarathonRepo().getDemoNextQuestion(currentQuestionNumber: currentQuestionNumber) : (await MarathonApiClient().getNextQuestion(questionId: currentQuestion.id, marathonId: marathonDetailModel.id!)); } notifyListeners(); } } void updateCardData() { if (currentQuestionNumber > 0) { // swiperController.swipeLeft(); } selectedOptionIndex = null; currentQuestionNumber++; cardContentList.add(const CardContent()); totalCurrentQuestionTime = currentQuestion.questionTime! + currentQuestion.nextQuestGap!; currentGapTime = currentQuestion.nextQuestGap!; totalMarathoners = currentQuestion.remainingParticipantCount!; questionCardStatus = QuestionCardStatus.question; notifyListeners(); } void populateQuestionStatusesList() { answerStatusesList.clear(); if (demoMarathonDetailModel.totalQuestions != null) { for (int i = 0; i < demoMarathonDetailModel.totalQuestions!; i++) { answerStatusesList.add(QuestionCardStatus.question); } notifyListeners(); } if (isUpComingMarathon) { if (marathonDetailModel.totalQuestions != null) { for (int i = 0; i < marathonDetailModel.totalQuestions!; i++) { answerStatusesList.add(QuestionCardStatus.question); } notifyListeners(); } } } void updateAnswerStatusesList(QuestionCardStatus status) { answerStatusesList[currentQuestionNumber - 1] = status; notifyListeners(); } void addItemToList(CardContent value) { cardContentList.add(value); notifyListeners(); } void updateCurrentQuestionOptionStatus({required QuestionsOptionStatus status, required int selectedOptIndex, required int correctOptionIndex}) { for (int i = 0; i < currentQuestion.questionOptions!.length; i++) { currentQuestion.questionOptions![i].optionStatus = QuestionsOptionStatus.unSelected; } if (status == QuestionsOptionStatus.wrong) { currentQuestion.questionOptions![correctOptionIndex].optionStatus = QuestionsOptionStatus.correct; // if person's answer is wrong we have to show him the actual right answer } currentQuestion.questionOptions![selectedOptIndex].optionStatus = status; selectedOptionId = currentQuestion.questionOptions![selectedOptIndex].id; selectedOptionIndex = selectedOptIndex; notifyListeners(); } void updateQuestionCardStatus(QuestionCardStatus status) { if (status == QuestionCardStatus.wrongAnswer || status == QuestionCardStatus.skippedAnswer) { if (AppState().getIsDemoMarathon) { updateIsUserOutOfGame = true; } } questionCardStatus = status; notifyListeners(); } void getCorrectAnswerAndUpdateAnswerColor() { log("correctOptionIndex"); callCountThreshold = 1; int correctOptionIndex = -1; if (demoMarathonDetailModel.displayCorrectAnswer ?? false) { correctOptionIndex = currentQuestion.questionOptions!.indexWhere((QuestionOptions element) => element.isCorrectOption ?? false); log("correctOptionIndex: $correctOptionIndex"); } if (selectedOptionIndex != null) { scheduleMicrotask(() async { if (AppState().getIsDemoMarathon) { if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { updateCurrentQuestionOptionStatus(status: QuestionsOptionStatus.correct, selectedOptIndex: selectedOptionIndex!, correctOptionIndex: correctOptionIndex); } else { updateCurrentQuestionOptionStatus(status: QuestionsOptionStatus.wrong, selectedOptIndex: selectedOptionIndex!, correctOptionIndex: correctOptionIndex); } } else { await callSubmitOptionApi().then((bool value) async { updateIsUserOutOfGame = !value; if (value) { updateCurrentQuestionOptionStatus(status: QuestionsOptionStatus.correct, selectedOptIndex: selectedOptionIndex!, correctOptionIndex: correctOptionIndex); } else { updateCurrentQuestionOptionStatus(status: QuestionsOptionStatus.wrong, selectedOptIndex: selectedOptionIndex!, correctOptionIndex: correctOptionIndex); } }); } }); } else { if (!AppState().getIsDemoMarathon) { scheduleMicrotask(() async { await callSubmitOptionApi().then((bool value) async { updateIsUserOutOfGame = !value; }); }); } } } void updateCardStatusToAnswer() { if (currentQuestionNumber == 0) { return; } callCountThreshold = 1; scheduleMicrotask(() async { await callNextQuestionApi(); }); if (selectedOptionIndex == null) { updateQuestionCardStatus(QuestionCardStatus.skippedAnswer); updateAnswerStatusesList(QuestionCardStatus.skippedAnswer); return; } if (AppState().getIsDemoMarathon) { if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { updateQuestionCardStatus(QuestionCardStatus.correctAnswer); updateAnswerStatusesList(QuestionCardStatus.correctAnswer); } else { updateQuestionCardStatus(QuestionCardStatus.wrongAnswer); updateAnswerStatusesList(QuestionCardStatus.wrongAnswer); } return; } if (!isUserOutOfGame) { updateQuestionCardStatus(QuestionCardStatus.correctAnswer); updateAnswerStatusesList(QuestionCardStatus.correctAnswer); return; } updateQuestionCardStatus(QuestionCardStatus.wrongAnswer); updateAnswerStatusesList(QuestionCardStatus.wrongAnswer); } void resetValues() async { _currentQuestionNumber = 0; iAmWinner = false; cardContentList.clear(); itsMarathonTime = false; timerForWinnerSelection.cancel(); timerForSponsorVideo.cancel(); timerToWaitForMarathon.cancel(); timerForQuestion.cancel(); _isMarathonCompleted = false; isUserOutOfGame = false; isButtonEnabled = false; isUserWaiting = false; canPlayDemo = true; totalCurrentQuestionTime = 0; sponsorsSecondsCounter = 0; totalSponsorVideoSeconds = 0; totalSecondsToWaitForWinner = 30; totalSecondsToWaitForMarathon = 30; AppState().setIsDemoMarathon = false; currentGapTime = 0; currentQuestion = QuestionModel(); if (answerStatusesList.isNotEmpty) { for (int i = 0; i < answerStatusesList.length; i++) { answerStatusesList[i] = QuestionCardStatus.question; } } AppRoutes.navigatorKey.currentContext!.setLocale(savedLocale); notifyListeners(); } void cancelTimer() { timerForQuestion.cancel(); notifyListeners(); } bool checkIfPrivilegedForMarathon() { for (PrivilegeListModel element in AppState().privilegeListModel!) { if (element.serviceName == "Marathon") { if (element.previlege != null) { return element.previlege!; } } } return false; } Future getMarathonDetailsFromApi() async { isLoading = true; notifyListeners(); isPrivilegedWithMarathon = checkIfPrivilegedForMarathon(); demoMarathonDetailModel = await DemoMarathonRepo().getDemoMarathonDetails(); if (isPrivilegedWithMarathon) { marathonDetailModel = await MarathonApiClient().getMarathonDetails(); updateTotalSecondsToWaitForMarathon = marathonDetailModel.marathonBufferTime ?? 30; if (marathonDetailModel.id == null) { isUpComingMarathon = false; isLoading = false; populateQuestionStatusesList(); notifyListeners(); return; } isUpComingMarathon = true; if (DateTime.parse(marathonDetailModel.startTime!).isAfter(DateTime.now())) { itsMarathonTime = false; } populateQuestionStatusesList(); } isLoading = false; notifyListeners(); } Future onJoinMarathonPressed(BuildContext context) async { try { Utils.showLoading(AppRoutes.navigatorKey.currentContext!); MarathonApiClient().joinMarathonAsParticipant().then((MarathonGenericModel? marathonGenericModel) async { Utils.hideLoading(AppRoutes.navigatorKey.currentContext!); if (marathonGenericModel != null && marathonGenericModel.data != null) { isUserWaiting = true; Navigator.pushReplacementNamed(context, AppRoutes.marathonWaitingScreen); } else if (marathonGenericModel!.data == null) { Utils.showToast(marathonGenericModel.message.toString()); } }); } catch (e) { Utils.hideLoading(AppRoutes.navigatorKey.currentContext!); Utils.confirmDialog(context, e.toString()); } } Future onJoinDemoMarathonPressed(BuildContext context) async { AppState().setIsDemoMarathon = true; await callNextQuestionApi(); } bool isTutorialLoading = false; TutorialNotificationModel? tutorial; Future getMarathonTutorial() async { isTutorialLoading = true; notifyListeners(); tutorial = await MarathonApiClient().getMarathonTutorial(); isTutorialLoading = false; notifyListeners(); } }