diff --git a/assets/images/wrong_answer.gif b/assets/images/wrong_answer.gif new file mode 100644 index 0000000..44f1d38 Binary files /dev/null and b/assets/images/wrong_answer.gif differ diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 1c5851a..59c0814 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -500,5 +500,9 @@ "codeExpire": "انتهت صلاحية رمز التحقق", "typeheretoreply": "اكتب هنا للرد", "favorite": "مفضلتي", - "searchfromchat": "البحث من الدردشة" + "searchfromchat": "البحث من الدردشة", + "yourAnswerCorrect": "إجابتك صحيحة", + "youMissedTheQuestion": "فاتك !! أنت خارج اللعبة. لكن يمكنك المتابعة.", + "wrongAnswer": "إجابة خاطئة! أنت خارج اللعبة. لكن يمكنك المتابعة." + } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b4b06f0..b0ab50e 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -500,6 +500,9 @@ "allQuestionsCorrect": "You have answered all questions correct", "typeheretoreply": "Type here to reply", "favorite" : "My Favorites", - "searchfromchat": "Search from chat" + "searchfromchat": "Search from chat", + "yourAnswerCorrect": "Your answer is correct", + "youMissedTheQuestion": "You Missed !! You are out of the game. But you can follow up.", + "wrongAnswer": "Wrong Answer! You are out of the game. But you can follow up." } \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore index 151026b..d032e39 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -31,3 +31,8 @@ Runner/GeneratedPluginRegistrant.* !default.mode2v3 !default.pbxuser !default.perspectivev3 + +ios/Podfile +ios/Runner/Runner.entitlements + + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..304aa00 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,30 @@ + + + + + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.com.cloudsolutions.mohemm + + com.apple.developer.icloud-services + + CloudDocuments + + com.apple.developer.networking.HotspotConfiguration + + com.apple.developer.networking.networkextension + + com.apple.developer.networking.wifi-info + + com.apple.developer.nfc.readersession.formats + + TAG + + com.apple.developer.ubiquity-container-identifiers + + iCloud.com.cloudsolutions.mohemm + + + diff --git a/lib/api/marathon/marathon_api_client.dart b/lib/api/marathon/marathon_api_client.dart new file mode 100644 index 0000000..1ec117d --- /dev/null +++ b/lib/api/marathon/marathon_api_client.dart @@ -0,0 +1,178 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:logger/logger.dart' as L; +import 'package:logging/logging.dart'; +import 'package:mohem_flutter_app/api/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/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/ui/marathon/marathon_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:signalr_netcore/http_connection_options.dart'; +import 'package:signalr_netcore/hub_connection.dart'; +import 'package:signalr_netcore/hub_connection_builder.dart'; + +class MarathonApiClient { + Future getMarathonToken() async { + String employeeUserName = AppState().getUserName ?? ""; + String employeeSession = AppState().postParamsObject?.pSessionId.toString() ?? ""; + + Map jsonObject = {"userName": employeeUserName, "password": employeeSession}; + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonParticipantLoginUrl, jsonObject); + + var json = jsonDecode(response.body); + + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); + + if (marathonModel.statusCode == 200) { + if (marathonModel.data != null && marathonModel.isSuccessful == true) { + AppState().setMarathonToken = marathonModel.data["token"] ?? ""; + print("bearer: ${AppState().getMarathonToken}"); + return marathonModel.data["token"] ?? ""; + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } + + Future getProjectId() async { + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonProjectGetUrl, {}, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); + + if (marathonModel.statusCode == 200) { + if (marathonModel.data != null && marathonModel.isSuccessful == true) { + logger.i("message: ${marathonModel.data[0]["id"]}"); + AppState().setMarathonProjectId = marathonModel.data[0]["id"] ?? ""; + return marathonModel.data[0]["id"] ?? ""; + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } + + Future getMarathonDetails() async { + String payrollString = AppState().postParamsObject?.payrollCodeStr.toString() ?? "CS"; + + Response response = await ApiClient().getJsonForResponse(ApiConsts.marathonUpcomingUrl + payrollString, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + + MarathonGenericModel marathonGenericModel = MarathonGenericModel.fromJson(json); + + MarathonDetailModel marathonDetailModel = MarathonDetailModel.fromJson(marathonGenericModel.data); + + AppState().setMarathonProjectId = marathonDetailModel.id!; + + return marathonDetailModel; + } + + late HubConnection hubConnection; + L.Logger logger = L.Logger(); + + Future buildHubConnection(BuildContext context) async { + HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hubConnection = HubConnectionBuilder() + .withUrl( + ApiConsts.marathonHubConnectionUrl + "?employeeNumber=${AppState().memberInformationList!.eMPLOYEENUMBER ?? ""}&employeeName=${AppState().memberInformationList!.eMPLOYEENAME ?? ""}", + options: httpOptions, + ) + .withAutomaticReconnect( + retryDelays: [2000, 5000, 10000, 20000], + ) + .configureLogging( + Logger("Logging"), + ) + .build(); + hubConnection.onclose( + ({Exception? error}) { + logger.i("onclose"); + }, + ); + hubConnection.onreconnecting( + ({Exception? error}) { + logger.i("onreconnecting"); + }, + ); + hubConnection.onreconnected( + ({String? connectionId}) { + logger.i("onreconnected"); + }, + ); + if (hubConnection.state != HubConnectionState.Connected) { + await hubConnection.start(); + logger.i("Started HubConnection"); + + await hubConnection.invoke( + "AddParticipant", + args: [ + { + "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "", + "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "", + "marathonId": AppState().getMarathonProjectId, + } + ], + ).catchError((e) { + logger.i("Error in AddParticipant: $e"); + }); + + context.read().addItemToList(ApiConsts.dummyQuestion); + + await hubConnection.invoke( + "SendQuestionToParticipant", + args: [ + { + "marathonId": "${AppState().getMarathonProjectId}", + } + ], + ).catchError((e) { + logger.i("Error in SendQuestionToParticipant: $e"); + }); + + try { + hubConnection.on("OnSendQuestionToParticipant", (List? arguments) { + onSendQuestionToParticipant(arguments, context); + }); + } catch (e, s) { + logger.i("Error in OnSendQuestionToParticipant"); + } + + try { + hubConnection.on("OnParticipantJoin", (List? arguments) { + onParticipantJoin(arguments, context); + }); + } catch (e, s) { + logger.i("Error in OnParticipantJoin"); + } + } + } + + Future onSendQuestionToParticipant(List? arguments, BuildContext context) async { + logger.i("onSendQuestionToParticipant arguments: $arguments"); + + if (arguments != null) { + Map data = arguments.first! as Map; + var json = data["data"]; + QuestionModel newQuestion = QuestionModel.fromJson(json); + context.read().onNewQuestionReceived(newQuestion); + } + } + + Future onParticipantJoin(List? arguments, BuildContext context) async { + logger.i("OnParticipantJoin arguments: $arguments"); + context.watch().totalMarathoners++; + } +} diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart index 49f4186..b0620ad 100644 --- a/lib/app_state/app_state.dart +++ b/lib/app_state/app_state.dart @@ -51,6 +51,18 @@ class AppState { String? get getMohemmWifiPassword => _mohemmWifiPassword; + String? _marathonToken ; + + set setMarathonToken(String token) => _marathonToken = token; + + String? get getMarathonToken => _marathonToken; + + String? _projectID ; + + set setMarathonProjectId(String token) => _projectID = token; + + String? get getMarathonProjectId => _projectID; + final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 5.0, mobileType: Platform.isAndroid ? "android" : "ios"); void setPostParamsInitConfig() { diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 4ba4e32..bdeeecd 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,3 +1,5 @@ +import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; + class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server diff --git a/lib/classes/date_uitl.dart b/lib/classes/date_uitl.dart index f8d1c02..f37fbea 100644 --- a/lib/classes/date_uitl.dart +++ b/lib/classes/date_uitl.dart @@ -3,6 +3,26 @@ import 'package:intl/intl.dart'; class DateUtil { /// convert String To Date function /// [date] String we want to convert + /// + /// + + + static DateTime convertStringToDateMarathon(String date) { + // /Date(1585774800000+0300)/ + if (date != null) { + const start = "/Date("; + const end = "+0300)"; + int startIndex = date.indexOf(start); + int endIndex = date.indexOf(end, startIndex + start.length); + return DateTime.fromMillisecondsSinceEpoch( + int.parse( + date.substring(startIndex + start.length, endIndex), + ), + ); + } else + return DateTime.now(); + } + static DateTime convertStringToDate(String date) { // /Date(1585774800000+0300)/ if (date != null) { @@ -55,8 +75,9 @@ class DateUtil { } return DateTime.now(); - } else + } else { return DateTime.now(); + } } static String convertDateToString(DateTime date) { @@ -94,7 +115,7 @@ class DateUtil { } static String convertDateMSToJsonDate(utc) { - var dt = new DateTime.fromMicrosecondsSinceEpoch(utc); + var dt = DateTime.fromMicrosecondsSinceEpoch(utc); return "/Date(" + (dt.millisecondsSinceEpoch * 1000).toString() + '+0300' + ")/"; } @@ -416,7 +437,7 @@ class DateUtil { /// get data formatted like 10:30 according to lang static String formatDateToTimeLang(DateTime date, bool isArabic) { - return DateFormat('HH:mm', isArabic ? "ar_SA" : "en_US").format(date); + return DateFormat('HH:mm a', isArabic ? "ar_SA" : "en_US").format(date); } /// get data formatted like 26/4/2020 10:30 diff --git a/lib/classes/decorations_helper.dart b/lib/classes/decorations_helper.dart index 77ea4a4..b313673 100644 --- a/lib/classes/decorations_helper.dart +++ b/lib/classes/decorations_helper.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; class MyDecorations { static Decoration shadowDecoration = BoxDecoration( @@ -22,4 +23,18 @@ class MyDecorations { ); return answerContainerDecoration; } + + static Decoration getAnswersContainerColor(QuestionsOptionStatus questionsOptionStatus) { + switch (questionsOptionStatus) { + case QuestionsOptionStatus.correct: + return getContainersDecoration(MyColors.greenColor); + case QuestionsOptionStatus.wrong: + return getContainersDecoration(MyColors.redColor); + + case QuestionsOptionStatus.selected: + return getContainersDecoration(MyColors.yellowColorII); + case QuestionsOptionStatus.unSelected: + return getContainersDecoration(MyColors.greyF7Color); + } + } } diff --git a/lib/classes/lottie_consts.dart b/lib/classes/lottie_consts.dart index 1b714a4..24dc423 100644 --- a/lib/classes/lottie_consts.dart +++ b/lib/classes/lottie_consts.dart @@ -4,4 +4,6 @@ class MyLottieConsts { static const String celebrate2Lottie = "assets/lottie/celebrate2.json"; static const String winnerLottie = "assets/lottie/winner3.json"; static const String allQuestions = "assets/lottie/all_questions.json"; + static const String wrongAnswerGif = "assets/images/wrong_answer.gif"; + } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f5d4960..2f7efcc 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -487,5 +487,8 @@ abstract class LocaleKeys { static const typeheretoreply = 'typeheretoreply'; static const favorite = 'favorite'; static const searchfromchat = 'searchfromchat'; + static const yourAnswerCorrect = 'yourAnswerCorrect'; + static const youMissedTheQuestion = 'youMissedTheQuestion'; + static const wrongAnswer = 'wrongAnswer'; } diff --git a/lib/models/marathon/marathon_generic_model.dart b/lib/models/marathon/marathon_generic_model.dart new file mode 100644 index 0000000..d0a0d52 --- /dev/null +++ b/lib/models/marathon/marathon_generic_model.dart @@ -0,0 +1,31 @@ +class MarathonGenericModel { + MarathonGenericModel({ + this.data, + this.isSuccessful, + this.message, + this.statusCode, + this.errors, + }); + + dynamic data; + bool? isSuccessful; + String? message; + int? statusCode; + dynamic errors; + + factory MarathonGenericModel.fromJson(Map json) => MarathonGenericModel( + data: json["data"], + isSuccessful: json["isSuccessful"], + message: json["message"], + statusCode: json["statusCode"], + errors: json["errors"], + ); + + Map toJson() => { + "data": data, + "isSuccessful": isSuccessful, + "message": message, + "statusCode": statusCode, + "errors": errors, + }; +} diff --git a/lib/models/marathon/marathon_model.dart b/lib/models/marathon/marathon_model.dart new file mode 100644 index 0000000..b32530c --- /dev/null +++ b/lib/models/marathon/marathon_model.dart @@ -0,0 +1,256 @@ +class MarathonDetailModel { + String? id; + String? titleEn; + String? titleAr; + String? descEn; + String? descAr; + int? winDeciderTime; + int? winnersCount; + int? questGapTime; + String? startTime; + String? endTime; + int? marathoneStatusId; + String? scheduleTime; + int? selectedLanguage; + Projects? projects; + List? sponsors; + List? questions; + int? totalQuestions; + + MarathonDetailModel( + {id, + titleEn, + titleAr, + descEn, + descAr, + winDeciderTime, + winnersCount, + questGapTime, + startTime, + endTime, + marathoneStatusId, + scheduleTime, + selectedLanguage, + projects, + sponsors, + questions, + totalQuestions}); + + MarathonDetailModel.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + descEn = json['descEn']; + descAr = json['descAr']; + winDeciderTime = json['winDeciderTime']; + winnersCount = json['winnersCount']; + questGapTime = json['questGapTime']; + startTime = json['startTime']; + endTime = json['endTime']; + marathoneStatusId = json['marathoneStatusId']; + scheduleTime = json['scheduleTime']; + selectedLanguage = json['selectedLanguage']; + projects = json['projects'] != null + ? Projects.fromJson(json['projects']) + : null; + if (json['sponsors'] != null) { + sponsors = []; + json['sponsors'].forEach((v) { + sponsors!.add( Sponsors.fromJson(v)); + }); + } + if (json['questions'] != null) { + questions = []; + json['questions'].forEach((v) { + questions!.add( Questions.fromJson(v)); + }); + } + totalQuestions = json["totalQuestions"]; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['descEn'] = descEn; + data['descAr'] = descAr; + data['winDeciderTime'] = winDeciderTime; + data['winnersCount'] = winnersCount; + data['questGapTime'] = questGapTime; + data['startTime'] = startTime; + data['endTime'] = endTime; + data['marathoneStatusId'] = marathoneStatusId; + data['scheduleTime'] = scheduleTime; + data['selectedLanguage'] = selectedLanguage; + if (projects != null) { + data['projects'] = projects!.toJson(); + } + if (sponsors != null) { + data['sponsors'] = sponsors!.map((v) => v.toJson()).toList(); + } + if (questions != null) { + data['questions'] = questions!.map((v) => v.toJson()).toList(); + } + data['totalQuestions'] = totalQuestions; + + return data; + } +} + +class Projects { + String? id; + String? nameEn; + String? nameAr; + String? projectCode; + + Projects({id, nameEn, nameAr, projectCode}); + + Projects.fromJson(Map json) { + id = json['id']; + nameEn = json['nameEn']; + nameAr = json['nameAr']; + projectCode = json['projectCode']; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['nameEn'] = nameEn; + data['nameAr'] = nameAr; + data['projectCode'] = projectCode; + return data; + } +} + +class Sponsors { + String? id; + String? nameEn; + Null? nameAr; + String? image; + Null? video; + Null? logo; + List? sponsorPrizes; + + Sponsors( + {id, + nameEn, + nameAr, + image, + video, + logo, + sponsorPrizes}); + + Sponsors.fromJson(Map json) { + id = json['id']; + nameEn = json['nameEn']; + nameAr = json['nameAr']; + image = json['image']; + video = json['video']; + logo = json['logo']; + if (json['sponsorPrizes'] != null) { + sponsorPrizes = []; + json['sponsorPrizes'].forEach((v) { + sponsorPrizes!.add( SponsorPrizes.fromJson(v)); + }); + } + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['nameEn'] = nameEn; + data['nameAr'] = nameAr; + data['image'] = image; + data['video'] = video; + data['logo'] = logo; + if (sponsorPrizes != null) { + data['sponsorPrizes'] = + sponsorPrizes!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class SponsorPrizes { + String? id; + String? marathonPrizeEn; + String? marathonPrizeAr; + + SponsorPrizes({id, marathonPrizeEn, marathonPrizeAr}); + + SponsorPrizes.fromJson(Map json) { + id = json['id']; + marathonPrizeEn = json['marathonPrizeEn']; + marathonPrizeAr = json['marathonPrizeAr']; + } + + Map toJson() { + Map data = new Map(); + data['id'] = id; + data['marathonPrizeEn'] = marathonPrizeEn; + data['marathonPrizeAr'] = marathonPrizeAr; + return data; + } +} + +class Questions { + String? id; + String? titleEn; + String? titleAr; + String? marathonId; + int? questionTypeId; + int? questionTime; + int? nextQuestGap; + int? gapType; + String? gapValue; + String? gapImage; + int? questOptionsLimit; + List? questionOptions; + + Questions( + {id, + titleEn, + titleAr, + marathonId, + questionTypeId, + questionTime, + nextQuestGap, + gapType, + gapValue, + gapImage, + questOptionsLimit, + questionOptions}); + + Questions.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + marathonId = json['marathonId']; + questionTypeId = json['questionTypeId']; + questionTime = json['questionTime']; + nextQuestGap = json['nextQuestGap']; + gapType = json['gapType']; + gapValue = json['gapValue']; + gapImage = json['gapImage']; + questOptionsLimit = json['questOptionsLimit']; + questionOptions = json['questionOptions']; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['marathonId'] = marathonId; + data['questionTypeId'] = questionTypeId; + data['questionTime'] = questionTime; + data['nextQuestGap'] = nextQuestGap; + data['gapType'] = gapType; + data['gapValue'] = gapValue; + data['gapImage'] = gapImage; + data['questOptionsLimit'] = questOptionsLimit; + data['questionOptions'] = questionOptions; + return data; + } +} diff --git a/lib/models/marathon/question_model.dart b/lib/models/marathon/question_model.dart new file mode 100644 index 0000000..0bb42cd --- /dev/null +++ b/lib/models/marathon/question_model.dart @@ -0,0 +1,117 @@ +enum QuestionsOptionStatus { correct, wrong, selected, unSelected } + +enum QuestionCardStatus { question, wrongAnswer, correctAnswer, skippedAnswer, completed, findingWinner, winnerFound } + +class QuestionModel { + String? id; + String? titleEn; + String? titleAr; + String? marathonId; + int? questionTypeId; + int? questionTime; + int? nextQuestGap; + int? gapType; + String? gapText; + String? gapImage; + int? questOptionsLimit; + List? questionOptions; + + QuestionModel({ + String? id, + String? titleEn, + String? titleAr, + String? marathonId, + int? questionTypeId, + int? questionTime, + int? nextQuestGap, + int? gapType, + String? gapText, + String? gapImage, + int? questOptionsLimit, + List? questionOptions, + }); + + QuestionModel.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + marathonId = json['marathonId']; + questionTypeId = json['questionTypeId']; + questionTime = json['questionTime']; + nextQuestGap = json['nextQuestGap']; + gapType = json['gapType']; + gapText = json['gapText']; + gapImage = json['gapImage']; + questOptionsLimit = json['questOptionsLimit']; + if (json['questionOptions'] != null) { + questionOptions = []; + json['questionOptions'].forEach((v) { + questionOptions!.add(QuestionOptions.fromJson(v)); + }); + } + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['marathonId'] = marathonId; + data['questionTypeId'] = questionTypeId; + data['questionTime'] = questionTime; + data['nextQuestGap'] = nextQuestGap; + data['gapType'] = gapType; + data['gapText'] = gapText; + data['gapImage'] = gapImage; + data['questOptionsLimit'] = questOptionsLimit; + if (questionOptions != null) { + data['questionOptions'] = questionOptions!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class QuestionOptions { + String? id; + String? titleEn; + String? titleAr; + String? questionId; + int? sequence; + String? image; + bool? isCorrectOption; + QuestionsOptionStatus? optionStatus; + + QuestionOptions({ + String? id, + String? titleEn, + String? titleAr, + String? questionId, + int? sequence, + String? image, + bool? isCorrectOption, + QuestionsOptionStatus? optionStatus, + }); + + QuestionOptions.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + questionId = json['questionId']; + sequence = json['sequence']; + image = json['image']; + isCorrectOption = json['isCorrectOption']; + optionStatus = QuestionsOptionStatus.unSelected; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['questionId'] = questionId; + data['sequence'] = sequence; + data['image'] = image; + data['isCorrectOption'] = isCorrectOption; + return data; + } +} diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 87c6b31..7cf28cf 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -19,6 +19,7 @@ import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/landing/widget/app_drawer.dart'; import 'package:mohem_flutter_app/ui/landing/widget/menus_widget.dart'; import 'package:mohem_flutter_app/ui/landing/widget/services_widget.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart'; @@ -41,6 +42,7 @@ class DashboardScreen extends StatefulWidget { class _DashboardScreenState extends State { late DashboardProviderModel data; + late MarathonProvider marathonProvider; final GlobalKey _scaffoldState = GlobalKey(); final RefreshController _refreshController = RefreshController(initialRefresh: false); @@ -52,6 +54,7 @@ class _DashboardScreenState extends State { super.initState(); scheduleMicrotask(() { data = Provider.of(context, listen: false); + marathonProvider = Provider.of(context, listen: false); _bHubCon(); _onRefresh(); }); @@ -88,6 +91,7 @@ class _DashboardScreenState extends State { data.fetchLeaveTicketBalance(context, DateTime.now()); data.fetchMenuEntries(); data.getCategoryOffersListAPI(context); + marathonProvider.getMarathonDetailsFromApi(); data.fetchChatCounts(); _refreshController.refreshCompleted(); } @@ -317,7 +321,7 @@ class _DashboardScreenState extends State { ), ], ).paddingOnly(left: 21, right: 21, top: 7), - const MarathonBanner().paddingAll(21), + context.watch().isLoading ? MarathonBannerShimmer().paddingAll(20) : MarathonBanner().paddingAll(20), ServicesWidget(), // 8.height, Container( @@ -363,7 +367,7 @@ class _DashboardScreenState extends State { ], ).paddingOnly(left: 21, right: 21), Consumer( - builder: (context, model, child) { + builder: (BuildContext context, DashboardProviderModel model, Widget? child) { return SizedBox( height: 103 + 33, child: ListView.separated( diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 68f0c41..e6b4838 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -143,8 +143,8 @@ class _LoginScreenState extends State { isAppOpenBySystem = (ModalRoute.of(context)!.settings.arguments ?? true) as bool; if (!kReleaseMode) { // username.text = "15444"; // Maha User - // username.text = "15153"; // Tamer User - // password.text = "Abcd@12345"; + username.text = "15153"; // Tamer User + password.text = "Abcd@1234"; // username.text = "206535"; // Hashim User // password.text = "Namira786"; diff --git a/lib/ui/marathon/marathon_intro_screen.dart b/lib/ui/marathon/marathon_intro_screen.dart index a6ec296..d85a82f 100644 --- a/lib/ui/marathon/marathon_intro_screen.dart +++ b/lib/ui/marathon/marathon_intro_screen.dart @@ -1,9 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/date_uitl.dart'; import 'package:mohem_flutter_app/classes/decorations_helper.dart'; import 'package:mohem_flutter_app/classes/lottie_consts.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; @@ -15,8 +18,6 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:provider/provider.dart'; -final int dummyEndTime = DateTime.now().millisecondsSinceEpoch + 1000 * 30; - class MarathonIntroScreen extends StatelessWidget { const MarathonIntroScreen({Key? key}) : super(key: key); @@ -32,7 +33,7 @@ class MarathonIntroScreen extends StatelessWidget { children: [ MarathonDetailsCard(provider: provider), 10.height, - MarathonTimerCard(provider: provider, timeToMarathon: dummyEndTime), + MarathonTimerCard(provider: provider, timeToMarathon: DateTime.parse(provider.marathonDetailModel.startTime!).millisecondsSinceEpoch,), ], ).expanded, 1.divider, @@ -58,36 +59,54 @@ class MarathonDetailsCard extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.contestTopicAbout.tr().toText16(color: MyColors.grey57Color), - "Saudi Arabia".toText20(color: MyColors.textMixColor), - "Nam suscipit turpis in pharetra euismsdef. Duis rutrum at nulla id aliquam".toText14(color: MyColors.grey57Color, weight: FontWeight.w500), - if (provider.itsMarathonTime) ...[ - 5.height, - Row( - children: [ - LocaleKeys.prize.tr().toText16(color: MyColors.grey57Color), - " LED 55\" Android TV".toText16(color: MyColors.greenColor, isBold: true), - ], - ), - Row( - children: [ - LocaleKeys.sponsoredBy.tr().toText16(color: MyColors.grey57Color), - " Extra".toText16(color: MyColors.darkTextColor), - ], - ), - 10.height, - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - "assets/images/logos/main_mohemm_logo.png", - height: 40, - fit: BoxFit.fill, - width: 150, - ) - ], - ), - ] + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.contestTopicAbout.tr().toText16(color: MyColors.grey77Color), + "${AppState().isArabic(context) ? provider.marathonDetailModel.titleAr : provider.marathonDetailModel.titleEn}".toText20(color: MyColors.textMixColor, isBold: true), + Row( + children: [ + Flexible( + child: "${AppState().isArabic(context) ? provider.marathonDetailModel.descAr : provider.marathonDetailModel.descEn}".toText14(color: MyColors.grey77Color), + ) + ], + ), + if (provider.itsMarathonTime && provider.marathonDetailModel.sponsors != null) ...[ + 5.height, + provider.marathonDetailModel.sponsors?.first.sponsorPrizes != null + ? Row( + children: [ + "${LocaleKeys.prize.tr()} ".toText16(color: MyColors.grey77Color, isBold: true), + "${AppState().isArabic(context) ? provider.marathonDetailModel.sponsors?.first.sponsorPrizes?.first.marathonPrizeAr : provider.marathonDetailModel.sponsors?.first.sponsorPrizes?.first.marathonPrizeAr}" + .toText16(color: MyColors.greenColor, isBold: true), + ], + ) + : const SizedBox(), + Row( + children: [ + "${LocaleKeys.sponsoredBy.tr()} ".toText16(color: MyColors.grey77Color), + "${AppState().isArabic(context) ? provider.marathonDetailModel.sponsors?.first.nameAr : provider.marathonDetailModel.sponsors?.first.nameEn}" + .toText16(color: MyColors.darkTextColor, isBold: true), + ], + ), + 10.height, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.network( + provider.marathonDetailModel.sponsors!.first.image!, + height: 40, + width: 150, + fit: BoxFit.fill, + errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + return const Center(); + }, + ) + ], + ), + ] + ], + ), ], ), ); @@ -114,14 +133,14 @@ class MarathonTimerCard extends StatelessWidget { children: [ Row( children: [ - LocaleKeys.gameDate.tr().toText16(color: MyColors.grey57Color), - " 10 Oct, 2022".toText16(color: MyColors.darkTextColor), + "${LocaleKeys.gameDate.tr()} ".toText16(color: MyColors.grey77Color), + DateUtil.getMonthDayYearDateFormatted(DateTime.parse(provider.marathonDetailModel.startTime!)).toText16(color: MyColors.darkTextColor, isBold: true), ], ), Row( children: [ - LocaleKeys.gameTime.tr().toText16(color: MyColors.grey57Color), - " 3:00 pm".toText16(color: MyColors.darkTextColor), + "${LocaleKeys.gameTime.tr()} ".toText16(color: MyColors.grey77Color), + DateUtil.formatDateToTimeLang(DateTime.parse(provider.marathonDetailModel.startTime!), AppState().isArabic(context)).toText16(color: MyColors.darkTextColor, isBold: true), ], ), Lottie.asset(MyLottieConsts.hourGlassLottie, height: 200), @@ -167,10 +186,21 @@ class MarathonFooter extends StatelessWidget { @override Widget build(BuildContext context) { - return provider.itsMarathonTime + return !provider.itsMarathonTime ? DefaultButton( LocaleKeys.joinMarathon.tr(), - () => Navigator.pushNamed(context, AppRoutes.marathonScreen), + () async { + Utils.showLoading(context); + try { + provider.resetValues(); + await provider.connectSignalrAndJoinMarathon(context); + } catch (e, s) { + Utils.confirmDialog(context, e.toString()); + print(s); + } + Utils.hideLoading(context); + Navigator.pushNamed(context, AppRoutes.marathonScreen); + }, ).insideContainer : Container( color: Colors.white, @@ -180,7 +210,9 @@ class MarathonFooter extends StatelessWidget { buildNoteForDemo(), DefaultButton( LocaleKeys.joinDemoMarathon.tr(), - () {}, + () { + provider.connectSignalrAndJoinMarathon(context); + }, color: MyColors.yellowColorII, ).insideContainer, ], diff --git a/lib/ui/marathon/marathon_provider.dart b/lib/ui/marathon/marathon_provider.dart index 46fa74d..b629b36 100644 --- a/lib/ui/marathon/marathon_provider.dart +++ b/lib/ui/marathon/marathon_provider.dart @@ -3,12 +3,64 @@ import 'dart:async'; import 'package:appinio_swiper/appinio_swiper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/api/marathon/marathon_api_client.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/ui/marathon/widgets/question_card.dart'; class MarathonProvider extends ChangeNotifier { final AppinioSwiperController swiperController = AppinioSwiperController(); + MarathonDetailModel marathonDetailModel = MarathonDetailModel(); + List cardContentList = []; + QuestionModel currentQuestion = QuestionModel(); + + QuestionCardStatus questionCardStatus = QuestionCardStatus.question; + + int? selectedOptionIndex; + int currentQuestionTime = 0; + + void onNewQuestionReceived(QuestionModel newQuestion) { + if (currentQuestionNumber > 0) { + swipeCardLeft(); + } + selectedOptionIndex = null; + currentQuestionNumber++; + currentQuestion = newQuestion; + cardContentList.add(const CardContent()); + currentQuestionTime = newQuestion.questionTime!; + questionCardStatus = QuestionCardStatus.question; + notifyListeners(); + } + + void addItemToList(CardContent value) { + cardContentList.add(value); + notifyListeners(); + } + + void updateCurrentQuestionOptionStatus(QuestionsOptionStatus status, int index) { + for (int i = 0; i < currentQuestion.questionOptions!.length; i++) { + currentQuestion.questionOptions![i].optionStatus = QuestionsOptionStatus.unSelected; + } + currentQuestion.questionOptions![index].optionStatus = status; + selectedOptionIndex = index; + notifyListeners(); + } + + void updateQuestionCardStatus(QuestionCardStatus status) { + questionCardStatus = status; + notifyListeners(); + } + + bool _isLoading = false; + + bool get isLoading => _isLoading; + + set isLoading(bool value) { + _isLoading = value; + notifyListeners(); + } + bool _itsMarathonTime = false; bool get itsMarathonTime => _itsMarathonTime; @@ -27,14 +79,7 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } - void swipeCardLeft() { - currentQuestionNumber = currentQuestionNumber + 1; - swiperController.swipeLeft(); - notifyListeners(); - } - - int _currentQuestionNumber = 1; - final int totalQuestions = 10; + int _currentQuestionNumber = 0; int get currentQuestionNumber => _currentQuestionNumber; @@ -43,44 +88,73 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } - void resetAll() { - isSelectedOptions[0] = false; - isSelectedOptions[1] = false; - isSelectedOptions[2] = false; - isSelectedOptions[3] = false; + int _totalMarathoners = 23; + + int get totalMarathoners => _totalMarathoners; + + set totalMarathoners(int value) { + _totalMarathoners = value; + notifyListeners(); + } + + void swipeCardLeft() { + swiperController.swipeLeft(); + notifyListeners(); + } + + void getCorrectAnswerAndUpdateAnswerColor() { + if (selectedOptionIndex != null) { + if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { + updateCurrentQuestionOptionStatus(QuestionsOptionStatus.correct, selectedOptionIndex!); + } else { + updateCurrentQuestionOptionStatus(QuestionsOptionStatus.wrong, selectedOptionIndex!); + } + } + } + + void updateCardStatusToAnswer() { + if (currentQuestionNumber == 0) { + return; + } + + if (selectedOptionIndex != null) { + if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { + updateQuestionCardStatus(QuestionCardStatus.correctAnswer); + } else { + updateQuestionCardStatus(QuestionCardStatus.wrongAnswer); + } + } else { + updateQuestionCardStatus(QuestionCardStatus.skippedAnswer); + } } Timer timerU = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); - int start = 8; void startTimer(BuildContext context) { - start = 8; const Duration oneSec = Duration(seconds: 1); timerU = Timer.periodic( oneSec, (Timer timer) async { - if (start == 0) { - if (currentQuestionNumber == totalQuestions) { - timer.cancel(); - cancelTimer(); - isMarathonCompleted = true; - await Future.delayed(const Duration(seconds: 3)).whenComplete( - () => Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ), - ); - - resetValues(); - - return; - } - resetAll(); - timer.cancel(); - cancelTimer(); - swipeCardLeft(); + if (currentQuestionTime == 2) { + getCorrectAnswerAndUpdateAnswerColor(); + } + if (currentQuestionTime == 0) { + updateCardStatusToAnswer(); + // if (currentQuestionNumber == 9) { + // timer.cancel(); + // cancelTimer(); + // isMarathonCompleted = true; + // await Future.delayed(const Duration(seconds: 2)).whenComplete( + // () => Navigator.pushReplacementNamed(context, AppRoutes.marathonWinnerSelection), + // ); + // + // resetValues(); + // + // return; + // } + // timer.cancel(); } else { - start--; + currentQuestionTime--; } notifyListeners(); }, @@ -88,9 +162,12 @@ class MarathonProvider extends ChangeNotifier { } void resetValues() { + _currentQuestionNumber = 0; + cardContentList.clear(); timerU.cancel(); _isMarathonCompleted = false; - _currentQuestionNumber = 1; + currentQuestionTime = 0; + currentQuestion = QuestionModel(); notifyListeners(); } @@ -98,4 +175,18 @@ class MarathonProvider extends ChangeNotifier { timerU.cancel(); notifyListeners(); } + + Future getMarathonDetailsFromApi() async { + isLoading = true; + notifyListeners(); + await MarathonApiClient().getMarathonToken().whenComplete(() async { + marathonDetailModel = await MarathonApiClient().getMarathonDetails(); + isLoading = false; + notifyListeners(); + }); + } + + Future connectSignalrAndJoinMarathon(BuildContext context) async { + await MarathonApiClient().buildHubConnection(context); + } } diff --git a/lib/ui/marathon/marathon_screen.dart b/lib/ui/marathon/marathon_screen.dart index 5f3e0d8..79d61fe 100644 --- a/lib/ui/marathon/marathon_screen.dart +++ b/lib/ui/marathon/marathon_screen.dart @@ -6,7 +6,6 @@ import 'package:lottie/lottie.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/decorations_helper.dart'; import 'package:mohem_flutter_app/classes/lottie_consts.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; @@ -14,6 +13,7 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/custom_status_widget.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/question_card_builder.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:provider/provider.dart'; @@ -23,48 +23,52 @@ class MarathonScreen extends StatelessWidget { @override Widget build(BuildContext context) { MarathonProvider provider = context.watch(); - return Scaffold( - appBar: AppBarWidget(context, title: LocaleKeys.brainMarathon.tr()), - body: SingleChildScrollView( - child: Column( - children: [ - 20.height, - MarathonProgressContainer(provider: provider).paddingOnly(left: 21, right: 21), - if (provider.isMarathonCompleted) - InkWell( - onTap: () { - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - }, - child: CustomStatusWidget( - asset: Lottie.asset( - MyLottieConsts.allQuestions, - height: 200, - ), - title: Text( - LocaleKeys.congrats.tr(), - style: const TextStyle( - height: 23 / 24, - color: MyColors.greenColor, - fontSize: 27, - letterSpacing: -1, - fontWeight: FontWeight.w600, - ), - ), - subTitle: Text( - LocaleKeys.allQuestionsCorrect.tr(), - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: -1.08), - ), - ).paddingOnly(top: 12, left: 21, right: 21), - ) - else - QuestionCard(provider: provider).paddingOnly(top: 12, left: 21, right: 21), - ], + return WillPopScope( + child: Scaffold( + appBar: AppBarWidget(context, title: LocaleKeys.brainMarathon.tr()), + body: SingleChildScrollView( + child: Column( + children: [ + 20.height, + MarathonProgressContainer(provider: provider).paddingOnly(left: 21, right: 21), + QuestionCardBuilder( + onQuestion: (BuildContext context) => QuestionCard(provider: provider), + onCompleted: (BuildContext context) => CustomStatusWidget( + asset: Lottie.asset(MyLottieConsts.allQuestions, height: 200), + title: LocaleKeys.congrats.tr().toText22(color: MyColors.greenColor), + subTitle: LocaleKeys.allQuestionsCorrect.toText18(color: MyColors.darkTextColor), + ), + onCorrectAnswer: (BuildContext context) => CustomStatusWidget( + asset: Lottie.asset(MyLottieConsts.allQuestions, height: 200), + title: LocaleKeys.congrats.tr().toText22(color: MyColors.greenColor), + subTitle: LocaleKeys.yourAnswerCorrect.toText18(color: MyColors.darkTextColor), + ), + onWinner: (BuildContext context) => QuestionCard(provider: provider), + onWrongAnswer: (BuildContext context) => CustomStatusWidget( + asset: Image.asset(MyLottieConsts.wrongAnswerGif, height: 200), + title: const Text(""), + subTitle: LocaleKeys.wrongAnswer.tr().toText18(color: MyColors.darkTextColor), + ), + onSkippedAnswer: (BuildContext context) => CustomStatusWidget( + asset: Image.asset(MyLottieConsts.wrongAnswerGif, height: 200), + title: const Text(""), + subTitle: LocaleKeys.youMissedTheQuestion.tr().toText18(color: MyColors.darkTextColor), + ), + questionCardStatus: provider.questionCardStatus, + onFindingWinner: (BuildContext context) => CustomStatusWidget( + asset: Lottie.asset(MyLottieConsts.winnerLottie, height: 168), + title: LocaleKeys.fingersCrossed.tr().toText22(color: MyColors.greenColor), + subTitle: LocaleKeys.winnerSelectedRandomly.tr().toText18(color: MyColors.darkTextColor), + ), + ).paddingOnly(top: 12, left: 21, right: 21), + ], + ), ), ), + onWillPop: () { + provider.resetValues(); + return Future.value(true); + }, ); } } @@ -89,7 +93,6 @@ class _MarathonProgressContainerState extends State { @override void dispose() { - widget.provider.cancelTimer(); super.dispose(); } @@ -108,10 +111,12 @@ class _MarathonProgressContainerState extends State { Container( decoration: BoxDecoration(color: MyColors.greenColor, borderRadius: BorderRadius.circular(5)), padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8), - child: "${widget.provider.currentQuestionNumber.toString()} / ${widget.provider.totalQuestions.toString()} ${LocaleKeys.question.tr()}".toText12(color: MyColors.white), + child: "${widget.provider.currentQuestionNumber.toString()} / ${widget.provider.marathonDetailModel.totalQuestions.toString()} ${LocaleKeys.question.tr()}" + .toText12(color: MyColors.white), ), - "23 ${LocaleKeys.marathoners.tr()}".toText14(), - "00:${widget.provider.start < 10 ? "0${widget.provider.start}" : widget.provider.start}".toText18(), + "${widget.provider.totalMarathoners} ${LocaleKeys.marathoners.tr()}".toText14(), + "00:${widget.provider.currentQuestionTime < 10 ? "0${widget.provider.currentQuestionTime}" : widget.provider.currentQuestionTime}" + .toText18(color: widget.provider.currentQuestionTime < 5 ? MyColors.redColor : MyColors.black), ], ), 12.height, @@ -119,7 +124,7 @@ class _MarathonProgressContainerState extends State { 8.height, Row( children: [ - "${widget.provider.currentQuestionNumber * 10}% ${LocaleKeys.completed.tr()}".toText14(), + "${((widget.provider.currentQuestionNumber / widget.provider.marathonDetailModel.totalQuestions!) * 100).toInt()}% ${LocaleKeys.completed.tr()}".toText14(), ], ), ], @@ -161,3 +166,33 @@ class _MarathonProgressContainerState extends State { ); } } + +// InkWell( +// onTap: () { +// Navigator.pushReplacementNamed( +// context, +// AppRoutes.marathonWinnerSelection, +// ); +// }, +// child: CustomStatusWidget( +// asset: Lottie.asset( +// MyLottieConsts.allQuestions, +// height: 200, +// ), +// title: Text( +// LocaleKeys.congrats.tr(), +// style: const TextStyle( +// height: 23 / 24, +// color: MyColors.greenColor, +// fontSize: 27, +// letterSpacing: -1, +// fontWeight: FontWeight.w600, +// ), +// ), +// subTitle: Text( +// LocaleKeys.allQuestionsCorrect.tr(), +// textAlign: TextAlign.center, +// style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: -1.08), +// ), +// ).paddingOnly(top: 12, left: 21, right: 21), +// ) diff --git a/lib/ui/marathon/marathon_winner_selection.dart b/lib/ui/marathon/marathon_winner_selection.dart index a5f4e8a..1f56801 100644 --- a/lib/ui/marathon/marathon_winner_selection.dart +++ b/lib/ui/marathon/marathon_winner_selection.dart @@ -108,14 +108,14 @@ class _QualifiersContainerState extends State { @override void initState() { scheduleMicrotask(() { - widget.provider.startTimer(context); + // widget.provider.startTimer(context); }); super.initState(); } @override void dispose() { - widget.provider.cancelTimer(); + // widget.provider.cancelTimer(); super.dispose(); } @@ -132,7 +132,7 @@ class _QualifiersContainerState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.winnerSelection.tr().toText21(color: MyColors.grey3AColor), - "00:${widget.provider.start < 10 ? "0${widget.provider.start}" : widget.provider.start}".toText18(color: MyColors.redColor), + // "00:${widget.provider.start < 10 ? "0${widget.provider.start}" : widget.provider.start}".toText18(color: MyColors.redColor), ], ), 10.height, diff --git a/lib/ui/marathon/widgets/countdown_timer.dart b/lib/ui/marathon/widgets/countdown_timer.dart index 4296bc5..cfa3b1b 100644 --- a/lib/ui/marathon/widgets/countdown_timer.dart +++ b/lib/ui/marathon/widgets/countdown_timer.dart @@ -30,7 +30,7 @@ class BuildCountdownTimer extends StatelessWidget { ); final TextStyle styleDigitHome = const TextStyle( - height: 23 / 27, + height: 22 / 27, color: MyColors.white, fontStyle: FontStyle.italic, letterSpacing: -1.44, diff --git a/lib/ui/marathon/widgets/custom_status_widget.dart b/lib/ui/marathon/widgets/custom_status_widget.dart index 4fde6cc..8287bb4 100644 --- a/lib/ui/marathon/widgets/custom_status_widget.dart +++ b/lib/ui/marathon/widgets/custom_status_widget.dart @@ -18,6 +18,7 @@ class CustomStatusWidget extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, + height: 440, decoration: MyDecorations.shadowDecoration, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), child: Column( diff --git a/lib/ui/marathon/widgets/marathon_banner.dart b/lib/ui/marathon/widgets/marathon_banner.dart index e90f545..e711319 100644 --- a/lib/ui/marathon/widgets/marathon_banner.dart +++ b/lib/ui/marathon/widgets/marathon_banner.dart @@ -11,7 +11,6 @@ import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; -import 'package:mohem_flutter_app/ui/marathon/marathon_intro_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/countdown_timer.dart'; import 'package:provider/provider.dart'; @@ -22,142 +21,179 @@ class MarathonBanner extends StatelessWidget { @override Widget build(BuildContext context) { MarathonProvider provider = context.read(); - return Container( - decoration: MyDecorations.shadowDecoration, - height: MediaQuery.of(context).size.height * 0.11, - clipBehavior: Clip.antiAlias, - child: Stack( - children: [ - Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - AppState().isArabic(context) ? math.pi : 0, - ), - child: SvgPicture.asset( - "assets/images/marathon_banner_bg.svg", - fit: BoxFit.fill, - width: double.infinity, - ), - ), - Positioned( - left: -20, - top: -10, - child: Transform.rotate( - angle: 15, - child: Container( - width: 65, - height: 32, - color: MyColors.darkDigitColor, - ), - ), - ), - SizedBox( - width: double.infinity, - height: double.infinity, - child: Row( + return provider.marathonDetailModel.startTime != null + ? Container( + decoration: MyDecorations.shadowDecoration, + height: MediaQuery.of(context).size.height * 0.11, + clipBehavior: Clip.antiAlias, + child: Stack( children: [ - const Expanded( - flex: 3, - child: SizedBox( + Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + AppState().isArabic(context) ? math.pi : 0, + ), + child: SvgPicture.asset( + "assets/images/marathon_banner_bg.svg", + fit: BoxFit.fill, width: double.infinity, - height: double.infinity, ), ), - Expanded( - flex: 5, - child: SizedBox( - width: double.infinity, - height: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AppState().isArabic(context) ? 0.height : 5.height, - AutoSizeText( - LocaleKeys.getReadyForContest.tr(), - minFontSize: 08, - maxFontSize: 11, - style: TextStyle( - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w600, - color: MyColors.white.withOpacity(0.83), - letterSpacing: -0.4, + AppState().isArabic(context) + ? Positioned( + right: -15, + top: -10, + child: Transform.rotate( + angle: 10, + child: Container( + width: 65, + height: 32, + color: MyColors.darkDigitColor, + ), + ), + ) + : Positioned( + left: -20, + top: -10, + child: Transform.rotate( + angle: 15, + child: Container( + width: 65, + height: 32, + color: MyColors.darkDigitColor, + ), + ), + ), + SizedBox( + width: double.infinity, + height: double.infinity, + child: Row( + children: [ + const Expanded( + flex: 3, + child: SizedBox( + width: double.infinity, + height: double.infinity, + ), + ), + Expanded( + flex: AppState().isArabic(context) ? 4 : 5, + child: SizedBox( + width: double.infinity, + height: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AppState().isArabic(context) ? 0.height : 5.height, + AutoSizeText( + LocaleKeys.getReadyForContest.tr(), + minFontSize: 08, + maxFontSize: 11, + style: TextStyle( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w600, + color: MyColors.white.withOpacity(0.83), + letterSpacing: -0.4, + ), + ), + AutoSizeText( + AppState().isArabic(context) ? provider.marathonDetailModel.titleAr ?? "" : provider.marathonDetailModel.titleEn ?? "", + style: TextStyle( + fontStyle: FontStyle.italic, + fontSize: 19, + fontWeight: FontWeight.bold, + color: MyColors.white.withOpacity(0.83), + height: 32 / 22, + ), + ), + 3.height, + BuildCountdownTimer( + timeToMarathon: DateTime.parse(provider.marathonDetailModel.startTime!).millisecondsSinceEpoch, + provider: provider, + screenFlag: 0, + ), + ], + ).paddingOnly( + left: AppState().isArabic(context) ? 12 : 3, + right: AppState().isArabic(context) ? 3 : 12, + ) + ], + ), + ), + ), + ], + ), + ), + AppState().isArabic(context) + ? Align( + alignment: Alignment.topRight, + child: SizedBox( + height: 20, + width: 35, + child: Transform.rotate( + angle: math.pi / 4.5, + child: Text( + LocaleKeys.brainMarathon.tr(), + textAlign: TextAlign.center, + maxLines: 2, + style: const TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + fontSize: 6, + height: 1.2, ), ), - AutoSizeText( - "Saudi Arabia", - style: TextStyle( - fontStyle: FontStyle.italic, - fontSize: 19, + ), + ), + ).paddingOnly(top: 5) + : Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 20, + width: 35, + child: Transform.rotate( + angle: -math.pi / 4.5, + child: Text( + LocaleKeys.brainMarathon.tr(), + textAlign: TextAlign.center, + maxLines: 2, + style: const TextStyle( + color: MyColors.kWhiteColor, fontWeight: FontWeight.bold, - color: MyColors.white.withOpacity(0.83), - height: 32 / 22, + fontSize: 6, + height: 1.2, ), ), - 3.height, - BuildCountdownTimer( - timeToMarathon: dummyEndTime, - provider: provider, - screenFlag: 0, - ), - ], - ).paddingOnly( - left: AppState().isArabic(context) ? 12 : 3, - right: AppState().isArabic(context) ? 3 : 12, - ) - ], - ), - ), - ), + ), + ), + ).paddingOnly(top: 5), + !AppState().isArabic(context) + ? Positioned( + right: 0, + bottom: 0, + child: RotatedBox( + quarterTurns: 4, + child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), + ).paddingAll(15), + ) + : Positioned( + bottom: 0, + left: 0, + child: RotatedBox( + quarterTurns: 2, + child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), + ).paddingAll(15), + ), ], + ).onPress( + () => Navigator.pushNamed(context, AppRoutes.marathonIntroScreen), ), - ), - Align( - alignment: Alignment.topLeft, - child: SizedBox( - height: 20, - width: 35, - child: Transform.rotate( - angle: -math.pi / 4.5, - child: Text( - LocaleKeys.brainMarathon.tr(), - textAlign: TextAlign.center, - maxLines: 2, - style: const TextStyle( - color: MyColors.kWhiteColor, - fontWeight: FontWeight.bold, - fontSize: 6, - height: 1.2, - ), - ), - ), - ), - ).paddingOnly(top: 5), - !AppState().isArabic(context) - ? Positioned( - right: 0, - bottom: 0, - child: RotatedBox( - quarterTurns: 4, - child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), - ).paddingAll(15), - ) - : Positioned( - bottom: 0, - left: 0, - child: RotatedBox( - quarterTurns: 2, - child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), - ).paddingAll(15), - ), - ], - ).onPress( - () => Navigator.pushNamed(context, AppRoutes.marathonIntroScreen), - ), - ); + ) + : const SizedBox(); } } diff --git a/lib/ui/marathon/widgets/marathon_header.dart b/lib/ui/marathon/widgets/marathon_header.dart index 0cc863b..fed6caa 100644 --- a/lib/ui/marathon/widgets/marathon_header.dart +++ b/lib/ui/marathon/widgets/marathon_header.dart @@ -28,8 +28,6 @@ class MarathonHeader extends StatelessWidget { color: MyColors.black, constraints: const BoxConstraints(), onPressed: () { - Provider.of(context, listen: false) - .resetValues(); Navigator.pop(context); }, ) diff --git a/lib/ui/marathon/widgets/question_card.dart b/lib/ui/marathon/widgets/question_card.dart index 56f1667..af538f8 100644 --- a/lib/ui/marathon/widgets/question_card.dart +++ b/lib/ui/marathon/widgets/question_card.dart @@ -1,160 +1,116 @@ import 'package:appinio_swiper/appinio_swiper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/decorations_helper.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/classes/lottie_consts.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; -import 'package:mohem_flutter_app/models/marathon_question_model.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:provider/provider.dart'; -List isSelectedOptions = [ - false, - false, - false, - false, -]; - -class QuestionCard extends StatefulWidget { +class QuestionCard extends StatelessWidget { final MarathonProvider provider; const QuestionCard({Key? key, required this.provider}) : super(key: key); - @override - State createState() => _QuestionCardState(); -} - -class _QuestionCardState extends State { - final List questionCards = []; - - @override - void initState() { - _loadCards(); - super.initState(); - } - - void _loadCards() { - for (DummyQuestionModel question in questions) { - questionCards.add( - CardContent( - question: question, - provider: widget.provider, - ), - ); - } - } - @override Widget build(BuildContext context) { return CupertinoPageScaffold( - child: SizedBox( - height: 440, - width: double.infinity, - child: Consumer( - builder: (BuildContext context, MarathonProvider provider, _) { - return AppinioSwiper( - padding: EdgeInsets.zero, - isDisabled: true, - controller: provider.swiperController, - unswipe: (int index, AppinioSwiperDirection direction) {}, - cards: questionCards, - onSwipe: (int index, AppinioSwiperDirection direction) { - if (direction == AppinioSwiperDirection.left) { - provider.startTimer(context); - } - }, - ); - }, - ), - ), + child: provider.cardContentList.isEmpty + ? Lottie.asset(MyLottieConsts.hourGlassLottie, height: 250).paddingOnly(top: 50) + : SizedBox( + height: 440, + width: double.infinity, + child: Consumer( + builder: (BuildContext context, MarathonProvider provider, _) { + return AppinioSwiper( + duration: const Duration(milliseconds: 400), + padding: EdgeInsets.zero, + isDisabled: true, + controller: provider.swiperController, + unswipe: (int index, AppinioSwiperDirection direction) {}, + onSwipe: (int index, AppinioSwiperDirection direction) {}, + cards: provider.cardContentList, + ); + }, + ), + ), ); } } class CardContent extends StatelessWidget { - final DummyQuestionModel question; - final MarathonProvider provider; - - const CardContent({ - Key? key, - required this.question, - required this.provider, - }) : super(key: key); + const CardContent({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 78, - width: double.infinity, - decoration: const BoxDecoration( - gradient: LinearGradient( - transform: GradientRotation(.83), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor, - ], - ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), + MarathonProvider provider = context.watch(); + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: CupertinoColors.white, + boxShadow: [ + BoxShadow( + color: CupertinoColors.systemGrey.withOpacity(0.2), + spreadRadius: 3, + blurRadius: 7, + offset: const Offset(0, 3), + ) + ], + ), + alignment: Alignment.center, + child: Column( + children: [ + Container( + height: 78, + width: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + transform: GradientRotation(.83), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ], + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), ), - ), - padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 17), - child: Text( - "What is the capital of Saudi Arabia?", - style: TextStyle( - color: MyColors.white, - fontSize: 16, - fontWeight: FontWeight.w600, + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + AppState().isArabic(context) ? provider.currentQuestion.titleAr ?? "" : provider.currentQuestion.titleEn ?? "", + style: const TextStyle( + color: MyColors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), ), ), - ), - AnswerContent(question: question, provider: provider), - ], + const AnswerContent(), + ], + ), ); } } -class AnswerContent extends StatefulWidget { - final DummyQuestionModel question; - final MarathonProvider provider; - - const AnswerContent({Key? key, required this.question, required this.provider}) : super(key: key); - - @override - State createState() => _AnswerContentState(); -} - -class _AnswerContentState extends State { - void updateOption(int index, bool value) { - isSelectedOptions[0] = false; - isSelectedOptions[1] = false; - isSelectedOptions[2] = false; - isSelectedOptions[3] = false; - isSelectedOptions[index] = value; - setState(() {}); - } - - Decoration getContainerColor(int index) { - if (!isSelectedOptions[index]) { - return MyDecorations.getContainersDecoration(MyColors.greyF7Color); - } - if (isSelectedOptions[index] && context.watch().start > 0) { - return MyDecorations.getContainersDecoration(MyColors.yellowColorII); - } - return MyDecorations.getContainersDecoration( - isSelectedOptions[index] ? MyColors.greenColor : MyColors.greyF7Color, - ); - } +class AnswerContent extends StatelessWidget { + const AnswerContent({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); return Container( padding: const EdgeInsets.symmetric(vertical: 31, horizontal: 13), decoration: const BoxDecoration( @@ -164,128 +120,48 @@ class _AnswerContentState extends State { bottomRight: Radius.circular(10), ), ), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // todo @faiz: Make a separate method and pass value and string , so we can minimize code duplication - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - return; - } - updateOption(0, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(0), - child: Center( - child: Text( - widget.question.opt1!, - style: TextStyle( - color: isSelectedOptions[0] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - return; - } - updateOption(1, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(1), - child: Center( - child: Text( - widget.question.opt2!, - style: TextStyle( - color: isSelectedOptions[1] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - return; - } - updateOption(2, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(2), - child: Center( - child: Text( - widget.question.opt3!, - style: TextStyle( - color: isSelectedOptions[2] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, + child: provider.currentQuestion.questionOptions != null + ? ListView.separated( + itemCount: provider.currentQuestion.questionOptions!.length, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + return AnswerTileForText( + index: index, + onAnswerTapped: () { + provider.updateCurrentQuestionOptionStatus(QuestionsOptionStatus.selected, index); + }, ); - return; - } - updateOption(3, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(3), - child: Center( - child: Text( - widget.question.opt3!, - style: TextStyle( - color: isSelectedOptions[3] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - ], + }, + separatorBuilder: (BuildContext context, int index) => 15.height, + ) + : const SizedBox(), + ); + } +} + +class AnswerTileForText extends StatelessWidget { + final int index; + final Function() onAnswerTapped; + + const AnswerTileForText({Key? key, required this.index, required this.onAnswerTapped}) : super(key: key); + + @override + Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); + return InkWell( + onTap: () { + onAnswerTapped(); + }, + child: Container( + alignment: Alignment.centerLeft, + decoration: MyDecorations.getAnswersContainerColor(provider.currentQuestion.questionOptions![index].optionStatus!), + child: Center( + child: (AppState().isArabic(context) ? provider.currentQuestion.questionOptions![index].titleAr! : provider.currentQuestion.questionOptions![index].titleEn!) + .toText16( + color: provider.currentQuestion.questionOptions![index].optionStatus == QuestionsOptionStatus.unSelected ? MyColors.darkTextColor : MyColors.white, + ) + .paddingOnly(top: 17, bottom: 17), + ), ), ); } diff --git a/lib/ui/marathon/widgets/question_card_builder.dart b/lib/ui/marathon/widgets/question_card_builder.dart new file mode 100644 index 0000000..e4bce29 --- /dev/null +++ b/lib/ui/marathon/widgets/question_card_builder.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; + +class QuestionCardBuilder extends StatelessWidget { + final WidgetBuilder onQuestion; + final WidgetBuilder onCompleted; + final WidgetBuilder onWrongAnswer; + final WidgetBuilder onCorrectAnswer; + final WidgetBuilder onWinner; + final WidgetBuilder onSkippedAnswer; + final WidgetBuilder onFindingWinner; + final QuestionCardStatus questionCardStatus; + + const QuestionCardBuilder({ + Key? key, + required this.onQuestion, + required this.onCompleted, + required this.onCorrectAnswer, + required this.onWinner, + required this.onSkippedAnswer, + required this.onWrongAnswer, + required this.onFindingWinner, + required this.questionCardStatus, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + switch (questionCardStatus) { + case QuestionCardStatus.question: + return onQuestion(context); + + case QuestionCardStatus.wrongAnswer: + return onWrongAnswer(context); + + case QuestionCardStatus.correctAnswer: + return onCorrectAnswer(context); + + case QuestionCardStatus.completed: + return onCompleted(context); + + case QuestionCardStatus.winnerFound: + return onWinner(context); + + case QuestionCardStatus.findingWinner: + return onFindingWinner(context); + + case QuestionCardStatus.skippedAnswer: + return onSkippedAnswer(context); + } + } +} diff --git a/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart b/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart index 444b83e..749599c 100644 --- a/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart +++ b/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart @@ -74,9 +74,9 @@ class _DynamicInputScreenState extends State { getBasicDetColsStructureList?.forEach((GetBasicDetColsStructureList element) { element.userBasicDetail = dynamicParams!.getEmployeeBasicDetailsList!.singleWhere((GetEmployeeBasicDetailsList userDetail) => userDetail.aPPLICATIONCOLUMNNAME == element.aPPLICATIONCOLUMNNAME); - if (element.objectValuesList != null) { - ObjectValuesList dropDownListValue = element.objectValuesList!.singleWhere((ObjectValuesList dropdown) => dropdown.cODE == element.userBasicDetail!.vARCHAR2VALUE); - element.userBasicDetail!.sEGMENTVALUEDSP = dropDownListValue.mEANING; + if (element.objectValuesList != null && element.userBasicDetail?.vARCHAR2VALUE != '') { + ObjectValuesList dropDownListValue = element.objectValuesList!.singleWhere((ObjectValuesList dropdown) => dropdown.cODE == element.userBasicDetail?.vARCHAR2VALUE); + element.userBasicDetail?.sEGMENTVALUEDSP = dropDownListValue.mEANING; } }); } else { @@ -93,9 +93,9 @@ class _DynamicInputScreenState extends State { getBasicDetColsStructureList?.forEach((GetBasicDetColsStructureList element) { element.userBasicDetail = dynamicParams!.getEmployeeBasicDetailsList!.singleWhere((GetEmployeeBasicDetailsList userDetail) => userDetail.aPPLICATIONCOLUMNNAME == element.aPPLICATIONCOLUMNNAME); - if (element.objectValuesList != null) { + if (element.objectValuesList != null && element.userBasicDetail!.vARCHAR2VALUE != '') { ObjectValuesList dropDownListValue = element.objectValuesList!.singleWhere((ObjectValuesList dropdown) => dropdown.cODE == element.userBasicDetail!.vARCHAR2VALUE); - element.userBasicDetail!.sEGMENTVALUEDSP = dropDownListValue.mEANING; + element.userBasicDetail?.sEGMENTVALUEDSP = dropDownListValue.mEANING; } }); } @@ -262,7 +262,7 @@ class _DynamicInputScreenState extends State { return PopupMenuButton( child: DynamicTextFieldWidget( (model.sEGMENTPROMPT ?? "") + (model.rEQUIREDFLAG == "Y" ? "*" : ""), - getBasicDetColsStructureList![index].userBasicDetail!.sEGMENTVALUEDSP ?? "", + getBasicDetColsStructureList![index].userBasicDetail?.sEGMENTVALUEDSP ?? "", isEnable: false, isPopup: true, ).paddingOnly(bottom: 12), @@ -363,7 +363,7 @@ class _DynamicInputScreenState extends State { Utils.showLoading(context); int numberValue = 0; List> values = getBasicDetDffStructureList!.map((e) { - String tempVar = e.userBasicDetail!.vARCHAR2VALUE ?? ""; + String tempVar = e.userBasicDetail?.vARCHAR2VALUE ?? ""; if (e.fORMATTYPE == "X") { // for date format type, date format is changed diff --git a/lib/widgets/shimmer/dashboard_shimmer_widget.dart b/lib/widgets/shimmer/dashboard_shimmer_widget.dart index 369b808..6fe5a03 100644 --- a/lib/widgets/shimmer/dashboard_shimmer_widget.dart +++ b/lib/widgets/shimmer/dashboard_shimmer_widget.dart @@ -188,6 +188,53 @@ class ServicesMenuShimmer extends StatelessWidget { } } + +class MarathonBannerShimmer extends StatelessWidget { + const MarathonBannerShimmer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: const Color(0xff000000).withOpacity(.05), + blurRadius: 26, + offset: const Offset(0, -3), + ), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset("assets/images/monthly_attendance.svg").toShimmer(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + "Attendan".toText11(isBold: false).toShimmer(), + 5.height, + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: LocaleKeys.attendance.tr().toText11(isBold: false).toShimmer(), + ), + 6.width, + SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4).toShimmer() + ], + ), + ], + ) + ], + ).paddingOnly(left: 10, right: 10, bottom: 10, top: 12), + ); + } +} + class ChatHomeShimmer extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/pubspec.yaml b/pubspec.yaml index fdbdfc8..0f1a7b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,9 @@ dependencies: appinio_swiper: ^1.1.1 expandable: ^5.0.1 +# networkImage + cached_network_image: ^3.2.2 + #Chat signalr_netcore: ^1.3.3 logging: ^1.0.1