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/chat/chat_api_client.dart b/lib/api/chat/chat_api_client.dart index 41f941f..1f97b1f 100644 --- a/lib/api/chat/chat_api_client.dart +++ b/lib/api/chat/chat_api_client.dart @@ -28,9 +28,7 @@ class ChatApiClient { "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG", }, ); - user.UserAutoLoginModel userLoginResponse = user.userAutoLoginModelFromJson( - response.body, - ); + user.UserAutoLoginModel userLoginResponse = user.userAutoLoginModelFromJson(response.body); return userLoginResponse; } @@ -42,9 +40,7 @@ class ChatApiClient { return searchUserJsonModel(response.body); } - List searchUserJsonModel(String str) => List.from( - json.decode(str).map((x) => ChatUser.fromJson(x)), - ); + List searchUserJsonModel(String str) => List.from(json.decode(str).map((x) => ChatUser.fromJson(x))); Future getRecentChats() async { try { @@ -58,7 +54,6 @@ class ChatApiClient { } catch (e) { e as APIException; if (e.message == "api_common_unauthorized") { - logger.d("Token Generated On APIIIIII"); user.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); if (userLoginResponse.response != null) { AppState().setchatUserDetails = userLoginResponse; @@ -98,9 +93,7 @@ class ChatApiClient { AppState().setchatUserDetails = userLoginResponse; getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); } else { - Utils.showToast( - userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", - ); + Utils.showToast(userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr"); } } throw e; @@ -108,13 +101,7 @@ class ChatApiClient { } Future favUser({required int userID, required int targetUserID}) async { - Response response = await ApiClient().postJsonForResponse( - "${ApiConsts.chatFavUser}addFavUser", - { - "targetUserId": targetUserID, - "userId": userID, - }, - token: AppState().chatDetails!.response!.token); + Response response = await ApiClient().postJsonForResponse("${ApiConsts.chatFavUser}addFavUser", {"targetUserId": targetUserID, "userId": userID}, token: AppState().chatDetails!.response!.token); fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); return favoriteChatUser; } @@ -137,9 +124,7 @@ class ChatApiClient { AppState().setchatUserDetails = userLoginResponse; unFavUser(userID: userID, targetUserID: targetUserID); } else { - Utils.showToast( - userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", - ); + Utils.showToast(userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr"); } } throw e; 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/colors.dart b/lib/classes/colors.dart index 10681be..4394279 100644 --- a/lib/classes/colors.dart +++ b/lib/classes/colors.dart @@ -62,4 +62,5 @@ class MyColors { static const Color grey9DColor = Color(0xff9D9D9D); static const Color darkDigitColor = Color(0xff2D2F39); static const Color grey71Color = Color(0xff717171); + static const Color darkGrey3BColor = Color(0xff3B3B3B); } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 4ba4e32..13c4258 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,7 +1,9 @@ +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 - // static String baseUrl = "https://hmgwebservices.com"; // Live server + //static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server + static String baseUrl = "https://hmgwebservices.com"; // Live server static String baseUrlServices = baseUrl + "/Services/"; // server // static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server static String utilitiesRest = baseUrlServices + "Utilities.svc/REST/"; @@ -24,6 +26,18 @@ class ApiConsts { static String chatMediaImageUploadUrl = chatServerBaseApiUrl + "shared/"; static String chatFavUser = chatServerBaseApiUrl + "FavUser/"; static String chatUserImages = chatServerBaseUrl + "empservice/api/employee/"; + + //Brain Marathon Constants + static String marathonBaseUrl = "https://18.188.181.12/service/"; + static String marathonParticipantLoginUrl = marathonBaseUrl + "api/auth/participantlogin"; + static String marathonProjectGetUrl = marathonBaseUrl + "api/Project/Project_Get"; + static String marathonUpcomingUrl = marathonBaseUrl + "api/marathon/upcoming/"; + static String marathonHubConnectionUrl = marathonBaseUrl + "MarathonBroadCast"; + + //DummyCards for the UI + + static CardContent dummyQuestion = const CardContent(); + } class SharedPrefsConsts { diff --git a/lib/classes/date_uitl.dart b/lib/classes/date_uitl.dart index 93a7e3f..ae80080 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/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index 95a990f..f3821f3 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -5,20 +5,15 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; -import 'package:logging/logging.dart'; import 'package:mohem_flutter_app/api/chat/chat_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/exceptions/api_exception.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; -import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as login; import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/widgets/image_picker.dart'; -import 'package:signalr_netcore/signalr_client.dart'; import 'package:uuid/uuid.dart'; class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { @@ -92,12 +87,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { if (isNewChat) userChatHistory = []; if (!loadMore) paginationVal = 0; isChatScreenActive = true; - Response response = await ChatApiClient().getSingleUserChatHistory( - senderUID: senderUID, - receiverUID: receiverUID, - loadMore: loadMore, - paginationVal: paginationVal, - ); + Response response = await ChatApiClient().getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); if (response.statusCode == 204) { if (isNewChat) { userChatHistory = []; @@ -107,25 +97,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } else { if (loadMore) { - List temp = getSingleUserChatModel( - response.body, - ).reversed.toList(); - userChatHistory.addAll( - temp, - ); + List temp = getSingleUserChatModel(response.body).reversed.toList(); + userChatHistory.addAll(temp); } else { - userChatHistory = getSingleUserChatModel( - response.body, - ).reversed.toList(); + userChatHistory = getSingleUserChatModel(response.body).reversed.toList(); } } isLoading = false; notifyListeners(); - markRead( - userChatHistory, - receiverUID, - ); - + markRead(userChatHistory, receiverUID); generateConvId(); } @@ -139,13 +119,10 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { for (SingleUserChatModel element in data!) { if (element.isSeen != null) { if (!element.isSeen!) { + print("Found Un Read"); + logger.d(jsonEncode(element)); dynamic data = [ - { - "userChatHistoryId": element.userChatHistoryId, - "TargetUserId": element.targetUserId, - "isDelivered": true, - "isSeen": true, - } + {"userChatHistoryId": element.userChatHistoryId, "TargetUserId": element.targetUserId, "isDelivered": true, "isSeen": true} ]; updateUserChatHistoryStatusAsync(data); } @@ -161,17 +138,22 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } void updateUserChatHistoryStatusAsync(List data) { - hubConnection.invoke( - "UpdateUserChatHistoryStatusAsync", - args: [data], - ); + try { + hubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + } catch (e) { + throw e; + } } - List getSingleUserChatModel(String str) => List.from( - json.decode(str).map( - (x) => SingleUserChatModel.fromJson(x), - ), - ); + void updateUserChatHistoryOnMsg(List data) { + try { + hubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + } catch (e) { + throw e; + } + } + + List getSingleUserChatModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); Future uploadAttachments(String userId, File file) async { dynamic result; @@ -191,15 +173,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void updateUserChatStatus(List? args) { dynamic items = args!.toList(); - for (dynamic cItem in items[0]) { + for (var cItem in items[0]) { for (SingleUserChatModel chat in userChatHistory) { - if (chat.userChatHistoryId.toString() == cItem["userChatHistoryId"].toString()) { + if (cItem["contantNo"].toString() == chat.contantNo.toString()) { chat.isSeen = cItem["isSeen"]; chat.isDelivered = cItem["isDelivered"]; - notifyListeners(); } } } + notifyListeners(); } void onChatSeen(List? args) { @@ -308,14 +290,9 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { userChatHistory.insert(0, data.first); var list = [ - { - "userChatHistoryId": data.first.userChatHistoryId, - "TargetUserId": data.first.targetUserId, - "isDelivered": true, - "isSeen": isChatScreenActive ? true : false, - } + {"userChatHistoryId": data.first.userChatHistoryId, "TargetUserId": temp.first.targetUserId, "isDelivered": true, "isSeen": isChatScreenActive ? true : false} ]; - updateUserChatHistoryStatusAsync(list); + updateUserChatHistoryOnMsg(list); notifyListeners(); } @@ -412,34 +389,34 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Uint8List? image, required bool isImageLoaded}) async { Uuid uuid = const Uuid(); + var contentNo = uuid.v4(); var msg = message.text; SingleUserChatModel data = SingleUserChatModel( - chatEventId: chatEventId, - chatSource: 1, - contant: msg, - contantNo: uuid.v4(), - conversationId: chatCID, - createdDate: DateTime.now(), - currentUserId: AppState().chatDetails!.response!.id, - currentUserName: AppState().chatDetails!.response!.userName, - targetUserId: targetUserId, - targetUserName: targetUserName, - isReplied: false, - fileTypeId: fileTypeId, - userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null, - fileTypeResponse: isAttachment - ? FileTypeResponse( - fileTypeId: fileTypeId, - fileTypeName: getFileType(getFileExtension(selectedFile.path).toString()), - fileKind: getFileExtension(selectedFile.path), - fileName: selectedFile.path.split("/").last, - fileTypeDescription: getFileTypeDescription(getFileExtension(selectedFile.path).toString()), - ) - : null, - image: image, - isImageLoaded: isImageLoaded, - ); + chatEventId: chatEventId, + chatSource: 1, + contant: msg, + contantNo: contentNo, + conversationId: chatCID, + createdDate: DateTime.now(), + currentUserId: AppState().chatDetails!.response!.id, + currentUserName: AppState().chatDetails!.response!.userName, + targetUserId: targetUserId, + targetUserName: targetUserName, + isReplied: false, + fileTypeId: fileTypeId, + userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null, + fileTypeResponse: isAttachment + ? FileTypeResponse( + fileTypeId: fileTypeId, + fileTypeName: getFileType(getFileExtension(selectedFile.path).toString()), + fileKind: getFileExtension(selectedFile.path), + fileName: selectedFile.path.split("/").last, + fileTypeDescription: getFileTypeDescription(getFileExtension(selectedFile.path).toString()), + ) + : null, + image: image, + isImageLoaded: isImageLoaded); userChatHistory.insert(0, data); isFileSelected = false; isMsgReply = false; @@ -448,7 +425,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); String chatData = - '{"contant":"$msg","contantNo":"${uuid.v4()}","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; + '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; await hubConnection.invoke("AddChatUserAsync", args: [json.decode(chatData)]); } @@ -456,11 +433,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); if (contain.isEmpty) { searchedChats!.add( - ChatUser( - id: targetUserId, - userName: targetUserName, - unreadMessageCount: 0 - ), + ChatUser(id: targetUserId, userName: targetUserName, unreadMessageCount: 0), ); notifyListeners(); } @@ -693,17 +666,21 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { // } void msgScroll() { - scrollController.animateTo( - scrollController.position.minScrollExtent - 100, - duration: const Duration(milliseconds: 500), - curve: Curves.easeIn, - ); - } - - // Future getDownLoadFile(String fileName) async { - // var data = await ChatApiClient().downloadURL(fileName: "data"); - // Image.memory(data); - // } + // scrollController.animateTo( + // // index: 150, + // duration: Duration(seconds: 2), + // curve: Curves.easeInOutCubic); + // scrollController.animateTo( + // scrollController.position.minScrollExtent - 100, + // duration: const Duration(milliseconds: 500), + // curve: Curves.easeIn, + // ); + } + +// Future getDownLoadFile(String fileName) async { +// var data = await ChatApiClient().downloadURL(fileName: "data"); +// Image.memory(data); +// } // void getUserChatHistoryNotDeliveredAsync({required int userId}) async { // try { @@ -713,12 +690,4 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { // } // } - - - - - - - - } diff --git a/lib/ui/chat/chat_bubble.dart b/lib/ui/chat/chat_bubble.dart index 62156c8..8995da1 100644 --- a/lib/ui/chat/chat_bubble.dart +++ b/lib/ui/chat/chat_bubble.dart @@ -74,23 +74,41 @@ class ChatBubble extends StatelessWidget { if (cItem.userChatReplyResponse != null && cItem.userChatReplyResponse!.fileTypeId == 12 || cItem.userChatReplyResponse!.fileTypeId == 3 || cItem.userChatReplyResponse!.fileTypeId == 4) + // Container( + // padding: EdgeInsets.all(0), // Border width + // decoration: BoxDecoration(color: Colors.red, borderRadius: const BorderRadius.all(Radius.circular(8))), + // child: ClipRRect( + // borderRadius: const BorderRadius.all( + // Radius.circular(8), + // ), + // child: SizedBox.fromSize( + // size: Size.fromRadius(8), // Image radius + // child: showImage( + // isReplyPreview: true, + // fileName: cItem.userChatReplyResponse!.contant!, + // fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg"), + // ), + // ), + // ), ClipRRect( - borderRadius: BorderRadius.circular( - 8, + borderRadius: BorderRadius.circular(8.0), + child: SizedBox( + height: 32, + width: 32, + child: showImage( + isReplyPreview: true, + fileName: cItem.userChatReplyResponse!.contant!, + fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg") + .paddingOnly(left: 10, right: 10, bottom: 16, top: 16), ), - child: showImage( - isReplyPreview: true, - fileName: cItem.userChatReplyResponse!.contant!, - fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg") - .paddingOnly(left: 10, right: 10, bottom: 16, top: 16), - ) + ), ], ), ), ).paddingOnly(right: 5, bottom: 7), if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) showImage(isReplyPreview: false, fileName: cItem.contant!, fileTypeDescription: cItem.fileTypeResponse!.fileTypeDescription).paddingOnly(right: 5).onPress(() { - showDialog(context: context, builder: (index) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!)); + showDialog(context: context, builder: (BuildContext context) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!)); }), cItem.contant!.toText12(), Align( @@ -110,7 +128,7 @@ class ChatBubble extends StatelessWidget { ).paddingOnly(top: 11, left: 13, right: 7, bottom: 5).objectContainerView(disablePadding: true).paddingOnly(left: MediaQuery.of(context).size.width * 0.3); } - Widget receiptUser(context) { + Widget receiptUser(BuildContext context) { return Container( padding: const EdgeInsets.only(top: 11, left: 13, right: 7, bottom: 5), decoration: BoxDecoration( @@ -153,27 +171,27 @@ class ChatBubble extends StatelessWidget { cItem.userChatReplyResponse!.fileTypeId == 4) ClipRRect( borderRadius: BorderRadius.circular(8.0), - child: showImage( + child: SizedBox( + height: 32, + width: 32, + child: showImage( isReplyPreview: true, fileName: cItem.userChatReplyResponse!.contant!, - fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg") - .paddingOnly(left: 10, right: 10, bottom: 16, top: 16), - ) + fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg")), + ).paddingOnly(left: 10, right: 10, bottom: 16, top: 16) ], ), ), ).paddingOnly(right: 5, bottom: 7), if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) showImage(isReplyPreview: false, fileName: cItem.contant!, fileTypeDescription: cItem.fileTypeResponse!.fileTypeDescription ?? "image/jpg").paddingOnly(right: 5).onPress(() { - showDialog(context: context, builder: (index) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!)); + showDialog(context: context, builder: (BuildContext context) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!)); }) else (cItem.contant! ?? "").toText12(color: Colors.white), Align( alignment: Alignment.centerRight, - child: dateTime.toText10( - color: Colors.white.withOpacity(.71), - ), + child: dateTime.toText10(color: Colors.white.withOpacity(.71)), ), ], ), @@ -196,7 +214,6 @@ class ChatBubble extends StatelessWidget { if (snapshot.data == null) { return SizedBox(); } else { - //data = image; cItem.image = snapshot.data; cItem.isImageLoaded = true; return Image.memory( diff --git a/lib/ui/chat/chat_detailed_screen.dart b/lib/ui/chat/chat_detailed_screen.dart index 643cc19..1d220bc 100644 --- a/lib/ui/chat/chat_detailed_screen.dart +++ b/lib/ui/chat/chat_detailed_screen.dart @@ -221,14 +221,11 @@ class _ChatDetailScreenState extends State { Widget showReplyImage(List data) { if (data.first.isImageLoaded! && data.first.image != null) { - return ClipRRect( - borderRadius: BorderRadius.circular(10.0), - child: Image.memory( - data.first.image!, - height: 43, - width: 43, - fit: BoxFit.cover, - ), + return Container( + width: 43, + height: 43, + decoration: BoxDecoration( + border: Border.all(color: MyColors.darkGrey3BColor, width: 1), borderRadius: BorderRadius.circular(10.0), image: DecorationImage(image: MemoryImage(data.first.image!), fit: BoxFit.cover)), ); } else { return const SizedBox(); diff --git a/lib/ui/chat/chat_full_image_preview.dart b/lib/ui/chat/chat_full_image_preview.dart index 2eaa09d..26364c5 100644 --- a/lib/ui/chat/chat_full_image_preview.dart +++ b/lib/ui/chat/chat_full_image_preview.dart @@ -25,7 +25,8 @@ class ChatImagePreviewScreen extends StatelessWidget { Image.memory( img, fit: BoxFit.cover, - + height: 400, + width: double.infinity, ).paddingAll(10), const Positioned( right: 0, diff --git a/lib/ui/chat/chat_home_screen.dart b/lib/ui/chat/chat_home_screen.dart index 9072b33..cd2245d 100644 --- a/lib/ui/chat/chat_home_screen.dart +++ b/lib/ui/chat/chat_home_screen.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -210,6 +211,9 @@ class _ChatHomeScreenState extends State { ), ), onPressed: () async { + // String plainText = 'Muhamad.Alam@cloudsolutions.com.sa'; + // String key = "PeShVmYp"; + // passEncrypt(plainText, "PeShVmYp"); showMyBottomSheet( context, callBackFunc: () {}, @@ -237,4 +241,127 @@ class _ChatHomeScreenState extends State { ), ); } + // + // void passEncrypt(String text, String pass) async { + // var salt = randomUint8List(8); + // var keyndIV = deriveKeyAndIV(pass, salt); + // var key = encrypt.Key(keyndIV.item1); + // var iv = encrypt.IV(keyndIV.item2); + // var encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7")); + // var encrypted = encrypter.encrypt(text, iv: iv); + // Uint8List encryptedBytesWithSalt = Uint8List.fromList(createUint8ListFromString("Salted__") + salt + encrypted.bytes); + // var resulttt = base64.encode(encryptedBytesWithSalt); + // print("Enc : " + resulttt); + // + // decryptAESCryptoJS(resulttt, pass); + // } + // + // Uint8List randomUint8List(int length) { + // assert(length > 0); + // var random = Random(); + // var ret = Uint8List(length); + // for (var i = 0; i < length; i++) { + // ret[i] = random.nextInt(256); + // } + // return ret; + // } + // + // void decryptAESCryptoJS(String encrypted, String passphrase) { + // try { + // Uint8List encryptedBytesWithSalt = base64.decode(encrypted); + // Uint8List encryptedBytes = encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length); + // var salt = encryptedBytesWithSalt.sublist(8, 16); + // var keyndIV = deriveKeyAndIV(passphrase, salt); + // var key = encrypt.Key(keyndIV.item1); + // var iv = encrypt.IV(keyndIV.item2); + // var encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7")); + // var decrypted = encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv); + // print("Dec : " + decrypted); + // // return decrypted; + // } catch (error) { + // throw error; + // } + // } + + void enc(String input) { + var ekey = "PeShVmYp"; + var eIV = "j70IbWYn"; + List eByt = utf8.encode(ekey); + List eIvByt = utf8.encode(eIV); + List iByt = utf8.encode(input); + + + + } + + // ///Accepts encrypted data and decrypt it. Returns plain text + // String decryptWithAES(String key, Encrypted encryptedData) { + // var cipherKey = encrypt.Key.fromUtf8(key); + // var encryptService = Encrypter(AES(cipherKey, mode: AESMode.cbc,padding: null)); + // var initVector = IV.fromUtf8(key.substring(0, 16)); + // return encryptService.decrypt(encryptedData, iv: initVector); + // } + // + // ///Encrypts the given plainText using the key. Returns encrypted data + // Encrypted encryptWithAES(String key, String plainText) { + // var cipherKey = encrypt.Key.fromUtf8(key); + // var encryptService = Encrypter(AES(cipherKey, mode: AESMode.cbc,padding: null)); + // var initVector = IV.fromUtf8("j70IbWYn"); + // Encrypted encryptedData = encryptService.encrypt(plainText, iv: initVector); + // print(encryptedData.base64); + // return encryptedData; + // } + // + // Tuple2 deriveKeyAndIV(String passphrase, Uint8List salt) { + // var password = createUint8ListFromString(passphrase); + // Uint8List concatenatedHashes = Uint8List(0); + // Uint8List currentHash = Uint8List(0); + // bool enoughBytesForKey = false; + // Uint8List preHash = Uint8List(0); + // + // while (!enoughBytesForKey) { + // int preHashLength = currentHash.length + password.length + salt.length; + // if (currentHash.length > 0) + // preHash = Uint8List.fromList(currentHash + password + salt); + // else + // preHash = Uint8List.fromList(password + salt); + // + // currentHash = preHash; + // concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash); + // if (concatenatedHashes.length >= 48) enoughBytesForKey = true; + // } + // + // var keyBtyes = concatenatedHashes.sublist(0, 32); + // var ivBtyes = concatenatedHashes.sublist(32, 48); + // return new Tuple2(keyBtyes, ivBtyes); + // } + // + // Uint8List createUint8ListFromString(String s) { + // var ret = new Uint8List(s.length); + // for (var i = 0; i < s.length; i++) { + // ret[i] = s.codeUnitAt(i); + // } + // return ret; + // } + // + // Uint8List genRandomWithNonZero(int seedLength) { + // var random = Random.secure(); + // const int randomMax = 245; + // Uint8List uint8list = Uint8List(seedLength); + // for (int i = 0; i < seedLength; i++) { + // uint8list[i] = random.nextInt(randomMax) + 1; + // } + // return uint8list; + // } + // + // + // + // void test(String text, String kk) { + // Uint8List key = Uint8List.fromList(utf8.encode(kk)); + // PaddedBlockCipher cipher = exp.PaddedBlockCipherImpl(exp.PKCS7Padding(), exp.ECBBlockCipher(exp.AESEngine())); + // cipher.init(true, PaddedBlockCipherParameters(KeyParameter(key), null)); + // var byte = Uint8List.fromList(utf8.encode(text)); + // var data = cipher.process(byte); + // print(data); + // } } diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index b8a8d95..7cf28cf 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -1,28 +1,25 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; +import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:mohem_flutter_app/api/dashboard_api_client.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/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'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; -import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; -import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.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'; @@ -45,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); @@ -56,6 +54,7 @@ class _DashboardScreenState extends State { super.initState(); scheduleMicrotask(() { data = Provider.of(context, listen: false); + marathonProvider = Provider.of(context, listen: false); _bHubCon(); _onRefresh(); }); @@ -92,6 +91,7 @@ class _DashboardScreenState extends State { data.fetchLeaveTicketBalance(context, DateTime.now()); data.fetchMenuEntries(); data.getCategoryOffersListAPI(context); + marathonProvider.getMarathonDetailsFromApi(); data.fetchChatCounts(); _refreshController.refreshCompleted(); } @@ -237,11 +237,14 @@ class _DashboardScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ 9.height, - CountdownTimer( - endTime: model.endTime, - onEnd: null, - endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), - textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + Directionality( + textDirection: ui.TextDirection.ltr, + child: CountdownTimer( + endTime: model.endTime, + onEnd: null, + endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), + textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + ), ), LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), 9.height, @@ -318,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( @@ -364,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/landing/today_attendance_screen2.dart b/lib/ui/landing/today_attendance_screen2.dart index ada652c..f0228eb 100644 --- a/lib/ui/landing/today_attendance_screen2.dart +++ b/lib/ui/landing/today_attendance_screen2.dart @@ -99,10 +99,13 @@ class _TodayAttendanceScreenState extends State { child: CountdownTimer( endTime: model.endTime, widgetBuilder: (context, v) { - return AutoSizeText( - getValue(v?.hours) + " : " + getValue(v?.min) + " : " + getValue(v?.sec), - maxLines: 1, - style: const TextStyle(color: Colors.white, fontSize: 42, letterSpacing: -1.92, fontWeight: FontWeight.bold, height: 1), + return Directionality( + textDirection: TextDirection.ltr, + child: AutoSizeText( + getValue(v?.hours) + " : " + getValue(v?.min) + " : " + getValue(v?.sec), + maxLines: 1, + style: const TextStyle(color: Colors.white, fontSize: 42, letterSpacing: -1.92, fontWeight: FontWeight.bold, height: 1), + ), ); }, onEnd: null, @@ -116,7 +119,7 @@ class _TodayAttendanceScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - LocaleKeys.shiftTime.tr().tr().toTextAuto(color: MyColors.greyACColor, fontSize: 18, maxLine: 1).paddingOnly(left: 21,right: 21), + LocaleKeys.shiftTime.tr().tr().toTextAuto(color: MyColors.greyACColor, fontSize: 18, maxLine: 1).paddingOnly(left: 21, right: 21), (model.attendanceTracking!.pShtName ?? "00:00:00").toString().toTextAuto(color: Colors.white, isBold: true, fontSize: 26, maxLine: 1), ], ), 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 bb8da6d..cfa3b1b 100644 --- a/lib/ui/marathon/widgets/countdown_timer.dart +++ b/lib/ui/marathon/widgets/countdown_timer.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:auto_size_text/auto_size_text.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -29,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, @@ -53,80 +54,83 @@ class BuildCountdownTimer extends StatelessWidget { ); Widget buildEmptyWidget() { - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - // todo @faiz: Make a separate method and pass string , so we can minimize code replication - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.days.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.hours.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.minutes.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.seconds.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - ], + return Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + // todo @faiz: Make a separate method and pass string , so we can minimize code replication + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.days.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.hours.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.minutes.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.seconds.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + ], + ), ); } @@ -149,108 +153,111 @@ class BuildCountdownTimer extends StatelessWidget { return buildEmptyWidget(); } - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - // todo @faiz: Make a separate method and pass value and string , so we can minimize code replication - time.days == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.days! < 10 ? "0${time.days.toString()}" : time.days.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.days.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - time.hours == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.hours! < 10 ? "0${time.hours.toString()}" : time.hours.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.hours.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - time.min == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.min! < 10 ? "0${time.min.toString()}" : time.min.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.minutes.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - time.sec == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.sec! < 10 ? "0${time.sec.toString()}" : time.sec.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.seconds.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - ], + return Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + // todo @faiz: Make a separate method and pass value and string , so we can minimize code replication + time.days == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.days! < 10 ? "0${time.days.toString()}" : time.days.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.days.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + time.hours == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.hours! < 10 ? "0${time.hours.toString()}" : time.hours.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.hours.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + time.min == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.min! < 10 ? "0${time.min.toString()}" : time.min.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.minutes.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + time.sec == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.sec! < 10 ? "0${time.sec.toString()}" : time.sec.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.seconds.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + ], + ), ); } 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..a2efb36 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 @@ -91,6 +94,9 @@ dependencies: camera: ^0.10.0+4 + #Encryption + cryptography: ^2.0.5 + cryptography_flutter: ^2.0.2 video_player: ^2.4.7