diff --git a/assets/audio/pulse_tone_android.mp3 b/assets/audio/pulse_tone_android.mp3 new file mode 100644 index 0000000..a4e2bde Binary files /dev/null and b/assets/audio/pulse_tone_android.mp3 differ diff --git a/assets/audio/pulse_tune_ios.caf b/assets/audio/pulse_tune_ios.caf new file mode 100644 index 0000000..c613dd1 Binary files /dev/null and b/assets/audio/pulse_tune_ios.caf differ diff --git a/assets/images/congrats.gif b/assets/images/congrats.gif new file mode 100644 index 0000000..32818b2 Binary files /dev/null and b/assets/images/congrats.gif differ diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 59c0814..c4d6d65 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -502,7 +502,12 @@ "favorite": "مفضلتي", "searchfromchat": "البحث من الدردشة", "yourAnswerCorrect": "إجابتك صحيحة", - "youMissedTheQuestion": "فاتك !! أنت خارج اللعبة. لكن يمكنك المتابعة.", - "wrongAnswer": "إجابة خاطئة! أنت خارج اللعبة. لكن يمكنك المتابعة." - + "youMissedTheQuestion": "نفد منك الوقت. أنت خارج اللعبة. لكن يمكنك الاستمرار وكمشاهد.", + "wrongAnswer": "إجابتك غير صحيحة. أنت خارج اللعبة. لكن يمكنك الاستمرار وكمشاهد.", + "oops": "أوه!!!", + "winner": "الفائز", + "youWantToLeaveMarathon": "هل أنت متأكد أنك تريد العودة؟ سوف تخرج من المسابقة.", + "ourSponsor": "راعينا:", + "startingIn": "يبدأ في", + "youAreOutOfContest": "أنت خارج المسابقة." } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b0ab50e..48a1db5 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -499,10 +499,16 @@ "codeExpire": "The verification code has been expired", "allQuestionsCorrect": "You have answered all questions correct", "typeheretoreply": "Type here to reply", - "favorite" : "My Favorites", + "favorite": "My Favorites", "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." + "youMissedTheQuestion": "You ran out of time. You are out of the game. But you can continue and as a viewer.", + "wrongAnswer": "Your answer is Incorrect. You are out of the game. But you can continue and as a viewer.", + "oops": "Ooopsss!!!!", + "winner": "WINNER", + "youWantToLeaveMarathon": "Are you sure you want to go back? You will be out of the contest.", + "ourSponsor": "Our Sponsor:", + "startingIn": "Starting in", + "youAreOutOfContest": "You are out of the contest." } \ No newline at end of file diff --git a/assets/lottie/marathon_waiting.json b/assets/lottie/marathon_waiting.json new file mode 100644 index 0000000..83e4756 --- /dev/null +++ b/assets/lottie/marathon_waiting.json @@ -0,0 +1 @@ +{"nm":"Loading_003","mn":"","layers":[{"ty":0,"nm":"2","mn":"","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"cl":"","ln":"","ddd":0,"bm":0,"tt":0,"hasMask":false,"td":0,"ao":0,"ks":{"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[250,250,0],"ix":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10}},"ef":[],"w":500,"h":500,"refId":"comp_0","ind":0},{"ty":0,"nm":"1","mn":"","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"cl":"","ln":"","ddd":0,"bm":0,"tt":0,"hasMask":false,"td":0,"ao":0,"ks":{"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[250,250,0],"ix":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":60,"ix":11},"r":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-31],"t":4},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-31],"t":207.000008431283}],"ix":10}},"ef":[],"w":500,"h":500,"refId":"comp_1","ind":1}],"ddd":0,"h":500,"w":500,"meta":{"a":"","k":"","d":"","g":"@lottiefiles/toolkit-js 0.17.4","tc":"#ffffff"},"v":"5.2.1","fr":29.9700012207031,"op":202.000008227629,"ip":9.00000036657752,"assets":[{"nm":"","mn":"","layers":[{"ty":4,"nm":"Layer 3 Outlines 3","mn":"","sr":1,"st":5.00000020365417,"op":605.000024642155,"ip":-19.0000007738859,"hd":false,"cl":"","ln":"","ddd":0,"bm":0,"tt":0,"hasMask":false,"td":1,"ao":0,"ks":{"a":{"a":0,"k":[186.018,192.618,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[97.292,100.917,100],"t":4},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[104.224,99.892,100],"t":108},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[97.292,100.917,100],"t":209.000008512745}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[252.209,249.449,0],"ix":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[310.866],"t":4},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-49],"t":209.000008512745}],"ix":10}},"ef":[],"shapes":[{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[51.471,-19.6],[51.29,75.368],[-11.48,42.136],[-85.813,28.535],[-16.737,-1.394],[-18.031,-37.988],[22.567,-60.623]],"o":[[-20.195,7.689],[-24.566,-36.096],[23.791,-87.319],[15.938,-5.3],[41.874,3.489],[28.123,59.252],[-20.321,54.592]],"v":[[18.613,169.26],[-146.062,126.343],[-172.423,1.333],[1.405,-192.793],[62.244,-200.318],[155.78,-124.529],[159.191,64.737]]},"ix":2}},{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":2,"cix":2,"np":0,"it":[{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"fl","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.098,0.1137,0.1451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100.261,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[184.153,201.962],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":0},{"ty":4,"nm":"Layer 5 Outlines 3","mn":"","sr":1,"st":5.00000020365417,"op":605.000024642155,"ip":-19.0000007738859,"hd":false,"cl":"","ln":"","ddd":0,"bm":0,"tt":2,"hasMask":false,"td":0,"ao":0,"ks":{"a":{"a":0,"k":[216.251,216.449,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[252.209,249.449,0],"ix":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10}},"ef":[],"shapes":[{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":1,"it":[{"ty":"sh","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 4","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[119.432,0],[0,-119.541],[-119.432,0],[0,119.542]],"o":[[-119.432,0],[0,119.542],[119.432,0],[0,-119.541]],"v":[[0,-216.449],[-216.25,0],[0,216.449],[216.251,0]]},"ix":2}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gf","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[65.201,-74.966],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.16862745098039217,0.7215686274509804,0.6588235294117647,0.455,0.13725490196078433,0.615686274509804,0.5607843137254902,0.999,0.11372549019607843,0.5725490196078431,0.6666666666666666],"ix":9}},"t":1,"a":{"a":0,"k":0},"s":{"a":0,"k":[-130.068,76.804],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[216.251,216.449],"ix":2},"r":{"a":0,"k":-149.651,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1}],"id":"comp_0","fr":30},{"nm":"","mn":"","layers":[{"ty":4,"nm":"Layer 3 Outlines 3","mn":"","sr":1,"st":5.00000020365417,"op":605.000024642155,"ip":-19.0000007738859,"hd":false,"cl":"","ln":"","ddd":0,"bm":0,"tt":0,"hasMask":false,"td":1,"ao":0,"ks":{"a":{"a":0,"k":[186.018,192.618,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[97.292,100.917,100],"t":4},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[104.224,99.892,100],"t":108},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[97.292,100.917,100],"t":209.000008512745}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[252.209,249.449,0],"ix":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-49],"t":4},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[310.866],"t":209.000008512745}],"ix":10}},"ef":[],"shapes":[{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[51.471,-19.6],[51.29,75.368],[-11.48,42.136],[-85.813,28.535],[-16.737,-1.394],[-18.031,-37.988],[22.567,-60.623]],"o":[[-20.195,7.689],[-24.566,-36.096],[23.791,-87.319],[15.938,-5.3],[41.874,3.489],[28.123,59.252],[-20.321,54.592]],"v":[[18.613,169.26],[-146.062,126.343],[-172.423,1.333],[1.405,-192.793],[62.244,-200.318],[155.78,-124.529],[159.191,64.737]]},"ix":2}},{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":2,"cix":2,"np":0,"it":[{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"fl","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.098,0.1137,0.1451],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100.261,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[184.153,201.962],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":0},{"ty":4,"nm":"Layer 5 Outlines 3","mn":"","sr":1,"st":5.00000020365417,"op":605.000024642155,"ip":-19.0000007738859,"hd":false,"cl":"","ln":"","ddd":0,"bm":0,"tt":2,"hasMask":false,"td":0,"ao":0,"ks":{"a":{"a":0,"k":[216.251,216.449,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[252.209,249.449,0],"ix":2},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10}},"ef":[],"shapes":[{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"gr","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":1,"it":[{"ty":"sh","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 4","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[119.432,0],[0,-119.541],[-119.432,0],[0,119.542]],"o":[[-119.432,0],[0,119.542],[119.432,0],[0,-119.541]],"v":[[0,-216.449],[-216.25,0],[0,216.449],[216.251,0]]},"ix":2}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gf","bm":0,"cl":"","ln":"","hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[65.201,-74.966],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.07058823529411765,0.3411764705882353,0.396078431372549,0.424,0.07058823529411765,0.3411764705882353,0.396078431372549,1,0.07058823529411765,0.3411764705882353,0.396078431372549],"ix":9}},"t":1,"a":{"a":0,"k":0},"s":{"a":0,"k":[-130.068,76.804],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[216.251,216.449],"ix":2},"r":{"a":0,"k":-149.651,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1}],"id":"comp_1","fr":30}]} \ No newline at end of file diff --git a/lib/api/marathon/marathon_api_client.dart b/lib/api/marathon/marathon_api_client.dart index 1ec117d..132ec30 100644 --- a/lib/api/marathon/marathon_api_client.dart +++ b/lib/api/marathon/marathon_api_client.dart @@ -1,22 +1,19 @@ 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 { + late HubConnection hubConnection; + L.Logger logger = L.Logger(); + Future getMarathonToken() async { String employeeUserName = AppState().getUserName ?? ""; String employeeSession = AppState().postParamsObject?.pSessionId.toString() ?? ""; @@ -55,11 +52,9 @@ class MarathonApiClient { 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 ""; } } @@ -70,9 +65,14 @@ class MarathonApiClient { Response response = await ApiClient().getJsonForResponse(ApiConsts.marathonUpcomingUrl + payrollString, token: AppState().getMarathonToken ?? await getMarathonToken()); var json = jsonDecode(response.body); + logger.i("json in getMarathonDetails: $json"); MarathonGenericModel marathonGenericModel = MarathonGenericModel.fromJson(json); + if (marathonGenericModel.data == null) { + return MarathonDetailModel(); + } + MarathonDetailModel marathonDetailModel = MarathonDetailModel.fromJson(marathonGenericModel.data); AppState().setMarathonProjectId = marathonDetailModel.id!; @@ -80,99 +80,165 @@ class MarathonApiClient { return marathonDetailModel; } - late HubConnection hubConnection; - L.Logger logger = L.Logger(); + Future joinMarathonAsParticipant() async { + Map jsonObject = { + "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "", + "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "", + "marathonId": AppState().getMarathonProjectId!, + }; - 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"); - } + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonJoinParticipantUrl, jsonObject, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); + + if (marathonModel.statusCode == 208) { + // means participant is already in the marathon i.e already joined + return true; + } - try { - hubConnection.on("OnParticipantJoin", (List? arguments) { - onParticipantJoin(arguments, context); - }); - } catch (e, s) { - logger.i("Error in OnParticipantJoin"); + if (marathonModel.statusCode == 200) { + if (marathonModel.data != null && marathonModel.isSuccessful == true) { + logger.i("joinMarathonAsParticipant: ${marathonModel.data}"); + return true; + } else { + return false; } + } else { + return false; } } - Future onSendQuestionToParticipant(List? arguments, BuildContext context) async { - logger.i("onSendQuestionToParticipant arguments: $arguments"); + Future getNextQuestion({required String? questionId, required String marathonId}) async { + Map jsonObject = { + "questionId": questionId, + "marathonId": marathonId, + }; - if (arguments != null) { - Map data = arguments.first! as Map; - var json = data["data"]; - QuestionModel newQuestion = QuestionModel.fromJson(json); - context.read().onNewQuestionReceived(newQuestion); + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonNextQuestionUrl, jsonObject, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + + var data = json["data"]; + + if (data != null) { + QuestionModel newQuestion = QuestionModel.fromJson(data); + return newQuestion; + } else { + return null; } } - Future onParticipantJoin(List? arguments, BuildContext context) async { - logger.i("OnParticipantJoin arguments: $arguments"); - context.watch().totalMarathoners++; + Future submitSelectedOption({required String? selectedAnswerId}) async { + Map jsonObject = {"selectedOptionId": selectedAnswerId}; + + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonSubmitAnswerUrl, jsonObject, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + logger.i("json: $json"); + + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); + + if (marathonModel.isSuccessful == null) { + return false; + } + + return marathonModel.isSuccessful!; } + +// Future buildHubConnection(BuildContext context, String prizeId) 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, +// "prizeId": "8577B2E8-5DD7-43F0-10DD-08DACB0AC064", +// } +// ], +// ).catchError((e) { +// logger.i("Error in AddParticipant: $e"); +// }); +// +// context.read().addItemToList(ApiConsts.dummyQuestion); +// +// await hubConnection.invoke( +// "SendQuestionToParticipant", +// args: [ +// { +// "marathonId": "${AppState().getMarathonProjectId}", +// } +// ], +// ).catchError((e) { +// Utils.confirmDialog(context, e.toString()); +// 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); +// AppRoutes.navigatorKey.currentContext!.read().onNewQuestionReceived(newQuestion); +// } +// } +// +// Future onParticipantJoin(List? arguments, BuildContext context) async { +// logger.i("OnParticipantJoin arguments: $arguments"); +// context.watch().totalMarathoners++; +// } } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 27fd9bb..97cb8e6 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -26,11 +26,15 @@ class ApiConsts { static String chatUserImages = chatServerBaseUrl + "empservice/api/employee/"; //Brain Marathon Constants - static String marathonBaseUrl = "https://marathoon.com/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"; + static String marathonBaseUrl = "https://marathoon.com/service/api/"; + static String marathonParticipantLoginUrl = marathonBaseUrl + "auth/participantlogin"; + static String marathonProjectGetUrl = marathonBaseUrl + "Project/Project_Get"; + static String marathonUpcomingUrl = marathonBaseUrl + "marathon/upcoming/"; + static String marathonJoinParticipantUrl = marathonBaseUrl + "participant/participant_join"; + static String marathonNextQuestionUrl = marathonBaseUrl + "question/next"; + static String marathonSubmitAnswerUrl = marathonBaseUrl + "question/submit"; + static String marathonQualifiersUrl = marathonBaseUrl + "winner/getWinner/"; + static String marathonSelectedWinner = marathonBaseUrl + "winner/getSelectedWinner/"; //DummyCards for the UI diff --git a/lib/classes/lottie_consts.dart b/lib/classes/lottie_consts.dart index 24dc423..7846f6c 100644 --- a/lib/classes/lottie_consts.dart +++ b/lib/classes/lottie_consts.dart @@ -4,6 +4,7 @@ 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 marathonWaiting = "assets/lottie/marathon_waiting.json"; static const String wrongAnswerGif = "assets/images/wrong_answer.gif"; - + static const String congratsGif = "assets/images/congrats.gif"; } diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 33d9830..3b30d05 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -86,6 +86,11 @@ class Utils { return prefs.getString(key) ?? ""; } + static Future removeStringFromPrefs(String key) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.remove(key); + } + static Future saveStringFromPrefs(String key, String value) async { SharedPreferences prefs = await SharedPreferences.getInstance(); return await prefs.setString(key, value); diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 7cf8a35..365a1c0 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -9,7 +9,6 @@ import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart'; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart'; import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart'; -import 'package:mohem_flutter_app/ui/landing/today_attendance_screen.dart'; import 'package:mohem_flutter_app/ui/landing/today_attendance_screen2.dart'; import 'package:mohem_flutter_app/ui/leave_balance/add_leave_balance_screen.dart'; import 'package:mohem_flutter_app/ui/leave_balance/leave_balance_screen.dart'; @@ -21,6 +20,8 @@ import 'package:mohem_flutter_app/ui/login/verify_last_login_screen.dart'; import 'package:mohem_flutter_app/ui/login/verify_login_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_intro_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_screen.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_sponsor_video_screen.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_waiting_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_winner_selection.dart'; import 'package:mohem_flutter_app/ui/marathon/winner_screen.dart'; import 'package:mohem_flutter_app/ui/misc/request_submit_screen.dart'; @@ -66,6 +67,7 @@ import 'package:mohem_flutter_app/ui/screens/pending_transactions/pending_transa import 'package:mohem_flutter_app/ui/screens/pending_transactions/pending_transactions_details.dart'; import 'package:mohem_flutter_app/ui/screens/submenu_screen.dart'; import 'package:mohem_flutter_app/ui/termination/end_employement.dart'; +import 'package:mohem_flutter_app/ui/unsafe_device_screen.dart'; import 'package:mohem_flutter_app/ui/work_list/item_history_screen.dart'; import 'package:mohem_flutter_app/ui/work_list/itg_detail_screen.dart'; import 'package:mohem_flutter_app/ui/work_list/work_list_screen.dart'; @@ -73,6 +75,8 @@ import 'package:mohem_flutter_app/ui/work_list/worklist_detail_screen.dart'; import 'package:mohem_flutter_app/ui/work_list/worklist_settings.dart'; class AppRoutes { + static GlobalKey navigatorKey = GlobalKey(); + static const String splash = "/splash"; static const String registerSelection = "/registerSelection"; static const String loginVerifyAccount = "/loginVerifyAccount"; @@ -185,6 +189,10 @@ class AppRoutes { static const String marathonScreen = "/marathonScreen"; static const String marathonWinnerSelection = "/marathonWinnerSelection"; static const String marathonWinnerScreen = "/marathonWinnerScreen"; + static const String marathonSponsorVideoScreen = "/marathonSponsorVideoScreen"; + static const String marathonWaitingScreen = "/marathonWaitingScreen"; + + static const String unsafeDeviceScreen = "/unsafeDeviceScreen"; static final Map routes = { login: (BuildContext context) => LoginScreen(), @@ -293,5 +301,9 @@ class AppRoutes { marathonScreen: (BuildContext context) => MarathonScreen(), marathonWinnerSelection: (BuildContext context) => MarathonWinnerSelection(), marathonWinnerScreen: (BuildContext context) => WinnerScreen(), + marathonSponsorVideoScreen: (BuildContext context) => const SponsorVideoScreen(), + marathonWaitingScreen: (BuildContext context) => const MarathonWaitingScreen(), + + unsafeDeviceScreen: (BuildContext context) => const UnsafeDeviceScreen(), }; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 3546f6b..01b7a08 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -90,8 +90,9 @@ extension EmailValidator on String { style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( + Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines, bool isCenter = false}) => Text( this, + textAlign: isCenter ? TextAlign.center : TextAlign.left, maxLines: maxlines, style: TextStyle( color: color ?? MyColors.darkTextColor, @@ -118,8 +119,9 @@ extension EmailValidator on String { style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), ); - Widget toText18({Color? color, bool isBold = false}) => Text( + Widget toText18({Color? color, bool isBold = false, bool isCentered = false}) => Text( this, + textAlign: isCentered ? TextAlign.center : null, style: TextStyle(fontSize: 18, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.08), ); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 2f7efcc..1d28232 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -490,5 +490,11 @@ abstract class LocaleKeys { static const yourAnswerCorrect = 'yourAnswerCorrect'; static const youMissedTheQuestion = 'youMissedTheQuestion'; static const wrongAnswer = 'wrongAnswer'; + static const oops = 'oops'; + static const winner = 'winner'; + static const youWantToLeaveMarathon = 'youWantToLeaveMarathon'; + static const ourSponsor = 'ourSponsor'; + static const startingIn = 'startingIn'; + static const youAreOutOfContest = 'youAreOutOfContest'; } diff --git a/lib/main.dart b/lib/main.dart index 4d686b8..4709be1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,11 +3,11 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; -import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/generated/codegen_loader.g.dart'; import 'package:mohem_flutter_app/models/post_params_model.dart'; +import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/eit_provider_model.dart'; import 'package:mohem_flutter_app/theme/app_theme.dart'; @@ -92,6 +92,7 @@ class MyApp extends StatelessWidget { MonthYearPickerLocalizations.delegate, ); return MaterialApp( + navigatorKey: AppRoutes.navigatorKey, builder: (BuildContext context, Widget? child) { return MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), @@ -249,3 +250,4 @@ class MyApp extends StatelessWidget { // }); // } // } + diff --git a/lib/models/marathon/marathon_model.dart b/lib/models/marathon/marathon_model.dart index b32530c..6fd1f5c 100644 --- a/lib/models/marathon/marathon_model.dart +++ b/lib/models/marathon/marathon_model.dart @@ -126,10 +126,10 @@ class Projects { class Sponsors { String? id; String? nameEn; - Null? nameAr; + String? nameAr; String? image; - Null? video; - Null? logo; + String? video; + String? logo; List? sponsorPrizes; Sponsors( @@ -186,7 +186,7 @@ class SponsorPrizes { } Map toJson() { - Map data = new Map(); + Map data = {}; data['id'] = id; data['marathonPrizeEn'] = marathonPrizeEn; data['marathonPrizeAr'] = marathonPrizeAr; diff --git a/lib/models/marathon/question_model.dart b/lib/models/marathon/question_model.dart index 0bb42cd..4501947 100644 --- a/lib/models/marathon/question_model.dart +++ b/lib/models/marathon/question_model.dart @@ -14,6 +14,7 @@ class QuestionModel { String? gapText; String? gapImage; int? questOptionsLimit; + int? remainingParticipantCount; List? questionOptions; QuestionModel({ @@ -28,6 +29,7 @@ class QuestionModel { String? gapText, String? gapImage, int? questOptionsLimit, + int? remainingParticipantCount, List? questionOptions, }); @@ -43,6 +45,7 @@ class QuestionModel { gapText = json['gapText']; gapImage = json['gapImage']; questOptionsLimit = json['questOptionsLimit']; + remainingParticipantCount = json['remainingParticipantCount']; if (json['questionOptions'] != null) { questionOptions = []; json['questionOptions'].forEach((v) { @@ -64,6 +67,7 @@ class QuestionModel { data['gapText'] = gapText; data['gapImage'] = gapImage; data['questOptionsLimit'] = questOptionsLimit; + data['remainingParticipantCount'] = remainingParticipantCount; if (questionOptions != null) { data['questionOptions'] = questionOptions!.map((v) => v.toJson()).toList(); } diff --git a/lib/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index 0482bc4..3f55417 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; +import 'package:just_audio/just_audio.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'; @@ -54,6 +55,8 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Future buildHubConnection() async { chatHubConnection = await getHubConnection(); await chatHubConnection.start(); + print("Startedddddddd"); + chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); } Future getHubConnection() async { @@ -75,7 +78,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void registerEvents() { chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus); - chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); + // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); // hubConnection.on("OnSeenChatUserAsync", onChatSeen); //hubConnection.on("OnUserTypingAsync", onUserTyping); chatHubConnection.on("OnUserCountAsync", userCountAsync); @@ -122,7 +125,9 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { ), ); notifyListeners(); - getUserImages(); + if (searchedChats!.isNotEmpty) { + getUserImages(); + } } Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async { @@ -135,6 +140,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { if (isNewChat) userChatHistory = []; if (!loadMore) paginationVal = 0; isChatScreenActive = true; + // if (chatHubConnection.state != HubConnectionState.Connected) { + // getUserAutoLoginToken().whenComplete(() async { + // await buildHubConnection(); + // getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore); + // }); + // return; + // } Response response = await ChatApiClient().getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); if (response.statusCode == 204) { if (isNewChat) { @@ -314,8 +326,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future onMsgReceived(List? parameters) async { - List data = []; - List temp = []; + List data = [], temp = []; for (dynamic msg in parameters!) { data = getSingleUserChatModel(jsonEncode(msg)); temp = getSingleUserChatModel(jsonEncode(msg)); @@ -336,16 +347,22 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } } + dynamic contain = searchedChats!.where((ChatUser element) => element.id == data.first.currentUserId); + if (contain.isEmpty) { + searchedChats!.add(ChatUser(id: data.first.currentUserId, userName: data.first.currentUserName, unreadMessageCount: 0, isImageLoading: false, image: "", isImageLoaded: true, userStatus: 1)); + } + setMsgTune(); userChatHistory.insert(0, data.first); if (searchedChats != null && !isChatScreenActive) { for (ChatUser user in searchedChats!) { if (user.id == data.first.currentUserId) { - var tempCount = user.unreadMessageCount ?? 0; + int tempCount = user.unreadMessageCount ?? 0; user.unreadMessageCount = tempCount + 1; } } + sort(); } List list = [ @@ -355,6 +372,14 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); } + void sort() { + searchedChats!.sort( + (ChatUser a, ChatUser b) => b.unreadMessageCount!.compareTo( + a.unreadMessageCount!, + ), + ); + } + void onUserTyping(List? parameters) { for (ChatUser user in searchedChats!) { if (user.id == parameters![1] && parameters[0] == true) { @@ -448,34 +473,35 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { Uint8List? image, required bool isImageLoaded}) async { Uuid uuid = const Uuid(); - var contentNo = uuid.v4(); + String contentNo = uuid.v4(); - var msg = message.text; + String msg = message.text; SingleUserChatModel data = SingleUserChatModel( - 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); + 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; @@ -492,7 +518,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, isImageLoading: false, image: "", isImageLoaded: true), ); notifyListeners(); } @@ -729,11 +755,25 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } } + notifyListeners(); } - ///getUserAutoLoginToken().whenComplete(() { -// buildHubConnection(); -// print("After Reconnect State: " + hubConnection.state.toString()); -// }); + void setMsgTune() async { + AudioPlayer player = AudioPlayer(); + await player.setVolume(1.0); + String audioAsset = ""; + if (Platform.isAndroid) { + audioAsset = "assets/audio/pulse_tone_android.mp3"; + } else { + audioAsset = "assets/audio/pulse_tune_ios.caf"; + } + try { + await player.setAsset(audioAsset); + await player.load(); + player.play(); + } catch (e) { + print("Error: $e"); + } + } } diff --git a/lib/ui/chat/chat_bubble.dart b/lib/ui/chat/chat_bubble.dart index ec772f6..ea43004 100644 --- a/lib/ui/chat/chat_bubble.dart +++ b/lib/ui/chat/chat_bubble.dart @@ -239,8 +239,7 @@ class ChatBubble extends StatelessWidget { return SizedBox( height: isReplyPreview ? 32 : 140, width: isReplyPreview ? 32 : 227, - child: const Center(child: CircularProgressIndicator()), - ); + ).toShimmer(); } }, ); diff --git a/lib/ui/chat/chat_detailed_screen.dart b/lib/ui/chat/chat_detailed_screen.dart index f027915..2ab5fd8 100644 --- a/lib/ui/chat/chat_detailed_screen.dart +++ b/lib/ui/chat/chat_detailed_screen.dart @@ -16,13 +16,11 @@ import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.da import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/ui/chat/call/chat_outgoing_call_screen.dart'; import 'package:mohem_flutter_app/ui/chat/chat_bubble.dart'; -import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:signalr_netcore/signalr_client.dart'; -import 'package:sizer/sizer.dart'; import 'package:swipe_to/swipe_to.dart'; class ChatDetailScreen extends StatefulWidget { @@ -34,9 +32,9 @@ class ChatDetailScreen extends StatefulWidget { } class _ChatDetailScreenState extends State { + final RefreshController _rc = RefreshController(initialRefresh: false); dynamic userDetails; late ChatProviderModel data; - final RefreshController _rc = RefreshController(initialRefresh: false); void getMoreChat() async { if (userDetails != null) { @@ -165,6 +163,10 @@ class _ChatDetailScreenState extends State { ), if (m.isFileSelected && m.sFileType == ".png" || m.sFileType == ".jpeg" || m.sFileType == ".jpg") SizedBox(height: 200, width: double.infinity, child: Image.file(m.selectedFile, fit: BoxFit.cover)).paddingOnly(left: 21, right: 21, top: 21), + const Divider( + height: 1, + color: MyColors.lightGreyEFColor, + ), TextField( controller: m.message, decoration: InputDecoration( diff --git a/lib/ui/chat/chat_home.dart b/lib/ui/chat/chat_home.dart index 4e218e3..76aa027 100644 --- a/lib/ui/chat/chat_home.dart +++ b/lib/ui/chat/chat_home.dart @@ -85,6 +85,7 @@ class _ChatHomeState extends State { onPageChanged: (int pageIndex) { setState(() { tabIndex = pageIndex; + }); }, children: [ diff --git a/lib/ui/chat/chat_home_screen.dart b/lib/ui/chat/chat_home_screen.dart index 641c13d..804332a 100644 --- a/lib/ui/chat/chat_home_screen.dart +++ b/lib/ui/chat/chat_home_screen.dart @@ -19,6 +19,7 @@ import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_s import 'package:mohem_flutter_app/widgets/circular_avatar.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:provider/provider.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; class ChatHomeScreen extends StatefulWidget { const ChatHomeScreen({Key? key}) : super(key: key); @@ -31,6 +32,8 @@ class _ChatHomeScreenState extends State { TextEditingController search = TextEditingController(); late ChatProviderModel data; + final RefreshController _rc = RefreshController(initialRefresh: false); + @override void initState() { super.initState(); @@ -86,7 +89,7 @@ class _ChatHomeScreenState extends State { itemCount: m.searchedChats!.length, shrinkWrap: true, physics: const ClampingScrollPhysics(), - padding: const EdgeInsets.only(bottom: 80.0), + padding: const EdgeInsets.only(bottom: 80.0), itemBuilder: (BuildContext context, int index) { return SizedBox( height: 55, @@ -99,7 +102,7 @@ class _ChatHomeScreenState extends State { height: 48, width: 48, ).toShimmer().circle(30), - if (m.searchedChats![index].isImageLoaded! && m.searchedChats![index].image != null && m.searchedChats![index].image.isNotEmpty) + if (m.searchedChats![index].isImageLoaded! && m.searchedChats![index].image.isNotEmpty) CircularAvatar( radius: 20, height: 48, @@ -125,7 +128,9 @@ class _ChatHomeScreenState extends State { ).circle(10), ) ], - ), + ).onPress(() { + print(jsonEncode(m.searchedChats![index])); + }), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/ui/chat/favorite_users_screen.dart b/lib/ui/chat/favorite_users_screen.dart index 6034151..9378625 100644 --- a/lib/ui/chat/favorite_users_screen.dart +++ b/lib/ui/chat/favorite_users_screen.dart @@ -23,13 +23,16 @@ class ChatFavoriteUsersScreen extends StatelessWidget { body: Consumer( builder: (BuildContext context, ChatProviderModel m, Widget? child) { if (m.isLoading) { - return ChatHomeShimmer(isDetailedScreen: false,); + return ChatHomeShimmer( + isDetailedScreen: false, + ); } else { return m.favUsersList != null && m.favUsersList.isNotEmpty ? ListView.separated( itemCount: m.favUsersList!.length, shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + physics: const ClampingScrollPhysics(), + padding: const EdgeInsets.only(bottom: 80.0), itemBuilder: (BuildContext context, int index) { return SizedBox( height: 55, diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index f06a405..4e6988b 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -6,7 +6,6 @@ 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/utils.dart'; @@ -322,9 +321,11 @@ class _DashboardScreenState extends State { ), ], ).paddingOnly(left: 21, right: 21, top: 7), - context.watch().isLoading ? MarathonBannerShimmer().paddingAll(20) : MarathonBanner().paddingAll(20), + context.watch().isLoading + ? const MarathonBannerShimmer().paddingAll(20) + : MarathonBanner(isMarathonUpcoming: context.watch().isUpComingMarathon).paddingAll(20), ServicesWidget(), - // 8.height, + 8.height, Container( width: double.infinity, padding: const EdgeInsets.only(top: 31), diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index e225459..a0ab3c9 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -25,6 +25,7 @@ import 'package:mohem_flutter_app/models/member_login_list_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/input_widget.dart'; +import 'package:safe_device/safe_device.dart'; class LoginScreen extends StatefulWidget { LoginScreen({Key? key}) : super(key: key); @@ -48,10 +49,34 @@ class _LoginScreenState extends State { bool? isAppOpenBySystem; + bool isJailBroken = false; + bool isRealDevice = false; + bool isOnExternalStorage = false; + bool isDevelopmentModeEnable = false; + @override void initState() { super.initState(); - // checkFirebaseToken(); + // checkFirebaseToken(); + if (kReleaseMode) { + checkDeviceSafety(); + } + } + + void checkDeviceSafety() async { + try { + isJailBroken = await SafeDevice.isJailBroken; + isRealDevice = await SafeDevice.isRealDevice; + if (Platform.isAndroid) { + isOnExternalStorage = await SafeDevice.isOnExternalStorage; + isDevelopmentModeEnable = await SafeDevice.isDevelopmentModeEnable; + } + if (isJailBroken || !isRealDevice || isOnExternalStorage || isDevelopmentModeEnable) { + Navigator.pushNamedAndRemoveUntil(context, AppRoutes.unsafeDeviceScreen, (_) => false); + } + } catch (error) { + print(error); + } } @override @@ -68,8 +93,7 @@ class _LoginScreenState extends State { await Firebase.initializeApp(); _firebaseMessaging = FirebaseMessaging.instance; firebaseToken = await _firebaseMessaging.getToken(); - loginInfo = await LoginApiClient().getMobileLoginInfoNEW( - firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); + loginInfo = await LoginApiClient().getMobileLoginInfoNEW(firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); if (loginInfo == null) { Utils.hideLoading(context); return; @@ -86,11 +110,9 @@ class _LoginScreenState extends State { } Future checkPrefs() async { - String username = - await Utils.getStringFromPrefs(SharedPrefsConsts.username); + String username = await Utils.getStringFromPrefs(SharedPrefsConsts.username); if (username.isNotEmpty) { - String password = - await Utils.getStringFromPrefs(SharedPrefsConsts.password); + String password = await Utils.getStringFromPrefs(SharedPrefsConsts.password); // String firebaseToken = await Utils.getStringFromPrefs(SharedPrefsConsts.firebaseToken); // print("firebaseToken:$firebaseToken"); this.username.text = username; @@ -103,30 +125,23 @@ class _LoginScreenState extends State { Utils.showLoading(context); try { _checkMobileAppVersion = await LoginApiClient().checkMobileAppVersion(); - _memberLoginList = - await LoginApiClient().memberLogin(username.text, password.text); + _memberLoginList = await LoginApiClient().memberLogin(username.text, password.text); AppState().setMemberLoginListModel = _memberLoginList; AppState().setUserName = username.text; AppState().password = password.text; if (_autoLogin) { - AppState().setMemberInformationListModel = - (await MemberInformationListModel.getFromPrefs()).first; - AppState().setPrivilegeListModel = - await PrivilegeListModel.getFromPrefs(); - String mohemmWifiSSID = - await Utils.getStringFromPrefs(SharedPrefsConsts.mohemmWifiSSID); - String mohemmWifiPassword = await Utils.getStringFromPrefs( - SharedPrefsConsts.mohemmWifiPassword); + AppState().setMemberInformationListModel = (await MemberInformationListModel.getFromPrefs()).first; + AppState().setPrivilegeListModel = await PrivilegeListModel.getFromPrefs(); + String mohemmWifiSSID = await Utils.getStringFromPrefs(SharedPrefsConsts.mohemmWifiSSID); + String mohemmWifiPassword = await Utils.getStringFromPrefs(SharedPrefsConsts.mohemmWifiPassword); AppState().setMohemmWifiSSID = mohemmWifiSSID; AppState().setMohemmWifiPassword = mohemmWifiPassword; } Utils.hideLoading(context); if (_autoLogin) { - Navigator.pushReplacementNamed(context, AppRoutes.verifyLastLogin, - arguments: loginInfo); + Navigator.pushReplacementNamed(context, AppRoutes.verifyLastLogin, arguments: loginInfo); } else { - Navigator.pushNamed(context, AppRoutes.verifyLogin, - arguments: "$firebaseToken"); + Navigator.pushNamed(context, AppRoutes.verifyLogin, arguments: "$firebaseToken"); } Utils.saveStringFromPrefs(SharedPrefsConsts.password, password.text); } catch (ex) { @@ -169,13 +184,7 @@ class _LoginScreenState extends State { Expanded(child: SizedBox()), Row( children: [ - LocaleKeys.english - .tr() - .toText14( - color: AppState().isArabic(context) - ? null - : MyColors.textMixColor) - .onPress(() { + LocaleKeys.english.tr().toText14(color: AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() { context.setLocale(const Locale("en", "US")); }), Container( @@ -184,13 +193,7 @@ class _LoginScreenState extends State { height: 16, margin: const EdgeInsets.only(left: 10, right: 10), ), - LocaleKeys.arabic - .tr() - .toText14( - color: !AppState().isArabic(context) - ? null - : MyColors.textMixColor) - .onPress(() { + LocaleKeys.arabic.tr().toText14(color: !AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() { context.setLocale(const Locale("ar", "SA")); }), ], @@ -206,23 +209,14 @@ class _LoginScreenState extends State { LocaleKeys.login.tr().toText24(isBold: true), LocaleKeys.pleaseEnterLoginDetails.tr().toText16(), 16.height, - InputWidget( - LocaleKeys.username.tr(), "123456", username), + InputWidget(LocaleKeys.username.tr(), "123456", username), 12.height, - InputWidget( - LocaleKeys.password.tr(), "xxxxxx", password, - isTextIsPassword: true), + InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true), 9.height, Align( alignment: Alignment.centerRight, - child: LocaleKeys.forgotPassword - .tr() - .toText12( - isUnderLine: true, - color: MyColors.textMixColor) - .onPress(() { - Navigator.pushNamed( - context, AppRoutes.forgotPassword); + child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(() { + Navigator.pushNamed(context, AppRoutes.forgotPassword); }), ), ], diff --git a/lib/ui/marathon/marathon_intro_screen.dart b/lib/ui/marathon/marathon_intro_screen.dart index d85a82f..f47b47e 100644 --- a/lib/ui/marathon/marathon_intro_screen.dart +++ b/lib/ui/marathon/marathon_intro_screen.dart @@ -1,21 +1,13 @@ 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'; 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_provider.dart'; -import 'package:mohem_flutter_app/ui/marathon/widgets/countdown_timer.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_details_card.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_footer.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_timer_card.dart'; 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'; class MarathonIntroScreen extends StatelessWidget { @@ -33,7 +25,10 @@ class MarathonIntroScreen extends StatelessWidget { children: [ MarathonDetailsCard(provider: provider), 10.height, - MarathonTimerCard(provider: provider, timeToMarathon: DateTime.parse(provider.marathonDetailModel.startTime!).millisecondsSinceEpoch,), + MarathonTimerCard( + provider: provider, + timeToMarathon: DateTime.parse(provider.marathonDetailModel.startTime!).millisecondsSinceEpoch, + ), ], ).expanded, 1.divider, @@ -43,180 +38,3 @@ class MarathonIntroScreen extends StatelessWidget { ); } } - -class MarathonDetailsCard extends StatelessWidget { - final MarathonProvider provider; - - const MarathonDetailsCard({Key? key, required this.provider}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 14), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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(); - }, - ) - ], - ), - ] - ], - ), - ], - ), - ); - } -} - -class MarathonTimerCard extends StatelessWidget { - final int timeToMarathon; - final MarathonProvider provider; - - const MarathonTimerCard({ - Key? key, - required this.provider, - required this.timeToMarathon, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 14), - child: Column( - children: [ - Row( - children: [ - "${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.grey77Color), - DateUtil.formatDateToTimeLang(DateTime.parse(provider.marathonDetailModel.startTime!), AppState().isArabic(context)).toText16(color: MyColors.darkTextColor, isBold: true), - ], - ), - Lottie.asset(MyLottieConsts.hourGlassLottie, height: 200), - BuildCountdownTimer(timeToMarathon: timeToMarathon, provider: provider, screenFlag: 1), - ], - ), - ); - } -} - -class MarathonFooter extends StatelessWidget { - final MarathonProvider provider; - - const MarathonFooter({ - Key? key, - required this.provider, - }) : super(key: key); - - Widget buildNoteForDemo() { - return RichText( - text: TextSpan( - children: [ - TextSpan( - text: LocaleKeys.note.tr(), - style: const TextStyle(color: MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.bold), - ), - TextSpan( - text: " " + LocaleKeys.demoMarathonNoteP1.tr(), - style: const TextStyle(color: MyColors.grey77Color, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.w500), - ), - TextSpan( - text: " " + LocaleKeys.demoMarathonNoteP2.tr(), - style: const TextStyle(color: MyColors.darkTextColor, fontSize: 17, fontWeight: FontWeight.bold), - ), - TextSpan( - text: " " + LocaleKeys.demoMarathonNoteP3.tr(), - style: const TextStyle(color: MyColors.grey77Color, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.w500), - ) - ], - ), - ).paddingOnly(right: 21, left: 21, top: 11, bottom: 0); - } - - @override - Widget build(BuildContext context) { - return !provider.itsMarathonTime - ? DefaultButton( - LocaleKeys.joinMarathon.tr(), - () 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, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - 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 b629b36..1cf8efd 100644 --- a/lib/ui/marathon/marathon_provider.dart +++ b/lib/ui/marathon/marathon_provider.dart @@ -2,62 +2,46 @@ import 'dart:async'; import 'package:appinio_swiper/appinio_swiper.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mohem_flutter_app/api/marathon/marathon_api_client.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; import 'package:mohem_flutter_app/models/marathon/question_model.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; +import 'package:video_player/video_player.dart'; class MarathonProvider extends ChangeNotifier { + //****************VARIABLES********** + final AppinioSwiperController swiperController = AppinioSwiperController(); MarathonDetailModel marathonDetailModel = MarathonDetailModel(); List cardContentList = []; QuestionModel currentQuestion = QuestionModel(); - + List answerStatusesList = []; 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(); - } + String? selectedOptionId; + int totalQualifiers = 0; - void addItemToList(CardContent value) { - cardContentList.add(value); - notifyListeners(); - } + bool _isLoading = false; - 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(); - } + bool get isLoading => _isLoading; - void updateQuestionCardStatus(QuestionCardStatus status) { - questionCardStatus = status; + set isLoading(bool value) { + _isLoading = value; notifyListeners(); } - bool _isLoading = false; + bool _isUpComingMarathon = true; - bool get isLoading => _isLoading; + bool get isUpComingMarathon => _isUpComingMarathon; - set isLoading(bool value) { - _isLoading = value; + set isUpComingMarathon(bool value) { + _isUpComingMarathon = value; notifyListeners(); } @@ -79,6 +63,13 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } + bool isUserOutOfGame = false; + + set updateIsUserOutOfGame(bool value) { + isUserOutOfGame = value; + notifyListeners(); + } + int _currentQuestionNumber = 0; int get currentQuestionNumber => _currentQuestionNumber; @@ -88,7 +79,7 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } - int _totalMarathoners = 23; + int _totalMarathoners = 0; int get totalMarathoners => _totalMarathoners; @@ -97,8 +88,202 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } - void swipeCardLeft() { - swiperController.swipeLeft(); + //****************SPONSOR VIDEO PLAYER********** + + late VideoPlayerController videoController; + + Future initializeVideoPlayer() async { + // videoController = VideoPlayerController.network(marathonDetailModel.sponsors!.first.video!)..initialize(); + videoController = VideoPlayerController.network("http://clips.vorwaerts-gmbh.de/VfE_html5.mp4"); + await videoController.initialize(); + await videoController.play(); + await videoController.setVolume(1.0); + await videoController.setLooping(false); + totalSponsorVideoSeconds = videoController.value.duration.inSeconds; + notifyListeners(); + } + + void disposeVideoPlayer() { + videoController.dispose(); + notifyListeners(); + } + + //****************TIMERS********** + + int totalSponsorVideoSeconds = 0; + Timer timerForSponsorVideo = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); + + void startTimerForSponsorVideo() { + const Duration oneSec = Duration(seconds: 1); + timerForSponsorVideo = Timer.periodic( + oneSec, + (Timer timer) async { + if (totalSponsorVideoSeconds == 0) { + timer.cancel(); + notifyListeners(); + return; + } else { + totalSponsorVideoSeconds--; + } + + notifyListeners(); + }, + ); + } + + int totalSecondsToWaitForMarathon = 20; + Timer timerToWaitForMarathon = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); + + void startTimerToMarathon(BuildContext context) { + const Duration oneSec = Duration(seconds: 1); + timerToWaitForMarathon = Timer.periodic( + oneSec, + (Timer timer) async { + if (totalSecondsToWaitForMarathon == 0) { + } else { + totalSecondsToWaitForMarathon--; + } + notifyListeners(); + }, + ); + } + + int totalCurrentQuestionTime = 0; + int currentGapTime = 0; + Timer timerForQuestion = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); + + void startTimerForQuestion() { + const Duration oneSec = Duration(seconds: 1); + timerForQuestion = Timer.periodic( + oneSec, + (Timer timer) async { + // This 2 is just to show the color of answer tile for 2 seconds and then update card status + if (totalCurrentQuestionTime - currentGapTime == currentQuestion.questionTime! - 2) { + getCorrectAnswerAndUpdateAnswerColor(); + } + + if (totalCurrentQuestionTime == currentGapTime) { + updateCardStatusToAnswer(); + + await callSubmitOptionApi().then((bool value) async { + if (value) { + await callNextQuestionApi(); + } + }); + } + + if (totalCurrentQuestionTime == 0) { + updateCardData(); + if (currentQuestionNumber == marathonDetailModel.totalQuestions! - 1) { + updateQuestionCardStatus(QuestionCardStatus.findingWinner); + timer.cancel(); + cancelTimer(); + notifyListeners(); + } + return; + } else { + totalCurrentQuestionTime--; + } + + notifyListeners(); + }, + ); + } + + int totalSecondsToWaitForWinner = 30; + Timer timerForWinnerSelection = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); + + void startTimerForWinnerSelection() { + const Duration oneSec = Duration(seconds: 1); + timerForWinnerSelection = Timer.periodic( + oneSec, + (Timer timer) async { + if (totalSecondsToWaitForWinner == 0) { + timer.cancel(); + updateQuestionCardStatus(QuestionCardStatus.winnerFound); + return; + } else { + totalSecondsToWaitForWinner--; + } + + notifyListeners(); + }, + ); + } + + //****************FUNCTIONS********* + + Future callSubmitOptionApi() async { + return await MarathonApiClient().submitSelectedOption(selectedAnswerId: selectedOptionId); + } + + // TODO: here I need to add a logic where I should call this function for Api but for the 1st question it should behave differently + // TODO: Verify the callings!!! + Future callNextQuestionApi() async { + if (currentQuestionNumber < marathonDetailModel.totalQuestions!) { + if (currentQuestionNumber == 0) { + currentQuestion = (await MarathonApiClient().getNextQuestion(questionId: null, marathonId: marathonDetailModel.id!))!; + if (Utils.isLoading) { + Utils.hideLoading(AppRoutes.navigatorKey.currentContext!); + } + startTimerForQuestion(); + updateCardData(); + + Navigator.pushReplacementNamed(AppRoutes.navigatorKey.currentContext!, AppRoutes.marathonScreen); + } else { + currentQuestion = (await MarathonApiClient().getNextQuestion(questionId: currentQuestion.id, marathonId: marathonDetailModel.id!))!; + } + notifyListeners(); + } + } + + void updateCardData() { + if (currentQuestionNumber > 0) { + print("swiped it away!!"); + swipeCardLeft(); + } + selectedOptionIndex = null; + currentQuestionNumber++; + cardContentList.add(const CardContent()); + totalCurrentQuestionTime = currentQuestion.questionTime! + currentQuestion.nextQuestGap!; + currentGapTime = currentQuestion.nextQuestGap!; + totalMarathoners = currentQuestion.remainingParticipantCount!; + questionCardStatus = QuestionCardStatus.question; + } + + void populateQuestionStatusesList() { + if (marathonDetailModel.totalQuestions != null) { + for (int i = 0; i < marathonDetailModel.totalQuestions! - 1; i++) { + answerStatusesList.add(QuestionCardStatus.question); + } + notifyListeners(); + } + } + + void updateAnswerStatusesList(QuestionCardStatus status) { + answerStatusesList[currentQuestionNumber - 1] = status; + notifyListeners(); + } + + void addItemToList(CardContent value) { + cardContentList.add(value); + notifyListeners(); + } + + void updateCurrentQuestionOptionStatus(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) { + if (status == QuestionCardStatus.wrongAnswer || status == QuestionCardStatus.skippedAnswer) { + updateIsUserOutOfGame = true; + } + questionCardStatus = status; notifyListeners(); } @@ -120,59 +305,36 @@ class MarathonProvider extends ChangeNotifier { if (selectedOptionIndex != null) { if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { updateQuestionCardStatus(QuestionCardStatus.correctAnswer); + updateAnswerStatusesList(QuestionCardStatus.correctAnswer); } else { updateQuestionCardStatus(QuestionCardStatus.wrongAnswer); + updateAnswerStatusesList(QuestionCardStatus.wrongAnswer); } } else { updateQuestionCardStatus(QuestionCardStatus.skippedAnswer); + updateAnswerStatusesList(QuestionCardStatus.skippedAnswer); } } - Timer timerU = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); - - void startTimer(BuildContext context) { - const Duration oneSec = Duration(seconds: 1); - timerU = Timer.periodic( - oneSec, - (Timer timer) async { - 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 { - currentQuestionTime--; - } - notifyListeners(); - }, - ); + void swipeCardLeft() { + swiperController.swipeLeft(); + notifyListeners(); } - void resetValues() { + void resetValues() async { _currentQuestionNumber = 0; cardContentList.clear(); - timerU.cancel(); + timerForWinnerSelection.cancel(); + timerForQuestion.cancel(); _isMarathonCompleted = false; - currentQuestionTime = 0; + totalCurrentQuestionTime = 0; currentQuestion = QuestionModel(); + notifyListeners(); } void cancelTimer() { - timerU.cancel(); + timerForQuestion.cancel(); notifyListeners(); } @@ -181,12 +343,51 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); await MarathonApiClient().getMarathonToken().whenComplete(() async { marathonDetailModel = await MarathonApiClient().getMarathonDetails(); + if (marathonDetailModel.id == null) { + isUpComingMarathon = false; + notifyListeners(); + return; + } + populateQuestionStatusesList(); isLoading = false; notifyListeners(); }); } - Future connectSignalrAndJoinMarathon(BuildContext context) async { - await MarathonApiClient().buildHubConnection(context); + Future buildConnectionWithSignalR(BuildContext context) async { + Utils.showLoading(context); + try { + resetValues(); + // await MarathonApiClient().buildHubConnection(context, marathonDetailModel.sponsors!.first.sponsorPrizes!.first.id!); + } catch (e) { + if (kDebugMode) { + print("error in buildConnectionWithSignalR: ${e.toString()}"); + } + Utils.hideLoading(context); + Utils.confirmDialog(context, e.toString()); + } + } + + Future onJoinMarathonPressed(BuildContext context) async { + //TODO: here we need to put a check to make sure we should not display sponsor when remaining time to marathon is less than 30 seconds plus video duration e.g. 30 seconds + video duration time + // if (marathonDetailModel.sponsors!.first.video != null && marathonDetailModel.sponsors!.first.video != "") { + if (false) { + await initializeVideoPlayer().then((_) { + startTimerForSponsorVideo(); + Navigator.pushNamed(context, AppRoutes.marathonSponsorVideoScreen); + }); + } else { + try { + Utils.showLoading(context); + bool isJoined = await MarathonApiClient().joinMarathonAsParticipant(); + if (isJoined) { + print("joined"); + callNextQuestionApi(); + } + } catch (e, s) { + Utils.hideLoading(context); + Utils.confirmDialog(context, e.toString()); + } + } } } diff --git a/lib/ui/marathon/marathon_screen.dart b/lib/ui/marathon/marathon_screen.dart index 79d61fe..e14f8c9 100644 --- a/lib/ui/marathon/marathon_screen.dart +++ b/lib/ui/marathon/marathon_screen.dart @@ -2,16 +2,22 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/classes/lottie_consts.dart'; +import 'package:mohem_flutter_app/classes/utils.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/models/marathon/question_model.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/marathon_progress_container.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_qualifiers_container.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'; @@ -20,46 +26,164 @@ import 'package:provider/provider.dart'; class MarathonScreen extends StatelessWidget { const MarathonScreen({Key? key}) : super(key: key); + Widget getSuccessWidget({required int? gapType, required String? gapImage, required String? gapText}) { + if (gapType == 1) { + if (gapText == null) { + return Image.asset(MyLottieConsts.congratsGif, height: 200); + } + return gapText.toText18(color: MyColors.darkTextColor, isCentered: true); + } + if (gapType == 2) { + if (gapImage == null) { + return Image.asset(MyLottieConsts.congratsGif, height: 200); + } + return Image.network(gapImage, height: 200); + } + return Image.asset(MyLottieConsts.congratsGif, height: 200); + } + + Widget getWinnerWidget(BuildContext context, {required MarathonProvider provider}) { + return Container( + width: double.infinity, + decoration: MyDecorations.shadowDecoration, + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 200, + child: Stack( + children: [ + Lottie.asset(MyLottieConsts.celebrate1Lottie, height: 200), + Lottie.asset(MyLottieConsts.celebrate2Lottie, height: 200), + ], + ), + ), + 26.height, + SizedBox( + height: 50, + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: SvgPicture.asset("assets/images/winner_ribbon.svg", height: 50), + ), + Align( + alignment: Alignment.center, + child: LocaleKeys.winner.tr().toText32(color: MyColors.white, isBold: true).paddingOnly(top: 07), + ) + ], + ), + ), + 12.height, + "Muhammad Shrouff".toText22(color: MyColors.grey3AColor), + "837436".toText22(color: MyColors.grey57Color), + 80.height, + if (provider.marathonDetailModel.sponsors != null && provider.marathonDetailModel.sponsors!.isNotEmpty) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + "${LocaleKeys.sponsoredBy.tr()} ".toText14(color: MyColors.grey77Color), + (AppState().isArabic(context) ? provider.marathonDetailModel.sponsors!.first.nameAr ?? "" : provider.marathonDetailModel.sponsors!.first.nameEn ?? "").toText14( + color: MyColors.darkTextColor, + isBold: true, + ), + ], + ), + 5.height, + Image.network( + provider.marathonDetailModel.sponsors!.first.image!, + height: 40, + width: 150, + fit: BoxFit.fill, + errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + return const Center(); + }, + ) + ], + ], + ), + ); + } + + Widget getNameContainer(BuildContext context) { + return Container( + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration( + color: MyColors.greenColor, + borderRadius: BorderRadius.circular(15), + boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: 26, offset: const Offset(0, -3))], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + (AppState().isArabic(context) ? AppState().memberInformationList!.eMPLOYEEDISPLAYNAMEEn! : AppState().memberInformationList!.eMPLOYEEDISPLAYNAMEAr!) + .toText17(isBold: true, color: MyColors.white), + AppState().memberInformationList!.eMPLOYEENUMBER!.toText17(isBold: true, color: MyColors.white), + ], + ), + ).paddingOnly(left: 20, right: 20, top: 12, bottom: 20); + } + @override Widget build(BuildContext context) { MarathonProvider provider = context.watch(); return WillPopScope( child: Scaffold( - appBar: AppBarWidget(context, title: LocaleKeys.brainMarathon.tr()), + appBar: AppBarWidget( + context, + title: LocaleKeys.brainMarathon.tr(), + onHomeTapped: () { + Utils.confirmDialog(context, LocaleKeys.youWantToLeaveMarathon.tr()); + }, + onBackTapped: () { + Utils.confirmDialog(context, LocaleKeys.youWantToLeaveMarathon.tr()); + }, + ), body: SingleChildScrollView( child: Column( children: [ 20.height, - MarathonProgressContainer(provider: provider).paddingOnly(left: 21, right: 21), + if (provider.questionCardStatus == QuestionCardStatus.findingWinner) ...[ + QualifiersContainer(provider: provider).paddingOnly(left: 21, right: 21), + ] else if (provider.questionCardStatus == QuestionCardStatus.winnerFound) + ...[] + else ...[ + MarathonProgressContainer(provider: provider).paddingOnly(left: 21, right: 21), + ], + if (provider.questionCardStatus == QuestionCardStatus.findingWinner) ...[ + getNameContainer(context), + ], 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), + subTitle: LocaleKeys.allQuestionsCorrect.toText18(color: MyColors.darkTextColor, isCentered: true), ), onCorrectAnswer: (BuildContext context) => CustomStatusWidget( - asset: Lottie.asset(MyLottieConsts.allQuestions, height: 200), + asset: getSuccessWidget(gapType: provider.currentQuestion.gapType, gapImage: provider.currentQuestion.gapImage, gapText: provider.currentQuestion.gapText), title: LocaleKeys.congrats.tr().toText22(color: MyColors.greenColor), - subTitle: LocaleKeys.yourAnswerCorrect.toText18(color: MyColors.darkTextColor), + subTitle: LocaleKeys.yourAnswerCorrect.toText18(color: MyColors.darkTextColor, isCentered: true), ), - onWinner: (BuildContext context) => QuestionCard(provider: provider), + onWinner: (BuildContext context) => getWinnerWidget(context, provider: provider), onWrongAnswer: (BuildContext context) => CustomStatusWidget( asset: Image.asset(MyLottieConsts.wrongAnswerGif, height: 200), - title: const Text(""), - subTitle: LocaleKeys.wrongAnswer.tr().toText18(color: MyColors.darkTextColor), + title: LocaleKeys.oops.tr().toText22(color: MyColors.redColor), + subTitle: LocaleKeys.wrongAnswer.tr().toText18(color: MyColors.darkTextColor, isCentered: true), ), onSkippedAnswer: (BuildContext context) => CustomStatusWidget( asset: Image.asset(MyLottieConsts.wrongAnswerGif, height: 200), - title: const Text(""), - subTitle: LocaleKeys.youMissedTheQuestion.tr().toText18(color: MyColors.darkTextColor), + title: LocaleKeys.oops.tr().toText22(color: MyColors.redColor), + subTitle: LocaleKeys.youMissedTheQuestion.tr().toText18(color: MyColors.darkTextColor, isCentered: true), ), - 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), + subTitle: LocaleKeys.winnerSelectedRandomly.tr().toText18(color: MyColors.darkTextColor, isCentered: true), ), + questionCardStatus: provider.questionCardStatus, ).paddingOnly(top: 12, left: 21, right: 21), ], ), @@ -72,127 +196,3 @@ class MarathonScreen extends StatelessWidget { ); } } - -class MarathonProgressContainer extends StatefulWidget { - final MarathonProvider provider; - - const MarathonProgressContainer({Key? key, required this.provider}) : super(key: key); - - @override - State createState() => _MarathonProgressContainerState(); -} - -class _MarathonProgressContainerState extends State { - @override - void initState() { - scheduleMicrotask(() { - widget.provider.startTimer(context); - }); - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 13), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 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.marathonDetailModel.totalQuestions.toString()} ${LocaleKeys.question.tr()}" - .toText12(color: MyColors.white), - ), - "${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, - stepper(widget.provider.currentQuestionNumber), - 8.height, - Row( - children: [ - "${((widget.provider.currentQuestionNumber / widget.provider.marathonDetailModel.totalQuestions!) * 100).toInt()}% ${LocaleKeys.completed.tr()}".toText14(), - ], - ), - ], - ), - ); - } - - Widget stepper(int value) { - return SizedBox( - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - for (int i = 0; i < 10; i++) - if (value <= i) roundContainer(MyColors.lightGreyDeColor, i != 0) else roundContainer(MyColors.greenColor, i != 0) - ], - ), - ); - } - - Widget roundContainer(Color color, bool isNeedLeftBorder) { - if (isNeedLeftBorder) { - return Row( - children: [ - Divider(thickness: 6, color: color).expanded, - Container( - width: 10, - height: 10, - decoration: BoxDecoration(shape: BoxShape.circle, color: color), - ), - ], - ).expanded; - } - - return Container( - width: 10, - height: 10, - decoration: BoxDecoration(shape: BoxShape.circle, color: color), - ); - } -} - -// 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_sponsor_video_screen.dart b/lib/ui/marathon/marathon_sponsor_video_screen.dart new file mode 100644 index 0000000..c81f59b --- /dev/null +++ b/lib/ui/marathon/marathon_sponsor_video_screen.dart @@ -0,0 +1,91 @@ +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/config/routes.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/ui/marathon/marathon_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:video_player/video_player.dart'; + +class SponsorVideoScreen extends StatelessWidget { + const SponsorVideoScreen({Key? key}) : super(key: key); + + Future onSponsorVideoClosed(BuildContext context) async { + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); + return WillPopScope( + onWillPop: () { + provider.videoController.dispose(); + return Future.value(true); + }, + child: Scaffold( + backgroundColor: MyColors.black, + body: SafeArea( + child: Stack( + children: [ + Align( + child: provider.videoController.value.isInitialized + ? AspectRatio( + aspectRatio: provider.videoController.value.aspectRatio, + child: VideoPlayer(provider.videoController), + ) + : Container(color: Colors.white), + ), + Align( + alignment: Alignment.topRight, + child: Container( + decoration: BoxDecoration( + color: MyColors.white, + shape: provider.totalSponsorVideoSeconds == 0 ? BoxShape.circle : BoxShape.rectangle, + borderRadius: provider.totalSponsorVideoSeconds == 0 ? null : BorderRadius.circular(15), + ), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8), + child: provider.totalSponsorVideoSeconds == 0 + ? InkWell( + onTap: () { + Navigator.pop(context); + provider.videoController.dispose(); + provider.buildConnectionWithSignalR(AppRoutes.navigatorKey.currentState!.overlay!.context); + }, + child: const Icon(Icons.close, size: 12), + ) + : Directionality( + textDirection: ui.TextDirection.ltr, + child: ("${LocaleKeys.ourSponsor.tr()} ${provider.totalSponsorVideoSeconds < 10 ? "0" : ""}${provider.totalSponsorVideoSeconds}").toText12(color: MyColors.darkTextColor), + ), + ), + ).paddingOnly(top: 20, right: 18), + Align( + alignment: Alignment.topLeft, + child: InkWell( + onTap: () { + Navigator.pop(context); + provider.videoController.dispose(); + provider.buildConnectionWithSignalR(AppRoutes.navigatorKey.currentState!.overlay!.context); + }, + child: Container( + decoration: BoxDecoration(color: MyColors.white, borderRadius: BorderRadius.circular(15)), + padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 6), + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: LocaleKeys.skip.tr().toText11(color: MyColors.darkTextColor), + ), + ), + ), + ).paddingOnly(top: 20, left: 18), + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/marathon/marathon_waiting_screen.dart b/lib/ui/marathon/marathon_waiting_screen.dart new file mode 100644 index 0000000..27f0c08 --- /dev/null +++ b/lib/ui/marathon/marathon_waiting_screen.dart @@ -0,0 +1,62 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +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/classes/utils.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/ui/marathon/marathon_provider.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:provider/provider.dart'; + +class MarathonWaitingScreen extends StatelessWidget { + const MarathonWaitingScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); + return Scaffold( + appBar: AppBarWidget( + context, + title: LocaleKeys.brainMarathon.tr(), + onHomeTapped: () { + Utils.confirmDialog(context, LocaleKeys.youWantToLeaveMarathon.tr()); + }, + onBackTapped: () { + Utils.confirmDialog(context, LocaleKeys.youWantToLeaveMarathon.tr()); + }, + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: double.infinity, + margin: const EdgeInsets.all(21), + decoration: MyDecorations.shadowDecoration, + child: Stack( + children: [ + Align( + child: Lottie.asset(MyLottieConsts.marathonWaiting, height: 200), + ), + Align( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LocaleKeys.startingIn.tr().toText16(), + "00:${provider.totalSecondsToWaitForMarathon < 10 ? "0${provider.totalSecondsToWaitForMarathon}" : provider.totalSecondsToWaitForMarathon}" + .toText18(color: provider.totalSecondsToWaitForMarathon < 5 ? MyColors.redColor : MyColors.black), + ], + ), + ), + ], + ), + ).expanded, + ], + ), + ); + } +} diff --git a/lib/ui/marathon/marathon_winner_selection.dart b/lib/ui/marathon/marathon_winner_selection.dart index 1f56801..ed49f04 100644 --- a/lib/ui/marathon/marathon_winner_selection.dart +++ b/lib/ui/marathon/marathon_winner_selection.dart @@ -1,10 +1,7 @@ -import 'dart:async'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; 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'; @@ -13,6 +10,7 @@ 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_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/custom_status_widget.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_qualifiers_container.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:provider/provider.dart'; @@ -28,7 +26,7 @@ class MarathonWinnerSelection extends StatelessWidget { child: Column( children: [ 20.height, - QualifiersContainer(provider: provider).paddingOnly(left: 21, right: 21), + QualifiersContainer(provider: provider,).paddingOnly(left: 21, right: 21), 12.height, InkWell( onTap: () { @@ -90,60 +88,3 @@ class MarathonWinnerSelection extends StatelessWidget { ); } } - -class QualifiersContainer extends StatefulWidget { - final MarathonProvider provider; - - const QualifiersContainer({Key? key, required this.provider}) : super(key: key); - - @override - State createState() => _QualifiersContainerState(); -} - -class _QualifiersContainerState extends State { - final int totalQuestions = 10; - - final int currentQuestion = 04; - - @override - void initState() { - scheduleMicrotask(() { - // widget.provider.startTimer(context); - }); - super.initState(); - } - - @override - void dispose() { - // widget.provider.cancelTimer(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.only(top: 14,left: 18,right: 14,bottom: 18), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - 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), - ], - ), - 10.height, - Row( - children: [ - "18".toText30(color: MyColors.greenColor, isBold: true),2.width, - LocaleKeys.qualifiers.tr().toText16(color: MyColors.greenColor), - ], - ), - ], - ), - ); - } -} diff --git a/lib/ui/marathon/widgets/custom_status_widget.dart b/lib/ui/marathon/widgets/custom_status_widget.dart index 8287bb4..44b44aa 100644 --- a/lib/ui/marathon/widgets/custom_status_widget.dart +++ b/lib/ui/marathon/widgets/custom_status_widget.dart @@ -22,6 +22,7 @@ class CustomStatusWidget extends StatelessWidget { decoration: MyDecorations.shadowDecoration, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ asset, 50.height, diff --git a/lib/ui/marathon/widgets/marathon_banner.dart b/lib/ui/marathon/widgets/marathon_banner.dart index e711319..d805362 100644 --- a/lib/ui/marathon/widgets/marathon_banner.dart +++ b/lib/ui/marathon/widgets/marathon_banner.dart @@ -16,7 +16,9 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/countdown_timer.dart'; import 'package:provider/provider.dart'; class MarathonBanner extends StatelessWidget { - const MarathonBanner({Key? key}) : super(key: key); + final bool isMarathonUpcoming; + + const MarathonBanner({Key? key, required this.isMarathonUpcoming}) : super(key: key); @override Widget build(BuildContext context) { @@ -76,7 +78,7 @@ class MarathonBanner extends StatelessWidget { height: double.infinity, ), ), - Expanded( + Expanded( flex: AppState().isArabic(context) ? 4 : 5, child: SizedBox( width: double.infinity, diff --git a/lib/ui/marathon/widgets/marathon_details_card.dart b/lib/ui/marathon/widgets/marathon_details_card.dart new file mode 100644 index 0000000..b74b78b --- /dev/null +++ b/lib/ui/marathon/widgets/marathon_details_card.dart @@ -0,0 +1,82 @@ + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.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/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; + + + +class MarathonDetailsCard extends StatelessWidget { + final MarathonProvider provider; + + const MarathonDetailsCard({Key? key, required this.provider}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: MyDecorations.shadowDecoration, + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 14), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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(); + }, + ) + ], + ), + ] + ], + ), + ], + ), + ); + } +} + diff --git a/lib/ui/marathon/widgets/marathon_footer.dart b/lib/ui/marathon/widgets/marathon_footer.dart new file mode 100644 index 0000000..b5083c0 --- /dev/null +++ b/lib/ui/marathon/widgets/marathon_footer.dart @@ -0,0 +1,66 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.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_provider.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; + +class MarathonFooter extends StatelessWidget { + final MarathonProvider provider; + + const MarathonFooter({ + Key? key, + required this.provider, + }) : super(key: key); + + Widget buildNoteForDemo() { + return RichText( + text: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.note.tr(), + style: const TextStyle(color: MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.bold), + ), + TextSpan( + text: " " + LocaleKeys.demoMarathonNoteP1.tr(), + style: const TextStyle(color: MyColors.grey77Color, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.w500), + ), + TextSpan( + text: " " + LocaleKeys.demoMarathonNoteP2.tr(), + style: const TextStyle(color: MyColors.darkTextColor, fontSize: 17, fontWeight: FontWeight.bold), + ), + TextSpan( + text: " " + LocaleKeys.demoMarathonNoteP3.tr(), + style: const TextStyle(color: MyColors.grey77Color, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.w500), + ) + ], + ), + ).paddingOnly(right: 21, left: 21, top: 11, bottom: 0); + } + + @override + Widget build(BuildContext context) { + return DefaultButton( + LocaleKeys.joinMarathon.tr(), + !provider.itsMarathonTime ? () => provider.onJoinMarathonPressed(context) : null, + ).insideContainer; + } +} + +//Container( +// color: Colors.white, +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// buildNoteForDemo(), +// DefaultButton( +// LocaleKeys.joinDemoMarathon.tr(), +// () { +// provider.connectSignalrAndJoinMarathon(context); +// }, +// color: MyColors.yellowColorII, +// ).insideContainer, +// ], +// ), +// ); diff --git a/lib/ui/marathon/widgets/marathon_progress_container.dart b/lib/ui/marathon/widgets/marathon_progress_container.dart new file mode 100644 index 0000000..c0dae6c --- /dev/null +++ b/lib/ui/marathon/widgets/marathon_progress_container.dart @@ -0,0 +1,115 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/decorations_helper.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/models/marathon/question_model.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; + +class MarathonProgressContainer extends StatelessWidget { + final MarathonProvider provider; + + const MarathonProgressContainer({Key? key, required this.provider}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: MyDecorations.shadowDecoration, + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 13), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + decoration: BoxDecoration(color: MyColors.greenColor, borderRadius: BorderRadius.circular(5)), + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8), + child: "${provider.currentQuestionNumber.toString()} / ${provider.marathonDetailModel.totalQuestions.toString()} ${LocaleKeys.question.tr()}".toText12(color: MyColors.white), + ), + "${provider.totalMarathoners} ${LocaleKeys.marathoners.tr()}".toText14(), + provider.questionCardStatus == QuestionCardStatus.question + ? "00:${(provider.totalCurrentQuestionTime - provider.currentGapTime) < 10 ? "0${provider.totalCurrentQuestionTime - provider.currentGapTime}" : provider.totalCurrentQuestionTime - provider.currentGapTime}" + .toText18(color: provider.totalCurrentQuestionTime - provider.currentGapTime < 5 ? MyColors.redColor : MyColors.black) + : const SizedBox(), + ], + ), + 12.height, + stepper(provider.currentQuestionNumber, provider.answerStatusesList, provider.marathonDetailModel.totalQuestions!, provider.isUserOutOfGame), + 8.height, + Row( + children: [ + "${((provider.currentQuestionNumber / provider.marathonDetailModel.totalQuestions!) * 100).toInt()}% ${LocaleKeys.completed.tr()}".toText14(), + ], + ), + ], + ), + ); + } + + Color getStepColor(QuestionCardStatus status, bool isOutOfGame) { + if (isOutOfGame) { + return MyColors.redColor; + } + switch (status) { + case QuestionCardStatus.question: + return MyColors.yellowColorII; + case QuestionCardStatus.wrongAnswer: + return MyColors.redColor; + case QuestionCardStatus.correctAnswer: + return MyColors.greenColor; + case QuestionCardStatus.skippedAnswer: + return MyColors.redColor; + case QuestionCardStatus.completed: + return MyColors.lightGreyDeColor; + case QuestionCardStatus.findingWinner: + return MyColors.lightGreyDeColor; + case QuestionCardStatus.winnerFound: + return MyColors.lightGreyDeColor; + } + } + + Widget stepper(int value, List statusesList, int totalQuestions, bool isOutOfGame) { + return SizedBox( + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + for (int i = 0; i < totalQuestions - 1; i++) + if (value <= i) + roundContainer(MyColors.lightGreyDeColor, i != 0) + else + roundContainer( + getStepColor(statusesList[i], isOutOfGame), + i != 0, + ) + ], + ), + ); + } + + Widget roundContainer(Color color, bool isNeedLeftBorder) { + if (isNeedLeftBorder) { + return Row( + children: [ + Divider(thickness: 6, color: color).expanded, + Container( + width: 10, + height: 10, + decoration: BoxDecoration(shape: BoxShape.circle, color: color), + ), + ], + ).expanded; + } + + return Container( + width: 10, + height: 10, + decoration: BoxDecoration(shape: BoxShape.circle, color: color), + ); + } +} diff --git a/lib/ui/marathon/widgets/marathon_qualifiers_container.dart b/lib/ui/marathon/widgets/marathon_qualifiers_container.dart new file mode 100644 index 0000000..0308027 --- /dev/null +++ b/lib/ui/marathon/widgets/marathon_qualifiers_container.dart @@ -0,0 +1,54 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/decorations_helper.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; + +class QualifiersContainer extends StatefulWidget { + final MarathonProvider provider; + + const QualifiersContainer({Key? key, required this.provider}) : super(key: key); + + @override + State createState() => _QualifiersContainerState(); +} + +class _QualifiersContainerState extends State { + @override + void initState() { + widget.provider.startTimerForWinnerSelection(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: MyDecorations.shadowDecoration, + padding: const EdgeInsets.only(top: 14, left: 18, right: 14, bottom: 18), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.winnerSelection.tr().toText21(color: MyColors.grey3AColor), + "00:${widget.provider.totalSecondsToWaitForWinner < 10 ? "0${widget.provider.totalSecondsToWaitForWinner}" : widget.provider.totalSecondsToWaitForWinner}".toText18(color: MyColors.redColor), + ], + ), + 10.height, + Row( + children: [ + widget.provider.totalQualifiers.toString().toText30(color: MyColors.greenColor, isBold: true), + 2.width, + LocaleKeys.qualifiers.tr().toText16(color: MyColors.greenColor), + ], + ), + ], + ), + ); + } +} diff --git a/lib/ui/marathon/widgets/marathon_timer_card.dart b/lib/ui/marathon/widgets/marathon_timer_card.dart new file mode 100644 index 0000000..e831738 --- /dev/null +++ b/lib/ui/marathon/widgets/marathon_timer_card.dart @@ -0,0 +1,51 @@ +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/extensions/string_extensions.dart'; +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/countdown_timer.dart'; + + +class MarathonTimerCard extends StatelessWidget { + final int timeToMarathon; + final MarathonProvider provider; + + const MarathonTimerCard({ + Key? key, + required this.provider, + required this.timeToMarathon, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: MyDecorations.shadowDecoration, + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 14), + child: Column( + children: [ + Row( + children: [ + "${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.grey77Color), + DateUtil.formatDateToTimeLang(DateTime.parse(provider.marathonDetailModel.startTime!), AppState().isArabic(context)).toText16(color: MyColors.darkTextColor, isBold: true), + ], + ), + Lottie.asset(MyLottieConsts.hourGlassLottie, height: 200), + BuildCountdownTimer(timeToMarathon: timeToMarathon, provider: provider, screenFlag: 1), + ], + ), + ); + } +} diff --git a/lib/ui/marathon/widgets/question_card.dart b/lib/ui/marathon/widgets/question_card.dart index af538f8..4009983 100644 --- a/lib/ui/marathon/widgets/question_card.dart +++ b/lib/ui/marathon/widgets/question_card.dart @@ -145,21 +145,32 @@ class AnswerTileForText extends StatelessWidget { const AnswerTileForText({Key? key, required this.index, required this.onAnswerTapped}) : super(key: key); + Color getAnswerTextColor(QuestionsOptionStatus status) { + switch (status) { + case QuestionsOptionStatus.correct: + return MyColors.white; + case QuestionsOptionStatus.wrong: + return MyColors.white; + case QuestionsOptionStatus.selected: + return MyColors.white; + case QuestionsOptionStatus.unSelected: + return MyColors.darkTextColor; + } + } + @override Widget build(BuildContext context) { MarathonProvider provider = context.watch(); return InkWell( onTap: () { - onAnswerTapped(); + provider.isUserOutOfGame ? null : 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, - ) + .toText16(color: provider.isUserOutOfGame ? MyColors.darkTextColor : getAnswerTextColor(provider.currentQuestion.questionOptions![index].optionStatus!)) .paddingOnly(top: 17, bottom: 17), ), ), diff --git a/lib/ui/screens/items_for_sale/fragments/add_details_fragment.dart b/lib/ui/screens/items_for_sale/fragments/add_details_fragment.dart index f0f2c62..9b82d9d 100644 --- a/lib/ui/screens/items_for_sale/fragments/add_details_fragment.dart +++ b/lib/ui/screens/items_for_sale/fragments/add_details_fragment.dart @@ -127,6 +127,7 @@ class _AddItemDetailsFragmentState extends State { isPopup: false, lines: 1, isInputTypeNum: true, + isInputTypeNumSigned: false, isReadOnly: false, onChange: (String value) { itemPrice = num.parse(value); diff --git a/lib/ui/screens/items_for_sale/items_for_sale_home.dart b/lib/ui/screens/items_for_sale/items_for_sale_home.dart index 437b400..443c8ba 100644 --- a/lib/ui/screens/items_for_sale/items_for_sale_home.dart +++ b/lib/ui/screens/items_for_sale/items_for_sale_home.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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'; @@ -74,6 +76,7 @@ class _ItemsForSaleState extends State { child: const Icon(Icons.add, color: Colors.white, size: 30), ).onPress( () { + Utils.removeStringFromPrefs(SharedPrefsConsts.editItemForSale); Navigator.pushNamed(context, AppRoutes.addNewItemForSale); }, ), diff --git a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart index 325f4f3..e9f46d7 100644 --- a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart +++ b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart @@ -143,7 +143,7 @@ class _OffersAndDiscountsDetailsState extends State { List getItemsForSaleWidgets() { List itemsList = []; - for (int i = 1; i < 5; i++) { + for (int i = 1; i < getOffersList.length; i++) { itemsList.add(getItemCard(getOffersList[i])); } return itemsList; diff --git a/lib/ui/screens/offers_and_discounts/offers_and_discounts_home.dart b/lib/ui/screens/offers_and_discounts/offers_and_discounts_home.dart index 343eed8..c62cc9c 100644 --- a/lib/ui/screens/offers_and_discounts/offers_and_discounts_home.dart +++ b/lib/ui/screens/offers_and_discounts/offers_and_discounts_home.dart @@ -80,39 +80,26 @@ class _OffersAndDiscountsHomeState extends State { // getItemsForSale(currentPageNo, currentCategoryID); }); }, - child: 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: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SvgPicture.string( - getCategoriesList[index].content!, - fit: BoxFit.contain, - width: 25, - height: 25, - ), - currentCategoryID == getCategoriesList[index].id ? const Icon(Icons.check_circle_rounded, color: MyColors.greenColor, size: 16.0) : Container(), - ], - ).expanded, - AppState().isArabic(context) ? getCategoriesList[index].categoryNameAr!.toText10(maxlines: 1) : getCategoriesList[index].categoryNameEn!.toText10(maxlines: 1) - ], - ).paddingOnly(left: 10, right: 10, bottom: 10, top: 12), - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.string( + getCategoriesList[index].content!, + fit: BoxFit.contain, + width: 25, + height: 25, + ), + currentCategoryID == getCategoriesList[index].id ? const Icon(Icons.check_circle_rounded, color: MyColors.greenColor, size: 16.0) : Container(), + ], + ).expanded, + AppState().isArabic(context) ? getCategoriesList[index].categoryNameAr!.toText10() : getCategoriesList[index].categoryNameEn!.toText10() + ], + ).paddingOnly(left: 10, right: 10, bottom: 10, top: 12).expanded.objectContainerView(disablePadding: true), ), ); }, diff --git a/lib/ui/unsafe_device_screen.dart b/lib/ui/unsafe_device_screen.dart new file mode 100644 index 0000000..dca8748 --- /dev/null +++ b/lib/ui/unsafe_device_screen.dart @@ -0,0 +1,72 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mohem_flutter_app/classes/colors.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/widgets/button/default_button.dart'; + +class UnsafeDeviceScreen extends StatefulWidget { + const UnsafeDeviceScreen({Key? key}) : super(key: key); + + @override + State createState() => _UnsafeDeviceScreenState(); +} + +class _UnsafeDeviceScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + 21.height, + Center(child: Image.asset("assets/images/logos/main_mohemm_logo.png", width: 200, height: 50)), + 50.height, + "Sorry".toText24(isBold: true), + 21.height, + "You are using Mohemm app on an unsafe device. To be able to use the app with all it's features, Please make sure that the below points are considered: " + .toText14(isCenter: true) + .paddingOnly(left: 20.0, right: 20.0), + 48.height, + passwordConstraintsUI("The device is not jailbroken or rooted.", true).paddingOnly(left: 24.0, right: 5.0), + 8.height, + passwordConstraintsUI("The app is not installed on external storage.", true).paddingOnly(left: 24.0, right: 5.0), + 8.height, + passwordConstraintsUI("Development mode is disabled.", true).paddingOnly(left: 24.0, right: 5.0), + 21.height, + DefaultButton(LocaleKeys.ok.tr(), () async { + if (Platform.isAndroid) { + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } else { + // MinimizeApp.minimizeApp(); + } + }).paddingAll(24) + ], + ), + ), + ); + } + + Widget passwordConstraintsUI(String description, bool check) { + return Row( + children: [ + 4.width, + SizedBox( + width: 12, + height: 12, + child: Checkbox(fillColor: MaterialStateProperty.all(MyColors.gradiantEndColor), shape: const CircleBorder(), value: check, onChanged: null), + ), + 8.width, + description.toText14() + ], + ); + } +} diff --git a/lib/widgets/app_bar_widget.dart b/lib/widgets/app_bar_widget.dart index 6f0d7fd..199074a 100644 --- a/lib/widgets/app_bar_widget.dart +++ b/lib/widgets/app_bar_widget.dart @@ -8,7 +8,14 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/widgets/circular_avatar.dart'; AppBar AppBarWidget(BuildContext context, - {required String title, bool showHomeButton = true, bool showNotificationButton = false, bool showMemberButton = false, String? image, List? actions}) { + {required String title, + bool showHomeButton = true, + bool showNotificationButton = false, + bool showMemberButton = false, + String? image, + List? actions, + void Function()? onHomeTapped, + void Function()? onBackTapped}) { return AppBar( leadingWidth: 0, // leading: GestureDetector( @@ -21,7 +28,9 @@ AppBar AppBarWidget(BuildContext context, children: [ GestureDetector( behavior: HitTestBehavior.opaque, - onTap: Feedback.wrapForTap(() => Navigator.maybePop(context), context), + onTap: Feedback.wrapForTap(() { + (onBackTapped == null ? Navigator.maybePop(context) : onBackTapped()); + }, context), child: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor), ), 4.width, @@ -43,7 +52,7 @@ AppBar AppBarWidget(BuildContext context, if (showHomeButton) IconButton( onPressed: () { - Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)); + onHomeTapped == null ? Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)) : onHomeTapped(); }, icon: const Icon(Icons.home, color: MyColors.darkIconColor), ), diff --git a/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart b/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart index 32b501c..475c4a5 100644 --- a/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart @@ -15,6 +15,7 @@ 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/chat/get_search_user_chat_model.dart'; import 'package:mohem_flutter_app/models/get_action_history_list_model.dart'; import 'package:mohem_flutter_app/models/worklist/get_favorite_replacements_model.dart'; @@ -90,10 +91,9 @@ class _SearchEmployeeBottomSheetState extends State { Utils.showLoading(context); chatUsersList = await ChatApiClient().getChatMemberFromSearch( searchText, - int.parse( - AppState().chatDetails!.response!.id.toString(), - ), + int.parse(AppState().chatDetails!.response!.id.toString()), ); + chatUsersList!.removeWhere((element) => element.id == AppState().chatDetails!.response!.id); Utils.hideLoading(context); setState(() {}); } catch (e) { diff --git a/lib/widgets/dynamic_forms/dynamic_textfield_widget.dart b/lib/widgets/dynamic_forms/dynamic_textfield_widget.dart index 91e0ce1..299ef7a 100644 --- a/lib/widgets/dynamic_forms/dynamic_textfield_widget.dart +++ b/lib/widgets/dynamic_forms/dynamic_textfield_widget.dart @@ -4,6 +4,7 @@ import 'package:mohem_flutter_app/classes/colors.dart'; class DynamicTextFieldWidget extends StatelessWidget { final String labelText; final String hintText; + // final TextEditingController controller; final VoidCallback? onTap; final IconData? suffixIconData; @@ -13,6 +14,7 @@ class DynamicTextFieldWidget extends StatelessWidget { final bool isPopup; final int? lines; final bool isInputTypeNum; + final bool isInputTypeNumSigned; final bool isObscureText; final bool isBackgroundEnable; final void Function(String)? onChange; @@ -28,6 +30,7 @@ class DynamicTextFieldWidget extends StatelessWidget { this.inputAction, this.onChange, this.isInputTypeNum = false, + this.isInputTypeNumSigned = true, this.isBackgroundEnable = false}); @override @@ -63,8 +66,13 @@ class DynamicTextFieldWidget extends StatelessWidget { ), TextField( enabled: isEnable, - scrollPadding: EdgeInsets.zero, readOnly: isReadOnly, - keyboardType: isInputTypeNum ? const TextInputType.numberWithOptions(signed: true) : TextInputType.text, + scrollPadding: EdgeInsets.zero, + readOnly: isReadOnly, + keyboardType: (isInputTypeNum) + ? isInputTypeNumSigned + ? const TextInputType.numberWithOptions(signed: true) + : TextInputType.number + : TextInputType.text, textInputAction: TextInputAction.done, //controller: controller, maxLines: lines, diff --git a/lib/widgets/item_detail_view_widget.dart b/lib/widgets/item_detail_view_widget.dart index 5148389..ccdedea 100644 --- a/lib/widgets/item_detail_view_widget.dart +++ b/lib/widgets/item_detail_view_widget.dart @@ -37,9 +37,9 @@ class ItemDetailViewCol extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "$title:".toText12(isBold: true, color: const Color(0xff2BB8A6)), + "$title:".toText12(isBold: true, color: const Color(0xff2BB8A6), maxLine: 2), 4.width, - (value.isEmpty ? "--" : value).toText12(color: MyColors.normalTextColor), + (value.isEmpty ? "--" : value).toText12(color: MyColors.normalTextColor, maxLine: 2), ], ); }