diff --git a/assets/images/wrong_answer.gif b/assets/images/wrong_answer.gif new file mode 100644 index 0000000..44f1d38 Binary files /dev/null and b/assets/images/wrong_answer.gif differ diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 1c5851a..59c0814 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -500,5 +500,9 @@ "codeExpire": "انتهت صلاحية رمز التحقق", "typeheretoreply": "اكتب هنا للرد", "favorite": "مفضلتي", - "searchfromchat": "البحث من الدردشة" + "searchfromchat": "البحث من الدردشة", + "yourAnswerCorrect": "إجابتك صحيحة", + "youMissedTheQuestion": "فاتك !! أنت خارج اللعبة. لكن يمكنك المتابعة.", + "wrongAnswer": "إجابة خاطئة! أنت خارج اللعبة. لكن يمكنك المتابعة." + } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b4b06f0..b0ab50e 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -500,6 +500,9 @@ "allQuestionsCorrect": "You have answered all questions correct", "typeheretoreply": "Type here to reply", "favorite" : "My Favorites", - "searchfromchat": "Search from chat" + "searchfromchat": "Search from chat", + "yourAnswerCorrect": "Your answer is correct", + "youMissedTheQuestion": "You Missed !! You are out of the game. But you can follow up.", + "wrongAnswer": "Wrong Answer! You are out of the game. But you can follow up." } \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore index 151026b..d032e39 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -31,3 +31,8 @@ Runner/GeneratedPluginRegistrant.* !default.mode2v3 !default.pbxuser !default.perspectivev3 + +ios/Podfile +ios/Runner/Runner.entitlements + + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..304aa00 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,30 @@ + + + + + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.com.cloudsolutions.mohemm + + com.apple.developer.icloud-services + + CloudDocuments + + com.apple.developer.networking.HotspotConfiguration + + com.apple.developer.networking.networkextension + + com.apple.developer.networking.wifi-info + + com.apple.developer.nfc.readersession.formats + + TAG + + com.apple.developer.ubiquity-container-identifiers + + iCloud.com.cloudsolutions.mohemm + + + diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index 7a6f668..1e66d45 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -18,8 +18,7 @@ class APIError { APIError(this.errorCode, this.errorMessage); - Map toJson() => - {'errorCode': errorCode, 'errorMessage': errorMessage}; + Map toJson() => {'errorCode': errorCode, 'errorMessage': errorMessage}; @override String toString() { @@ -54,8 +53,7 @@ APIException _throwAPIException(Response response) { return APIException(APIException.INTERNAL_SERVER_ERROR); case 444: var downloadUrl = response.headers["location"]; - return APIException(APIException.UPGRADE_REQUIRED, - arguments: downloadUrl); + return APIException(APIException.UPGRADE_REQUIRED, arguments: downloadUrl); default: return APIException(APIException.OTHER); } @@ -68,13 +66,8 @@ class ApiClient { factory ApiClient() => _instance; - Future postJsonForObject( - FactoryConstructor factoryConstructor, String url, T jsonObject, - {String? token, - Map? queryParameters, - Map? headers, - int retryTimes = 0, - bool isFormData = false}) async { + Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, + {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isFormData = false}) async { var _headers = {'Accept': 'application/json'}; if (headers != null && headers.isNotEmpty) { _headers.addAll(headers); @@ -84,12 +77,7 @@ class ApiClient { var bodyJson = json.encode(jsonObject); print("body:$bodyJson"); } - var response = await postJsonForResponse(url, jsonObject, - token: token, - queryParameters: queryParameters, - headers: _headers, - retryTimes: retryTimes, - isFormData: isFormData); + var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData); // try { if (!kReleaseMode) { logger.i("res: " + response.body); @@ -102,8 +90,7 @@ class ApiClient { return factoryConstructor(jsonData); } else { APIError? apiError; - apiError = - APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage']); + apiError = APIError(jsonData['ErrorCode'], jsonData['ErrorEndUserMessage']); throw APIException(APIException.BAD_REQUEST, error: apiError); } // } catch (ex) { @@ -116,11 +103,7 @@ class ApiClient { } Future postJsonForResponse(String url, T jsonObject, - {String? token, - Map? queryParameters, - Map? headers, - int retryTimes = 0, - bool isFormData = false}) async { + {String? token, Map? queryParameters, Map? headers, int retryTimes = 0, bool isFormData = false}) async { String? requestBody; late Map stringObj; if (jsonObject != null) { @@ -134,22 +117,13 @@ class ApiClient { if (isFormData) { headers = {'Content-Type': 'application/x-www-form-urlencoded'}; - stringObj = ((jsonObject ?? {}) as Map) - .map((key, value) => MapEntry(key, value?.toString() ?? "")); + stringObj = ((jsonObject ?? {}) as Map).map((key, value) => MapEntry(key, value?.toString() ?? "")); } - return await _postForResponse(url, isFormData ? stringObj : requestBody, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes); + return await _postForResponse(url, isFormData ? stringObj : requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } - Future _postForResponse(String url, requestBody, - {String? token, - Map? queryParameters, - Map? headers, - int retryTimes = 0}) async { + Future _postForResponse(String url, requestBody, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { try { var _headers = {}; if (token != null) { @@ -164,9 +138,7 @@ class ApiClient { var queryString = new Uri(queryParameters: queryParameters).query; url = url + '?' + queryString; } - var response = - await _post(Uri.parse(url), body: requestBody, headers: _headers) - .timeout(Duration(seconds: 120)); + var response = await _post(Uri.parse(url), body: requestBody, headers: _headers).timeout(Duration(seconds: 120)); if (response.statusCode >= 200 && response.statusCode < 300) { return response; @@ -177,11 +149,7 @@ class ApiClient { if (retryTimes > 0) { print('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); - return await _postForResponse(url, requestBody, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes - 1); + return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } @@ -189,11 +157,7 @@ class ApiClient { if (retryTimes > 0) { print('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); - return await _postForResponse(url, requestBody, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes - 1); + return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } @@ -203,39 +167,23 @@ class ApiClient { if (retryTimes > 0) { print('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); - return await _postForResponse(url, requestBody, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes - 1); + return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } } - Future getJsonForResponse(String url, - {String? token, - Map? queryParameters, - Map? headers, - int retryTimes = 0}) async { + Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { if (headers == null) { headers = {'Content-Type': 'application/json'}; } else { headers['Content-Type'] = 'application/json'; } - return await _getForResponse(url, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } - Future _getForResponse(String url, - {String? token, - Map? queryParameters, - Map? headers, - int retryTimes = 0}) async { + Future _getForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { try { var _headers = {}; if (token != null) { @@ -250,8 +198,7 @@ class ApiClient { var queryString = new Uri(queryParameters: queryParameters).query; url = url + '?' + queryString; } - var response = await _get(Uri.parse(url), headers: _headers) - .timeout(Duration(seconds: 60)); + var response = await _get(Uri.parse(url), headers: _headers).timeout(Duration(seconds: 60)); if (response.statusCode >= 200 && response.statusCode < 300) { return response; @@ -262,11 +209,7 @@ class ApiClient { if (retryTimes > 0) { print('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); - return await _getForResponse(url, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes - 1); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } @@ -274,11 +217,7 @@ class ApiClient { if (retryTimes > 0) { print('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); - return await _getForResponse(url, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes - 1); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } @@ -288,19 +227,14 @@ class ApiClient { if (retryTimes > 0) { print('will retry after 3 seconds...'); await Future.delayed(Duration(seconds: 3)); - return await _getForResponse(url, - token: token, - queryParameters: queryParameters, - headers: headers, - retryTimes: retryTimes - 1); + return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); } else { throw APIException(APIException.OTHER, arguments: e); } } } - Future _get(url, {Map? headers}) => - _withClient((client) => client.get(url, headers: headers)); + Future _get(url, {Map? headers}) => _withClient((client) => client.get(url, headers: headers)); bool _certificateCheck(X509Certificate cert, String host, int port) => true; @@ -314,8 +248,5 @@ class ApiClient { } } - Future _post(url, - {Map? headers, body, Encoding? encoding}) => - _withClient((client) => - client.post(url, headers: headers, body: body, encoding: encoding)); + Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); } diff --git a/lib/api/chat/chat_api_client.dart b/lib/api/chat/chat_api_client.dart new file mode 100644 index 0000000..1f97b1f --- /dev/null +++ b/lib/api/chat/chat_api_client.dart @@ -0,0 +1,167 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:http/http.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/classes/utils.dart'; +import 'package:mohem_flutter_app/exceptions/api_exception.dart'; +import 'package:mohem_flutter_app/main.dart'; +import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; +import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as user; +import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav; + +class ChatApiClient { + static final ChatApiClient _instance = ChatApiClient._internal(); + + ChatApiClient._internal(); + + factory ChatApiClient() => _instance; + + Future getUserLoginToken() async { + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.chatLoginTokenUrl}externaluserlogin", + { + "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER.toString(), + "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG", + }, + ); + user.UserAutoLoginModel userLoginResponse = user.userAutoLoginModelFromJson(response.body); + return userLoginResponse; + } + + Future?> getChatMemberFromSearch(String sName, int cUserId) async { + Response response = await ApiClient().getJsonForResponse( + "${ApiConsts.chatLoginTokenUrl}getUserWithStatusAndFavAsync/$sName/$cUserId", + token: AppState().chatDetails!.response!.token, + ); + return searchUserJsonModel(response.body); + } + + List searchUserJsonModel(String str) => List.from(json.decode(str).map((x) => ChatUser.fromJson(x))); + + Future getRecentChats() async { + try { + Response response = await ApiClient().getJsonForResponse( + "${ApiConsts.chatRecentUrl}getchathistorybyuserid", + token: AppState().chatDetails!.response!.token, + ); + return ChatUserModel.fromJson( + json.decode(response.body), + ); + } catch (e) { + e as APIException; + if (e.message == "api_common_unauthorized") { + user.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); + if (userLoginResponse.response != null) { + AppState().setchatUserDetails = userLoginResponse; + getRecentChats(); + } else { + Utils.showToast( + userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", + ); + } + } + throw e; + } + } + + Future getFavUsers() async { + Response favRes = await ApiClient().getJsonForResponse( + "${ApiConsts.chatFavUser}getFavUserById/${AppState().chatDetails!.response!.id}", + token: AppState().chatDetails!.response!.token, + ); + return ChatUserModel.fromJson( + json.decode(favRes.body), + ); + } + + Future getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false, required int paginationVal}) async { + try { + Response response = await ApiClient().getJsonForResponse( + "${ApiConsts.chatSingleUserHistoryUrl}GetUserChatHistory/$senderUID/$receiverUID/$paginationVal", + token: AppState().chatDetails!.response!.token, + ); + return response; + } catch (e) { + e as APIException; + if (e.message == "api_common_unauthorized") { + user.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); + if (userLoginResponse.response != null) { + AppState().setchatUserDetails = userLoginResponse; + getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); + } else { + Utils.showToast(userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr"); + } + } + throw e; + } + } + + Future favUser({required int userID, required int targetUserID}) async { + Response response = await ApiClient().postJsonForResponse("${ApiConsts.chatFavUser}addFavUser", {"targetUserId": targetUserID, "userId": userID}, token: AppState().chatDetails!.response!.token); + fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); + return favoriteChatUser; + } + + Future unFavUser({required int userID, required int targetUserID}) async { + try { + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.chatFavUser}deleteFavUser", + {"targetUserId": targetUserID, "userId": userID}, + token: AppState().chatDetails!.response!.token, + ); + fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); + return favoriteChatUser; + } catch (e) { + e as APIException; + if (e.message == "api_common_unauthorized") { + logger.d("Token Generated On APIIIIII"); + user.UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); + if (userLoginResponse.response != null) { + AppState().setchatUserDetails = userLoginResponse; + unFavUser(userID: userID, targetUserID: targetUserID); + } else { + Utils.showToast(userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr"); + } + } + throw e; + } + } + + Future uploadMedia(String userId, File file) async { + dynamic request = MultipartRequest('POST', Uri.parse('${ApiConsts.chatMediaImageUploadUrl}upload')); + request.fields.addAll({'userId': userId, 'fileSource': '1'}); + request.files.add(await MultipartFile.fromPath('files', file.path)); + request.headers.addAll({'Authorization': 'Bearer ${AppState().chatDetails!.response!.token}'}); + StreamedResponse response = await request.send(); + return response; + } + + // Download File For Chat + + Future downloadURL({required String fileName, required String fileTypeDescription}) async { + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.chatMediaImageUploadUrl}download", + {"fileType": fileTypeDescription, "fileName": fileName, "fileSource": 1}, + token: AppState().chatDetails!.response!.token, + ); + Uint8List data = Uint8List.fromList(response.bodyBytes); + return data; + } + + Future getUsersImages({required List encryptedEmails}) async { + Response response = await ApiClient().postJsonForResponse( + "${ApiConsts.chatUserImages}images", + { + "encryptedEmails": ["/g8Rc+s6eEOdci41PwJuV5dX+gXe51G9OTHzb9ahcVlHCmVvNhxReirudF79+hdxVSkCnQ6wC5DBFV8xnJlC74X6157PxF7mNYrAYuHRgp4="], + "fromClient": true + }, + token: AppState().chatDetails!.response!.token, + ); + logger.d(response.body); + // Uint8List data = Uint8List.fromList(response.body); + } +} diff --git a/lib/api/dashboard_api_client.dart b/lib/api/dashboard_api_client.dart index c8fb015..3eab65b 100644 --- a/lib/api/dashboard_api_client.dart +++ b/lib/api/dashboard_api_client.dart @@ -185,8 +185,22 @@ class DashboardApiClient { Future getChatCount() async { Response response = await ApiClient().getJsonForResponse( - "${ApiConsts.chatServerBaseApiUrl}user/unreadconversationcount/${AppState().getUserName}", + "${ApiConsts.chatLoginTokenUrl}unreadconversationcount/${AppState().getUserName}", ); return chatUnreadCovnCountModelFromJson(response.body); } + +// Future setAdvertisementViewed(String masterID, int advertisementId) async { +// String url = "${ApiConsts.cocRest}Mohemm_ITG_UpdateAdvertisementAsViewed"; +// +// Map postParams = { +// "ItgNotificationMasterId": masterID, +// "ItgAdvertisement": {"advertisementId": advertisementId, "acknowledgment": true} //Mobile Id +// }; +// postParams.addAll(AppState().postParamsJson); +// return await ApiClient().postJsonForObject((json) { +// // ItgMainRes responseData = ItgMainRes.fromJson(json); +// return json; +// }, url, postParams); +// } } diff --git a/lib/api/marathon/marathon_api_client.dart b/lib/api/marathon/marathon_api_client.dart new file mode 100644 index 0000000..1ec117d --- /dev/null +++ b/lib/api/marathon/marathon_api_client.dart @@ -0,0 +1,178 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:logger/logger.dart' as L; +import 'package:logging/logging.dart'; +import 'package:mohem_flutter_app/api/api_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/consts.dart'; +import 'package:mohem_flutter_app/models/marathon/marathon_generic_model.dart'; +import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:signalr_netcore/http_connection_options.dart'; +import 'package:signalr_netcore/hub_connection.dart'; +import 'package:signalr_netcore/hub_connection_builder.dart'; + +class MarathonApiClient { + Future getMarathonToken() async { + String employeeUserName = AppState().getUserName ?? ""; + String employeeSession = AppState().postParamsObject?.pSessionId.toString() ?? ""; + + Map jsonObject = {"userName": employeeUserName, "password": employeeSession}; + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonParticipantLoginUrl, jsonObject); + + var json = jsonDecode(response.body); + + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); + + if (marathonModel.statusCode == 200) { + if (marathonModel.data != null && marathonModel.isSuccessful == true) { + AppState().setMarathonToken = marathonModel.data["token"] ?? ""; + print("bearer: ${AppState().getMarathonToken}"); + return marathonModel.data["token"] ?? ""; + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } + + Future getProjectId() async { + Response response = await ApiClient().postJsonForResponse(ApiConsts.marathonProjectGetUrl, {}, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(json); + + if (marathonModel.statusCode == 200) { + if (marathonModel.data != null && marathonModel.isSuccessful == true) { + logger.i("message: ${marathonModel.data[0]["id"]}"); + AppState().setMarathonProjectId = marathonModel.data[0]["id"] ?? ""; + return marathonModel.data[0]["id"] ?? ""; + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } else { + //TODO : DO ERROR HANDLING HERE + return ""; + } + } + + Future getMarathonDetails() async { + String payrollString = AppState().postParamsObject?.payrollCodeStr.toString() ?? "CS"; + + Response response = await ApiClient().getJsonForResponse(ApiConsts.marathonUpcomingUrl + payrollString, token: AppState().getMarathonToken ?? await getMarathonToken()); + + var json = jsonDecode(response.body); + + MarathonGenericModel marathonGenericModel = MarathonGenericModel.fromJson(json); + + MarathonDetailModel marathonDetailModel = MarathonDetailModel.fromJson(marathonGenericModel.data); + + AppState().setMarathonProjectId = marathonDetailModel.id!; + + return marathonDetailModel; + } + + late HubConnection hubConnection; + L.Logger logger = L.Logger(); + + Future buildHubConnection(BuildContext context) async { + HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hubConnection = HubConnectionBuilder() + .withUrl( + ApiConsts.marathonHubConnectionUrl + "?employeeNumber=${AppState().memberInformationList!.eMPLOYEENUMBER ?? ""}&employeeName=${AppState().memberInformationList!.eMPLOYEENAME ?? ""}", + options: httpOptions, + ) + .withAutomaticReconnect( + retryDelays: [2000, 5000, 10000, 20000], + ) + .configureLogging( + Logger("Logging"), + ) + .build(); + hubConnection.onclose( + ({Exception? error}) { + logger.i("onclose"); + }, + ); + hubConnection.onreconnecting( + ({Exception? error}) { + logger.i("onreconnecting"); + }, + ); + hubConnection.onreconnected( + ({String? connectionId}) { + logger.i("onreconnected"); + }, + ); + if (hubConnection.state != HubConnectionState.Connected) { + await hubConnection.start(); + logger.i("Started HubConnection"); + + await hubConnection.invoke( + "AddParticipant", + args: [ + { + "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER ?? "", + "employeeName": AppState().memberInformationList!.eMPLOYEENAME ?? "", + "marathonId": AppState().getMarathonProjectId, + } + ], + ).catchError((e) { + logger.i("Error in AddParticipant: $e"); + }); + + context.read().addItemToList(ApiConsts.dummyQuestion); + + await hubConnection.invoke( + "SendQuestionToParticipant", + args: [ + { + "marathonId": "${AppState().getMarathonProjectId}", + } + ], + ).catchError((e) { + logger.i("Error in SendQuestionToParticipant: $e"); + }); + + try { + hubConnection.on("OnSendQuestionToParticipant", (List? arguments) { + onSendQuestionToParticipant(arguments, context); + }); + } catch (e, s) { + logger.i("Error in OnSendQuestionToParticipant"); + } + + try { + hubConnection.on("OnParticipantJoin", (List? arguments) { + onParticipantJoin(arguments, context); + }); + } catch (e, s) { + logger.i("Error in OnParticipantJoin"); + } + } + } + + Future onSendQuestionToParticipant(List? arguments, BuildContext context) async { + logger.i("onSendQuestionToParticipant arguments: $arguments"); + + if (arguments != null) { + Map data = arguments.first! as Map; + var json = data["data"]; + QuestionModel newQuestion = QuestionModel.fromJson(json); + context.read().onNewQuestionReceived(newQuestion); + } + } + + Future onParticipantJoin(List? arguments, BuildContext context) async { + logger.i("OnParticipantJoin arguments: $arguments"); + context.watch().totalMarathoners++; + } +} diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart index 49f4186..b0620ad 100644 --- a/lib/app_state/app_state.dart +++ b/lib/app_state/app_state.dart @@ -51,6 +51,18 @@ class AppState { String? get getMohemmWifiPassword => _mohemmWifiPassword; + String? _marathonToken ; + + set setMarathonToken(String token) => _marathonToken = token; + + String? get getMarathonToken => _marathonToken; + + String? _projectID ; + + set setMarathonProjectId(String token) => _projectID = token; + + String? get getMarathonProjectId => _projectID; + final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 5.0, mobileType: Platform.isAndroid ? "android" : "ios"); void setPostParamsInitConfig() { diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart index f99cc31..4394279 100644 --- a/lib/classes/colors.dart +++ b/lib/classes/colors.dart @@ -17,6 +17,7 @@ class MyColors { static const Color greyF7Color = Color(0xffF7F7F7); static const Color grey80Color = Color(0xff808080); static const Color grey70Color = Color(0xff707070); + static const Color grey7BColor = Color(0xff7B7B7B); static const Color greyACColor = Color(0xffACACAC); static const Color grey98Color = Color(0xff989898); static const Color lightGreyEFColor = Color(0xffEFEFEF); @@ -61,4 +62,5 @@ class MyColors { static const Color grey9DColor = Color(0xff9D9D9D); static const Color darkDigitColor = Color(0xff2D2F39); static const Color grey71Color = Color(0xff717171); + static const Color darkGrey3BColor = Color(0xff3B3B3B); } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index ede22e3..57e92ec 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,7 +1,9 @@ +import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; + class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server - //static String baseUrl = "https://hmgwebservices.com"; // Live server + // static String baseUrl = "https://hmgwebservices.com"; // Live server static String baseUrlServices = baseUrl + "/Services/"; // server // static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server static String utilitiesRest = baseUrlServices + "Utilities.svc/REST/"; @@ -10,15 +12,32 @@ class ApiConsts { static String user = baseUrlServices + "api/User/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/"; + // todo @aamir move api end point last repo to concerned method. + //Chat - static String chatServerBaseUrl = "https://apiderichat.hmg.com"; - static String chatServerBaseApiUrl = "https://apiderichat.hmg.com/api/"; - static String chatHubConnectionUrl = chatServerBaseUrl + "/ConnectionChatHub"; - static String chatSearchMember = "user/getUserWithStatusAndFavAsync/"; - static String chatRecentUrl = "UserChatHistory/getchathistorybyuserid"; //For a Mem - static String chatSingleUserHistoryUrl = "UserChatHistory/GetUserChatHistory"; - static String chatMediaImageUploadUrl = "shared/upload"; - static String chatFavoriteUsers = "FavUser/getFavUserById/"; + static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; + static String chatServerBaseApiUrl = chatServerBaseUrl + "api/"; + static String chatLoginTokenUrl = chatServerBaseApiUrl + "user/"; + static String chatHubConnectionUrl = chatServerBaseUrl + "ConnectionChatHub"; + + // static String chatSearchMember = chatLoginTokenUrl + "user/"; + static String chatRecentUrl = chatServerBaseApiUrl + "UserChatHistory/"; //For a Mem + static String chatSingleUserHistoryUrl = chatServerBaseApiUrl + "UserChatHistory/"; + static String chatMediaImageUploadUrl = chatServerBaseApiUrl + "shared/"; + static String chatFavUser = chatServerBaseApiUrl + "FavUser/"; + static String chatUserImages = chatServerBaseUrl + "empservice/api/employee/"; + + //Brain Marathon Constants + static String marathonBaseUrl = "https://18.188.181.12/service/"; + static String marathonParticipantLoginUrl = marathonBaseUrl + "api/auth/participantlogin"; + static String marathonProjectGetUrl = marathonBaseUrl + "api/Project/Project_Get"; + static String marathonUpcomingUrl = marathonBaseUrl + "api/marathon/upcoming/"; + static String marathonHubConnectionUrl = marathonBaseUrl + "MarathonBroadCast"; + + //DummyCards for the UI + + static CardContent dummyQuestion = const CardContent(); + } class SharedPrefsConsts { diff --git a/lib/classes/date_uitl.dart b/lib/classes/date_uitl.dart index f7b8192..f37fbea 100644 --- a/lib/classes/date_uitl.dart +++ b/lib/classes/date_uitl.dart @@ -3,6 +3,26 @@ import 'package:intl/intl.dart'; class DateUtil { /// convert String To Date function /// [date] String we want to convert + /// + /// + + + static DateTime convertStringToDateMarathon(String date) { + // /Date(1585774800000+0300)/ + if (date != null) { + const start = "/Date("; + const end = "+0300)"; + int startIndex = date.indexOf(start); + int endIndex = date.indexOf(end, startIndex + start.length); + return DateTime.fromMillisecondsSinceEpoch( + int.parse( + date.substring(startIndex + start.length, endIndex), + ), + ); + } else + return DateTime.now(); + } + static DateTime convertStringToDate(String date) { // /Date(1585774800000+0300)/ if (date != null) { @@ -20,7 +40,7 @@ class DateUtil { } static DateTime convertSimpleStringDateToDate(String date) { - return DateFormat("MM/dd/yyyy hh:mm:ss").parse(date); + return DateFormat("MM/dd/yyyy hh:mm:ss a").parse(date); } static DateTime convertSimpleStringDateToDateddMMyyyy(String date) { @@ -55,8 +75,9 @@ class DateUtil { } return DateTime.now(); - } else + } else { return DateTime.now(); + } } static String convertDateToString(DateTime date) { @@ -94,7 +115,7 @@ class DateUtil { } static String convertDateMSToJsonDate(utc) { - var dt = new DateTime.fromMicrosecondsSinceEpoch(utc); + var dt = DateTime.fromMicrosecondsSinceEpoch(utc); return "/Date(" + (dt.millisecondsSinceEpoch * 1000).toString() + '+0300' + ")/"; } @@ -416,7 +437,7 @@ class DateUtil { /// get data formatted like 10:30 according to lang static String formatDateToTimeLang(DateTime date, bool isArabic) { - return DateFormat('HH:mm', isArabic ? "ar_SA" : "en_US").format(date); + return DateFormat('HH:mm a', isArabic ? "ar_SA" : "en_US").format(date); } /// get data formatted like 26/4/2020 10:30 diff --git a/lib/classes/decorations_helper.dart b/lib/classes/decorations_helper.dart index 77ea4a4..b313673 100644 --- a/lib/classes/decorations_helper.dart +++ b/lib/classes/decorations_helper.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; class MyDecorations { static Decoration shadowDecoration = BoxDecoration( @@ -22,4 +23,18 @@ class MyDecorations { ); return answerContainerDecoration; } + + static Decoration getAnswersContainerColor(QuestionsOptionStatus questionsOptionStatus) { + switch (questionsOptionStatus) { + case QuestionsOptionStatus.correct: + return getContainersDecoration(MyColors.greenColor); + case QuestionsOptionStatus.wrong: + return getContainersDecoration(MyColors.redColor); + + case QuestionsOptionStatus.selected: + return getContainersDecoration(MyColors.yellowColorII); + case QuestionsOptionStatus.unSelected: + return getContainersDecoration(MyColors.greyF7Color); + } + } } diff --git a/lib/classes/lottie_consts.dart b/lib/classes/lottie_consts.dart index 1b714a4..24dc423 100644 --- a/lib/classes/lottie_consts.dart +++ b/lib/classes/lottie_consts.dart @@ -4,4 +4,6 @@ class MyLottieConsts { static const String celebrate2Lottie = "assets/lottie/celebrate2.json"; static const String winnerLottie = "assets/lottie/winner3.json"; static const String allQuestions = "assets/lottie/all_questions.json"; + static const String wrongAnswerGif = "assets/images/wrong_answer.gif"; + } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index ab3fb9a..7cf8a35 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -7,6 +7,7 @@ import 'package:mohem_flutter_app/ui/chat/chat_detailed_screen.dart'; import 'package:mohem_flutter_app/ui/chat/chat_home.dart'; 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'; @@ -87,6 +88,7 @@ class AppRoutes { static const String addEitScreen = "/addeitScreen"; static const String initialRoute = login; static const String survey = "/survey"; + static const String advertisement = "/advertisement"; //Work List static const String workList = "/workList"; @@ -116,8 +118,7 @@ class AppRoutes { static const String addVacationRule = "/addVacationRule"; //Bottom Sheet - static const String attendanceDetailsBottomSheet = - "/attendanceDetailsBottomSheet"; + static const String attendanceDetailsBottomSheet = "/attendanceDetailsBottomSheet"; //Profile static const String profile = "/profile"; @@ -135,8 +136,7 @@ class AppRoutes { // Pending Transactions static const String pendingTransactions = "/pendingTransactions"; - static const String pendingTransactionsDetails = - "/pendingTransactionsDetails"; + static const String pendingTransactionsDetails = "/pendingTransactionsDetails"; // Announcements static const String announcements = "/announcements"; @@ -192,6 +192,7 @@ class AppRoutes { verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(), dashboard: (BuildContext context) => DashboardScreen(), survey: (BuildContext context) => SurveyScreen(), + advertisement: (BuildContext context) => ITGAdsScreen(), subMenuScreen: (BuildContext context) => SubMenuScreen(), newPassword: (BuildContext context) => NewPasswordScreen(), @@ -223,8 +224,7 @@ class AppRoutes { addVacationRule: (BuildContext context) => AddVacationRuleScreen(), //Bottom Sheet - attendanceDetailsBottomSheet: (BuildContext context) => - AttendenceDetailsBottomSheet(), + attendanceDetailsBottomSheet: (BuildContext context) => AttendenceDetailsBottomSheet(), //Profile //profile: (BuildContext context) => Profile(), @@ -235,13 +235,10 @@ class AppRoutes { familyMembers: (BuildContext context) => FamilyMembers(), dynamicScreen: (BuildContext context) => DynamicListViewScreen(), addDynamicInput: (BuildContext context) => DynamicInputScreen(), - addDynamicInputProfile: (BuildContext context) => - DynamicInputScreenProfile(), - addDynamicAddressScreen: (BuildContext context) => - DynamicInputScreenAddress(), + addDynamicInputProfile: (BuildContext context) => DynamicInputScreenProfile(), + addDynamicAddressScreen: (BuildContext context) => DynamicInputScreenAddress(), - deleteFamilyMember: (BuildContext context) => - DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int), + deleteFamilyMember: (BuildContext context) => DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int), requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(), addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(), @@ -251,8 +248,7 @@ class AppRoutes { mowadhafhiHRRequest: (BuildContext context) => MowadhafhiHRRequest(), pendingTransactions: (BuildContext context) => PendingTransactions(), - pendingTransactionsDetails: (BuildContext context) => - PendingTransactionsDetails(), + pendingTransactionsDetails: (BuildContext context) => PendingTransactionsDetails(), announcements: (BuildContext context) => Announcements(), announcementsDetails: (BuildContext context) => AnnouncementDetails(), @@ -268,8 +264,7 @@ class AppRoutes { // Offers & Discounts offersAndDiscounts: (BuildContext context) => OffersAndDiscountsHome(), - offersAndDiscountsDetails: (BuildContext context) => - OffersAndDiscountsDetails(), + offersAndDiscountsDetails: (BuildContext context) => OffersAndDiscountsDetails(), //pay slip monthlyPaySlip: (BuildContext context) => MonthlyPaySlipScreen(), @@ -296,8 +291,7 @@ class AppRoutes { // Marathon marathonIntroScreen: (BuildContext context) => MarathonIntroScreen(), marathonScreen: (BuildContext context) => MarathonScreen(), - marathonWinnerSelection: (BuildContext context) => - MarathonWinnerSelection(), + marathonWinnerSelection: (BuildContext context) => MarathonWinnerSelection(), marathonWinnerScreen: (BuildContext context) => WinnerScreen(), }; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 8bc15e7..3546f6b 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -136,7 +136,7 @@ extension EmailValidator on String { Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.31, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)), + style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)), ); Widget toText22({Color? color, bool isBold = false}) => Text( @@ -149,6 +149,11 @@ extension EmailValidator on String { style: TextStyle(height: 23 / 24, color: color ?? MyColors.darkTextColor, fontSize: 24, letterSpacing: -1.44, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), ); + Widget toText30({Color? color, bool isBold = false}) => Text( + this, + style: TextStyle(height: 20 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.2, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), + ); + Widget toText32({Color? color, bool isBold = false}) => Text( this, style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f5d4960..2f7efcc 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -487,5 +487,8 @@ abstract class LocaleKeys { static const typeheretoreply = 'typeheretoreply'; static const favorite = 'favorite'; static const searchfromchat = 'searchfromchat'; + static const yourAnswerCorrect = 'yourAnswerCorrect'; + static const youMissedTheQuestion = 'youMissedTheQuestion'; + static const wrongAnswer = 'wrongAnswer'; } diff --git a/lib/main.dart b/lib/main.dart index a2ae0ae..4d686b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,7 +3,7 @@ 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/api/chat/chat_provider_model.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'; diff --git a/lib/models/chat/call.dart b/lib/models/chat/call.dart index 20f6a79..7f8f6eb 100644 --- a/lib/models/chat/call.dart +++ b/lib/models/chat/call.dart @@ -7,7 +7,7 @@ import 'dart:convert'; class CallDataModel { CallDataModel({ this.callerId, - this.callReciverId, + this.callReceiverID, this.notificationForeground, this.message, this.title, @@ -27,7 +27,7 @@ class CallDataModel { }); String? callerId; - String? callReciverId; + String? callReceiverID; String? notificationForeground; String? message; String? title; @@ -51,7 +51,7 @@ class CallDataModel { factory CallDataModel.fromJson(Map json) => CallDataModel( callerId: json["callerID"] == null ? null : json["callerID"], - callReciverId: json["callReciverID"] == null ? null : json["callReciverID"], + callReceiverID: json["callReceiverID"] == null ? null : json["callReceiverID"], notificationForeground: json["notification_foreground"] == null ? null : json["notification_foreground"], message: json["message"] == null ? null : json["message"], title: json["title"] == null ? null : json["title"], @@ -78,7 +78,7 @@ class CallDataModel { Map toJson() => { "callerID": callerId == null ? null : callerId, - "callReciverID": callReciverId == null ? null : callReciverId, + "callReceiverID": callReceiverID == null ? null : callReceiverID, "notification_foreground": notificationForeground == null ? null : notificationForeground, "message": message == null ? null : message, "title": title == null ? null : title, diff --git a/lib/models/chat/get_search_user_chat_model.dart b/lib/models/chat/get_search_user_chat_model.dart index ceee0de..31d1085 100644 --- a/lib/models/chat/get_search_user_chat_model.dart +++ b/lib/models/chat/get_search_user_chat_model.dart @@ -19,21 +19,21 @@ class ChatUserModel { } class ChatUser { - ChatUser({ - this.id, - this.userName, - this.email, - this.phone, - this.title, - this.userStatus, - this.image, - this.unreadMessageCount, - this.userAction, - this.isPin, - this.isFav, - this.isAdmin, - this.isTyping, - }); + ChatUser( + {this.id, + this.userName, + this.email, + this.phone, + this.title, + this.userStatus, + this.image, + this.unreadMessageCount, + this.userAction, + this.isPin, + this.isFav, + this.isAdmin, + this.isTyping, + this.isLoadingCounter}); int? id; String? userName; @@ -48,6 +48,7 @@ class ChatUser { bool? isFav; bool? isAdmin; bool? isTyping; + bool? isLoadingCounter; factory ChatUser.fromJson(Map json) => ChatUser( id: json["id"] == null ? null : json["id"], @@ -63,6 +64,7 @@ class ChatUser { isFav: json["isFav"] == null ? null : json["isFav"], isAdmin: json["isAdmin"] == null ? null : json["isAdmin"], isTyping: false, + isLoadingCounter: true, ); Map toJson() => { diff --git a/lib/models/chat/get_single_user_chat_list_model.dart b/lib/models/chat/get_single_user_chat_list_model.dart index 6a35f0e..07b2f51 100644 --- a/lib/models/chat/get_single_user_chat_list_model.dart +++ b/lib/models/chat/get_single_user_chat_list_model.dart @@ -1,32 +1,35 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; + List singleUserChatModelFromJson(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); String singleUserChatModelToJson(List data) => json.encode(List.from(data.map((x) => x.toJson()))); class SingleUserChatModel { - SingleUserChatModel({ - this.userChatHistoryId, - this.userChatHistoryLineId, - this.contant, - this.contantNo, - this.currentUserId, - this.currentUserName, - this.targetUserId, - this.targetUserName, - this.encryptedTargetUserId, - this.encryptedTargetUserName, - this.chatEventId, - this.fileTypeId, - this.isSeen, - this.isDelivered, - this.createdDate, - this.chatSource, - this.conversationId, - this.fileTypeResponse, - this.userChatReplyResponse, - this.isReplied, - }); + SingleUserChatModel( + {this.userChatHistoryId, + this.userChatHistoryLineId, + this.contant, + this.contantNo, + this.currentUserId, + this.currentUserName, + this.targetUserId, + this.targetUserName, + this.encryptedTargetUserId, + this.encryptedTargetUserName, + this.chatEventId, + this.fileTypeId, + this.isSeen, + this.isDelivered, + this.createdDate, + this.chatSource, + this.conversationId, + this.fileTypeResponse, + this.userChatReplyResponse, + this.isReplied, + this.isImageLoaded, + this.image}); int? userChatHistoryId; int? userChatHistoryLineId; @@ -48,29 +51,32 @@ class SingleUserChatModel { FileTypeResponse? fileTypeResponse; UserChatReplyResponse? userChatReplyResponse; bool? isReplied; + bool? isImageLoaded; + Uint8List? image; factory SingleUserChatModel.fromJson(Map json) => SingleUserChatModel( - userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], - userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"], - contant: json["contant"] == null ? null : json["contant"], - contantNo: json["contantNo"] == null ? null : json["contantNo"], - currentUserId: json["currentUserId"] == null ? null : json["currentUserId"], - currentUserName: json["currentUserName"] == null ? null : json["currentUserName"], - targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], - targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], - encryptedTargetUserId: json["encryptedTargetUserId"] == null ? null : json["encryptedTargetUserId"], - encryptedTargetUserName: json["encryptedTargetUserName"] == null ? null : json["encryptedTargetUserName"], - chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], - fileTypeId: json["fileTypeId"], - isSeen: json["isSeen"] == null ? null : json["isSeen"], - isDelivered: json["isDelivered"] == null ? null : json["isDelivered"], - createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), - chatSource: json["chatSource"] == null ? null : json["chatSource"], - conversationId: json["conversationId"] == null ? null : json["conversationId"], - fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), - userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]), - isReplied: false, - ); + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + userChatHistoryLineId: json["userChatHistoryLineId"] == null ? null : json["userChatHistoryLineId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + currentUserId: json["currentUserId"] == null ? null : json["currentUserId"], + currentUserName: json["currentUserName"] == null ? null : json["currentUserName"], + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + encryptedTargetUserId: json["encryptedTargetUserId"] == null ? null : json["encryptedTargetUserId"], + encryptedTargetUserName: json["encryptedTargetUserName"] == null ? null : json["encryptedTargetUserName"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + fileTypeId: json["fileTypeId"], + isSeen: json["isSeen"] == null ? null : json["isSeen"], + isDelivered: json["isDelivered"] == null ? null : json["isDelivered"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + chatSource: json["chatSource"] == null ? null : json["chatSource"], + conversationId: json["conversationId"] == null ? null : json["conversationId"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + userChatReplyResponse: json["userChatReplyResponse"] == null ? null : UserChatReplyResponse.fromJson(json["userChatReplyResponse"]), + isReplied: false, + isImageLoaded: false, + image: null); Map toJson() => { "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, @@ -138,6 +144,8 @@ class UserChatReplyResponse { this.targetUserId, this.targetUserName, this.fileTypeResponse, + this.isImageLoaded, + this.image, }); int? userChatHistoryId; @@ -149,18 +157,21 @@ class UserChatReplyResponse { int? targetUserId; String? targetUserName; FileTypeResponse? fileTypeResponse; + bool? isImageLoaded; + Uint8List? image; factory UserChatReplyResponse.fromJson(Map json) => UserChatReplyResponse( - userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], - chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], - contant: json["contant"] == null ? null : json["contant"], - contantNo: json["contantNo"] == null ? null : json["contantNo"], - fileTypeId: json["fileTypeId"], - createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), - targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], - targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], - fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), - ); + userChatHistoryId: json["userChatHistoryId"] == null ? null : json["userChatHistoryId"], + chatEventId: json["chatEventId"] == null ? null : json["chatEventId"], + contant: json["contant"] == null ? null : json["contant"], + contantNo: json["contantNo"] == null ? null : json["contantNo"], + fileTypeId: json["fileTypeId"], + createdDate: json["createdDate"] == null ? null : DateTime.parse(json["createdDate"]), + targetUserId: json["targetUserId"] == null ? null : json["targetUserId"], + targetUserName: json["targetUserName"] == null ? null : json["targetUserName"], + fileTypeResponse: json["fileTypeResponse"] == null ? null : FileTypeResponse.fromJson(json["fileTypeResponse"]), + isImageLoaded: false, + image: null); Map toJson() => { "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, diff --git a/lib/models/marathon/marathon_generic_model.dart b/lib/models/marathon/marathon_generic_model.dart new file mode 100644 index 0000000..d0a0d52 --- /dev/null +++ b/lib/models/marathon/marathon_generic_model.dart @@ -0,0 +1,31 @@ +class MarathonGenericModel { + MarathonGenericModel({ + this.data, + this.isSuccessful, + this.message, + this.statusCode, + this.errors, + }); + + dynamic data; + bool? isSuccessful; + String? message; + int? statusCode; + dynamic errors; + + factory MarathonGenericModel.fromJson(Map json) => MarathonGenericModel( + data: json["data"], + isSuccessful: json["isSuccessful"], + message: json["message"], + statusCode: json["statusCode"], + errors: json["errors"], + ); + + Map toJson() => { + "data": data, + "isSuccessful": isSuccessful, + "message": message, + "statusCode": statusCode, + "errors": errors, + }; +} diff --git a/lib/models/marathon/marathon_model.dart b/lib/models/marathon/marathon_model.dart new file mode 100644 index 0000000..b32530c --- /dev/null +++ b/lib/models/marathon/marathon_model.dart @@ -0,0 +1,256 @@ +class MarathonDetailModel { + String? id; + String? titleEn; + String? titleAr; + String? descEn; + String? descAr; + int? winDeciderTime; + int? winnersCount; + int? questGapTime; + String? startTime; + String? endTime; + int? marathoneStatusId; + String? scheduleTime; + int? selectedLanguage; + Projects? projects; + List? sponsors; + List? questions; + int? totalQuestions; + + MarathonDetailModel( + {id, + titleEn, + titleAr, + descEn, + descAr, + winDeciderTime, + winnersCount, + questGapTime, + startTime, + endTime, + marathoneStatusId, + scheduleTime, + selectedLanguage, + projects, + sponsors, + questions, + totalQuestions}); + + MarathonDetailModel.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + descEn = json['descEn']; + descAr = json['descAr']; + winDeciderTime = json['winDeciderTime']; + winnersCount = json['winnersCount']; + questGapTime = json['questGapTime']; + startTime = json['startTime']; + endTime = json['endTime']; + marathoneStatusId = json['marathoneStatusId']; + scheduleTime = json['scheduleTime']; + selectedLanguage = json['selectedLanguage']; + projects = json['projects'] != null + ? Projects.fromJson(json['projects']) + : null; + if (json['sponsors'] != null) { + sponsors = []; + json['sponsors'].forEach((v) { + sponsors!.add( Sponsors.fromJson(v)); + }); + } + if (json['questions'] != null) { + questions = []; + json['questions'].forEach((v) { + questions!.add( Questions.fromJson(v)); + }); + } + totalQuestions = json["totalQuestions"]; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['descEn'] = descEn; + data['descAr'] = descAr; + data['winDeciderTime'] = winDeciderTime; + data['winnersCount'] = winnersCount; + data['questGapTime'] = questGapTime; + data['startTime'] = startTime; + data['endTime'] = endTime; + data['marathoneStatusId'] = marathoneStatusId; + data['scheduleTime'] = scheduleTime; + data['selectedLanguage'] = selectedLanguage; + if (projects != null) { + data['projects'] = projects!.toJson(); + } + if (sponsors != null) { + data['sponsors'] = sponsors!.map((v) => v.toJson()).toList(); + } + if (questions != null) { + data['questions'] = questions!.map((v) => v.toJson()).toList(); + } + data['totalQuestions'] = totalQuestions; + + return data; + } +} + +class Projects { + String? id; + String? nameEn; + String? nameAr; + String? projectCode; + + Projects({id, nameEn, nameAr, projectCode}); + + Projects.fromJson(Map json) { + id = json['id']; + nameEn = json['nameEn']; + nameAr = json['nameAr']; + projectCode = json['projectCode']; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['nameEn'] = nameEn; + data['nameAr'] = nameAr; + data['projectCode'] = projectCode; + return data; + } +} + +class Sponsors { + String? id; + String? nameEn; + Null? nameAr; + String? image; + Null? video; + Null? logo; + List? sponsorPrizes; + + Sponsors( + {id, + nameEn, + nameAr, + image, + video, + logo, + sponsorPrizes}); + + Sponsors.fromJson(Map json) { + id = json['id']; + nameEn = json['nameEn']; + nameAr = json['nameAr']; + image = json['image']; + video = json['video']; + logo = json['logo']; + if (json['sponsorPrizes'] != null) { + sponsorPrizes = []; + json['sponsorPrizes'].forEach((v) { + sponsorPrizes!.add( SponsorPrizes.fromJson(v)); + }); + } + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['nameEn'] = nameEn; + data['nameAr'] = nameAr; + data['image'] = image; + data['video'] = video; + data['logo'] = logo; + if (sponsorPrizes != null) { + data['sponsorPrizes'] = + sponsorPrizes!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class SponsorPrizes { + String? id; + String? marathonPrizeEn; + String? marathonPrizeAr; + + SponsorPrizes({id, marathonPrizeEn, marathonPrizeAr}); + + SponsorPrizes.fromJson(Map json) { + id = json['id']; + marathonPrizeEn = json['marathonPrizeEn']; + marathonPrizeAr = json['marathonPrizeAr']; + } + + Map toJson() { + Map data = new Map(); + data['id'] = id; + data['marathonPrizeEn'] = marathonPrizeEn; + data['marathonPrizeAr'] = marathonPrizeAr; + return data; + } +} + +class Questions { + String? id; + String? titleEn; + String? titleAr; + String? marathonId; + int? questionTypeId; + int? questionTime; + int? nextQuestGap; + int? gapType; + String? gapValue; + String? gapImage; + int? questOptionsLimit; + List? questionOptions; + + Questions( + {id, + titleEn, + titleAr, + marathonId, + questionTypeId, + questionTime, + nextQuestGap, + gapType, + gapValue, + gapImage, + questOptionsLimit, + questionOptions}); + + Questions.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + marathonId = json['marathonId']; + questionTypeId = json['questionTypeId']; + questionTime = json['questionTime']; + nextQuestGap = json['nextQuestGap']; + gapType = json['gapType']; + gapValue = json['gapValue']; + gapImage = json['gapImage']; + questOptionsLimit = json['questOptionsLimit']; + questionOptions = json['questionOptions']; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['marathonId'] = marathonId; + data['questionTypeId'] = questionTypeId; + data['questionTime'] = questionTime; + data['nextQuestGap'] = nextQuestGap; + data['gapType'] = gapType; + data['gapValue'] = gapValue; + data['gapImage'] = gapImage; + data['questOptionsLimit'] = questOptionsLimit; + data['questionOptions'] = questionOptions; + return data; + } +} diff --git a/lib/models/marathon/question_model.dart b/lib/models/marathon/question_model.dart new file mode 100644 index 0000000..0bb42cd --- /dev/null +++ b/lib/models/marathon/question_model.dart @@ -0,0 +1,117 @@ +enum QuestionsOptionStatus { correct, wrong, selected, unSelected } + +enum QuestionCardStatus { question, wrongAnswer, correctAnswer, skippedAnswer, completed, findingWinner, winnerFound } + +class QuestionModel { + String? id; + String? titleEn; + String? titleAr; + String? marathonId; + int? questionTypeId; + int? questionTime; + int? nextQuestGap; + int? gapType; + String? gapText; + String? gapImage; + int? questOptionsLimit; + List? questionOptions; + + QuestionModel({ + String? id, + String? titleEn, + String? titleAr, + String? marathonId, + int? questionTypeId, + int? questionTime, + int? nextQuestGap, + int? gapType, + String? gapText, + String? gapImage, + int? questOptionsLimit, + List? questionOptions, + }); + + QuestionModel.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + marathonId = json['marathonId']; + questionTypeId = json['questionTypeId']; + questionTime = json['questionTime']; + nextQuestGap = json['nextQuestGap']; + gapType = json['gapType']; + gapText = json['gapText']; + gapImage = json['gapImage']; + questOptionsLimit = json['questOptionsLimit']; + if (json['questionOptions'] != null) { + questionOptions = []; + json['questionOptions'].forEach((v) { + questionOptions!.add(QuestionOptions.fromJson(v)); + }); + } + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['marathonId'] = marathonId; + data['questionTypeId'] = questionTypeId; + data['questionTime'] = questionTime; + data['nextQuestGap'] = nextQuestGap; + data['gapType'] = gapType; + data['gapText'] = gapText; + data['gapImage'] = gapImage; + data['questOptionsLimit'] = questOptionsLimit; + if (questionOptions != null) { + data['questionOptions'] = questionOptions!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class QuestionOptions { + String? id; + String? titleEn; + String? titleAr; + String? questionId; + int? sequence; + String? image; + bool? isCorrectOption; + QuestionsOptionStatus? optionStatus; + + QuestionOptions({ + String? id, + String? titleEn, + String? titleAr, + String? questionId, + int? sequence, + String? image, + bool? isCorrectOption, + QuestionsOptionStatus? optionStatus, + }); + + QuestionOptions.fromJson(Map json) { + id = json['id']; + titleEn = json['titleEn']; + titleAr = json['titleAr']; + questionId = json['questionId']; + sequence = json['sequence']; + image = json['image']; + isCorrectOption = json['isCorrectOption']; + optionStatus = QuestionsOptionStatus.unSelected; + } + + Map toJson() { + Map data = {}; + data['id'] = id; + data['titleEn'] = titleEn; + data['titleAr'] = titleAr; + data['questionId'] = questionId; + data['sequence'] = sequence; + data['image'] = image; + data['isCorrectOption'] = isCorrectOption; + return data; + } +} diff --git a/lib/api/chat/chat_provider_model.dart b/lib/provider/chat_provider_model.dart similarity index 56% rename from lib/api/chat/chat_provider_model.dart rename to lib/provider/chat_provider_model.dart index 7a27c1f..f3821f3 100644 --- a/lib/api/chat/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -1,23 +1,19 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.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/api/chat/chat_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; -import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/get_search_user_chat_model.dart'; import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; -import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart' as login; import 'package:mohem_flutter_app/models/chat/make_user_favotire_unfavorite_chat_model.dart' as fav; +import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/widgets/image_picker.dart'; -import 'package:signalr_netcore/signalr_client.dart'; import 'package:uuid/uuid.dart'; class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { @@ -26,9 +22,6 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { TextEditingController search = TextEditingController(); List userChatHistory = []; List? pChatHistory, searchedChats; - late HubConnection hubConnection; - L.Logger logger = L.Logger(); - bool hubConInitialized = false; String chatCID = ''; bool isLoading = true; bool isChatScreenActive = false; @@ -40,56 +33,20 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { List favUsersList = []; int paginationVal = 0; - Future getUserAutoLoginToken(BuildContext cxt) async { - Response response = await ApiClient().postJsonForResponse( - "${ApiConsts.chatServerBaseApiUrl}user/externaluserlogin", - { - "employeeNumber": AppState().memberInformationList!.eMPLOYEENUMBER.toString(), - "password": "FxIu26rWIKoF8n6mpbOmAjDLphzFGmpG", - }, - ); - login.UserAutoLoginModel userLoginResponse = login.userAutoLoginModelFromJson( - response.body, - ); - - if (userLoginResponse.response != null) { - hubConInitialized = true; - AppState().setchatUserDetails = userLoginResponse; - await buildHubConnection(); - } else { - Utils.showToast( - userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", - ); - return; - } + void registerEvents() { + hubConnection.on("OnUpdateUserStatusAsync", changeStatus); + hubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); + // hubConnection.on("OnSeenChatUserAsync", onChatSeen); + //hubConnection.on("OnUserTypingAsync", onUserTyping); + hubConnection.on("OnUserCountAsync", userCountAsync); + hubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); + hubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); + hubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); } - Future?> getChatMemberFromSearch(String sName, int cUserId) async { - Response response = await ApiClient().getJsonForResponse( - "${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatSearchMember}$sName/$cUserId", - token: AppState().chatDetails!.response!.token, - ); - return searchUserJsonModel(response.body); - } - - List searchUserJsonModel(String str) => List.from( - json.decode(str).map( - (x) => ChatUser.fromJson(x), - ), - ); - void getUserRecentChats() async { - Response response = await ApiClient().getJsonForResponse( - "${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatRecentUrl}", - token: AppState().chatDetails!.response!.token, - ); - ChatUserModel recentChat = userToList(response.body); - - Response favRes = await ApiClient().getJsonForResponse( - "${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatFavoriteUsers}${AppState().chatDetails!.response!.id}", - token: AppState().chatDetails!.response!.token, - ); - ChatUserModel favUList = userToList(favRes.body); + ChatUserModel recentChat = await ChatApiClient().getRecentChats(); + ChatUserModel favUList = await ChatApiClient().getFavUsers(); if (favUList.response != null && recentChat.response != null) { favUsersList = favUList.response!; @@ -108,21 +65,20 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } pChatHistory = recentChat.response ?? []; pChatHistory!.sort( - (ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo( - b.userName!.toLowerCase(), - ), + (ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase()), ); - searchedChats = pChatHistory; isLoading = false; + await invokeUserChatHistoryNotDeliveredAsync( + userId: int.parse( + AppState().chatDetails!.response!.id.toString(), + ), + ); notifyListeners(); } - Future getUserChatHistoryNotDeliveredAsync({required int userId}) async { - await hubConnection.invoke( - "GetUserChatHistoryNotDeliveredAsync", - args: [userId], - ); + Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async { + await hubConnection.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); return ""; } @@ -131,10 +87,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { if (isNewChat) userChatHistory = []; if (!loadMore) paginationVal = 0; isChatScreenActive = true; - Response response = await ApiClient().getJsonForResponse( - "${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatSingleUserHistoryUrl}/$senderUID/$receiverUID/$paginationVal", - token: AppState().chatDetails!.response!.token, - ); + Response response = await ChatApiClient().getSingleUserChatHistory(senderUID: senderUID, receiverUID: receiverUID, loadMore: loadMore, paginationVal: paginationVal); if (response.statusCode == 204) { if (isNewChat) { userChatHistory = []; @@ -144,27 +97,15 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } else { if (loadMore) { - List temp = getSingleUserChatModel( - response.body, - ).reversed.toList(); - userChatHistory.addAll( - temp, - ); + List temp = getSingleUserChatModel(response.body).reversed.toList(); + userChatHistory.addAll(temp); } else { - userChatHistory = getSingleUserChatModel( - response.body, - ).reversed.toList(); + userChatHistory = getSingleUserChatModel(response.body).reversed.toList(); } } - await getUserChatHistoryNotDeliveredAsync( - userId: senderUID, - ); isLoading = false; notifyListeners(); - markRead( - userChatHistory, - receiverUID, - ); + markRead(userChatHistory, receiverUID); generateConvId(); } @@ -173,134 +114,74 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { chatCID = uuid.v4(); } - void markRead(List data, reciverID) { - for (SingleUserChatModel element in data!) { - if (!element.isSeen!) { - dynamic data = [ - { - "userChatHistoryId": element.userChatHistoryId, - "TargetUserId": element.targetUserId, - "isDelivered": true, - "isSeen": true, + void markRead(List data, int receiverID) { + if (data != null) { + for (SingleUserChatModel element in data!) { + if (element.isSeen != null) { + if (!element.isSeen!) { + print("Found Un Read"); + logger.d(jsonEncode(element)); + dynamic data = [ + {"userChatHistoryId": element.userChatHistoryId, "TargetUserId": element.targetUserId, "isDelivered": true, "isSeen": true} + ]; + updateUserChatHistoryStatusAsync(data); } - ]; - updateUserChatHistoryStatusAsync(data); + } } - } - for (ChatUser element in searchedChats!) { - if (element.id == reciverID) { - element.unreadMessageCount = 0; - notifyListeners(); + for (ChatUser element in searchedChats!) { + if (element.id == receiverID) { + element.unreadMessageCount = 0; + notifyListeners(); + } } } } void updateUserChatHistoryStatusAsync(List data) { - hubConnection.invoke( - "UpdateUserChatHistoryStatusAsync", - args: [data], - ); + try { + hubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + } catch (e) { + throw e; + } } - List getSingleUserChatModel(String str) => List.from( - json.decode(str).map( - (x) => SingleUserChatModel.fromJson(x), - ), - ); + void updateUserChatHistoryOnMsg(List data) { + try { + hubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); + } catch (e) { + throw e; + } + } - ChatUserModel userToList(String str) => ChatUserModel.fromJson( - json.decode(str), - ); + List getSingleUserChatModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); Future uploadAttachments(String userId, File file) async { dynamic result; - dynamic request = MultipartRequest( - 'POST', - Uri.parse( - '${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatMediaImageUploadUrl}', - ), - ); - request.fields.addAll({'userId': userId, 'fileSource': '1'}); - request.files.add(await MultipartFile.fromPath('files', file.path)); - request.headers.addAll( - { - 'Authorization': 'Bearer ${AppState().chatDetails!.response!.token}', - }, - ); try { - StreamedResponse response = await request.send(); + StreamedResponse response = await ChatApiClient().uploadMedia(userId, file); if (response.statusCode == 200) { - result = jsonDecode( - await response.stream.bytesToString(), - ); + result = jsonDecode(await response.stream.bytesToString()); } else { result = []; } } catch (e) { - if (kDebugMode) { - print(e); - } + throw e; } - ; - return result; - } - Future buildHubConnection() async { - HttpConnectionOptions httpOp = HttpConnectionOptions( - skipNegotiation: false, - logMessageContent: true, - ); - hubConnection = HubConnectionBuilder() - .withUrl( - ApiConsts.chatHubConnectionUrl + "?UserId=${AppState().chatDetails!.response!.id}&source=Web&access_token=${AppState().chatDetails!.response!.token}", - options: httpOp, - ) - .withAutomaticReconnect( - retryDelays: [2000, 5000, 10000, 20000], - ) - .configureLogging( - Logger("Loggin"), - ) - .build(); - hubConnection.onclose( - ({Exception? error}) {}, - ); - hubConnection.onreconnecting( - ({Exception? error}) {}, - ); - hubConnection.onreconnected( - ({String? connectionId}) {}, - ); - if (hubConnection.state != HubConnectionState.Connected) { - await hubConnection.start(); - getUserChatHistoryNotDeliveredAsync( - userId: int.parse( - AppState().chatDetails!.response!.id.toString(), - ), - ); - hubConnection.on("OnUpdateUserStatusAsync", changeStatus); - hubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); - // hubConnection.on("OnSeenChatUserAsync", onChatSeen); - - //hubConnection.on("OnUserTypingAsync", onUserTyping); - hubConnection.on("OnUserCountAsync", userCountAsync); - hubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); - hubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); - hubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); - } + return result; } void updateUserChatStatus(List? args) { dynamic items = args!.toList(); - for (dynamic cItem in items[0]) { + for (var cItem in items[0]) { for (SingleUserChatModel chat in userChatHistory) { - if (chat.userChatHistoryId.toString() == cItem["userChatHistoryId"].toString()) { + if (cItem["contantNo"].toString() == chat.contantNo.toString()) { chat.isSeen = cItem["isSeen"]; chat.isDelivered = cItem["isDelivered"]; - notifyListeners(); } } } + notifyListeners(); } void onChatSeen(List? args) { @@ -342,32 +223,23 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { void chatNotDelivered(List? args) { dynamic items = args!.toList(); + logger.d(items); for (dynamic item in items[0]) { - searchedChats!.forEach((element) { - if (element.id == item["currentUserId"]) { - var val = element.unreadMessageCount == null ? 0 : element.unreadMessageCount; - element.unreadMessageCount = val! + 1; - } - }); - // dynamic data = [ - // { - // "userChatHistoryId": item["userChatHistoryId"], - // "TargetUserId": item["targetUserId"], - // "isDelivered": true, - // "isSeen": true, - // } - // ]; - // updateUserChatHistoryStatusAsync(data); + searchedChats!.forEach( + (ChatUser element) { + if (element.id == item["currentUserId"]) { + int? val = element.unreadMessageCount ?? 0; + element.unreadMessageCount = val! + 1; + } + element.isLoadingCounter = false; + }, + ); } notifyListeners(); } void changeStatus(List? args) { - if (kDebugMode) { - // print("================= Status Online // Offline ===================="); - } dynamic items = args!.toList(); - // logger.d(items); for (ChatUser user in searchedChats!) { if (user.id == items.first["id"]) { user.userStatus = items.first["userStatus"]; @@ -401,32 +273,30 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { data.first.targetUserName = temp.first.currentUserName; data.first.currentUserId = temp.first.targetUserId; data.first.currentUserName = temp.first.targetUserName; + if (data.first.fileTypeId == 12 || data.first.fileTypeId == 4 || data.first.fileTypeId == 3) { + data.first.image = await ChatApiClient().downloadURL(fileName: data.first.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg"); + } + if (data.first.userChatReplyResponse != null) { + if (data.first.fileTypeResponse != null) { + if (data.first.userChatReplyResponse!.fileTypeId == 12 || data.first.userChatReplyResponse!.fileTypeId == 4 || data.first.userChatReplyResponse!.fileTypeId == 3) { + data.first.userChatReplyResponse!.image = + await ChatApiClient().downloadURL(fileName: data.first.userChatReplyResponse!.contant!, fileTypeDescription: data.first.fileTypeResponse!.fileTypeDescription ?? "image/jpg"); + data.first.userChatReplyResponse!.isImageLoaded = true; + } + } + } } + userChatHistory.insert(0, data.first); - // searchedChats!.forEach((element) { - // if (element.id == data.first.currentUserId) { - // var val = element.unreadMessageCount == null ? 0 : element.unreadMessageCount; - // element.unreadMessageCount = val! + 1; - // } - // }); var list = [ - { - "userChatHistoryId": data.first.userChatHistoryId, - "TargetUserId": data.first.targetUserId, - "isDelivered": true, - "isSeen": isChatScreenActive ? true : false, - } + {"userChatHistoryId": data.first.userChatHistoryId, "TargetUserId": temp.first.targetUserId, "isDelivered": true, "isSeen": isChatScreenActive ? true : false} ]; - updateUserChatHistoryStatusAsync(list); - + updateUserChatHistoryOnMsg(list); notifyListeners(); - // if (isChatScreenActive) scrollToBottom(); } void onUserTyping(List? parameters) { - // print("==================== Typing Active =================="); - // logger.d(parameters); for (ChatUser user in searchedChats!) { if (user.id == parameters![1] && parameters[0] == true) { user.isTyping = parameters[0] as bool?; @@ -509,34 +379,44 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future sendChatToServer( - {required int chatEventId, required fileTypeId, required int targetUserId, required String targetUserName, required chatReplyId, required bool isAttachment, required bool isReply}) async { + {required int chatEventId, + required fileTypeId, + required int targetUserId, + required String targetUserName, + required chatReplyId, + required bool isAttachment, + required bool isReply, + Uint8List? image, + required bool isImageLoaded}) async { Uuid uuid = const Uuid(); + var contentNo = uuid.v4(); var msg = message.text; SingleUserChatModel data = SingleUserChatModel( - chatEventId: chatEventId, - chatSource: 1, - contant: msg, - contantNo: uuid.v4(), - conversationId: chatCID, - createdDate: DateTime.now(), - currentUserId: AppState().chatDetails!.response!.id, - currentUserName: AppState().chatDetails!.response!.userName, - targetUserId: targetUserId, - targetUserName: targetUserName, - isReplied: false, - fileTypeId: fileTypeId, - userChatReplyResponse: isReply ? UserChatReplyResponse.fromJson(repliedMsg.first.toJson()) : null, - fileTypeResponse: isAttachment - ? FileTypeResponse( - fileTypeId: fileTypeId, - fileTypeName: getFileType(getFileExtension(selectedFile.path).toString()), - fileKind: getFileExtension(selectedFile.path), - fileName: selectedFile.path.split("/").last, - fileTypeDescription: getFileTypeDescription(getFileExtension(selectedFile.path).toString()), - ) - : null, - ); + 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; @@ -545,7 +425,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { notifyListeners(); String chatData = - '{"contant":"$msg","contantNo":"${uuid.v4()}","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; + '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; await hubConnection.invoke("AddChatUserAsync", args: [json.decode(chatData)]); } @@ -553,51 +433,68 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); if (contain.isEmpty) { searchedChats!.add( - ChatUser( - id: targetUserId, - userName: targetUserName, - ), + ChatUser(id: targetUserId, userName: targetUserName, unreadMessageCount: 0), ); notifyListeners(); } if (!isFileSelected && !isMsgReply) { - logger.d("Normal Text Message"); + print("Normal Text Msg"); if (message.text == null || message.text.isEmpty) { return; } - sendChatToServer(chatEventId: 1, fileTypeId: null, targetUserId: targetUserId, targetUserName: targetUserName, isAttachment: false, chatReplyId: null, isReply: false); - } + sendChatToServer( + chatEventId: 1, fileTypeId: null, targetUserId: targetUserId, targetUserName: targetUserName, isAttachment: false, chatReplyId: null, isReply: false, isImageLoaded: false, image: null); + } // normal Text msg if (isFileSelected && !isMsgReply) { + print("Normal Attachment Msg"); Utils.showLoading(context); - //logger.d("Normal Attachment Message"); dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile); + logger.d(value); String? ext = getFileExtension(selectedFile.path); Utils.hideLoading(context); - sendChatToServer(chatEventId: 2, fileTypeId: getFileType(ext.toString()), targetUserId: targetUserId, targetUserName: targetUserName, isAttachment: true, chatReplyId: null, isReply: false); - } + sendChatToServer( + chatEventId: 2, + fileTypeId: getFileType(ext.toString()), + targetUserId: targetUserId, + targetUserName: targetUserName, + isAttachment: true, + chatReplyId: null, + isReply: false, + isImageLoaded: true, + image: selectedFile.readAsBytesSync()); + } // normal attachemnt msg if (!isFileSelected && isMsgReply) { - // logger.d("Normal Text Message With Reply"); + print("Normal Text To Text Reply"); if (message.text == null || message.text.isEmpty) { return; } sendChatToServer( - chatEventId: 1, fileTypeId: null, targetUserId: targetUserId, targetUserName: targetUserName, chatReplyId: repliedMsg.first.userChatHistoryId, isAttachment: false, isReply: true); - } + chatEventId: 1, + fileTypeId: null, + targetUserId: targetUserId, + targetUserName: targetUserName, + chatReplyId: repliedMsg.first.userChatHistoryId, + isAttachment: false, + isReply: true, + isImageLoaded: repliedMsg.first.isImageLoaded!, + image: repliedMsg.first.image); + } // reply msg over image && normal if (isFileSelected && isMsgReply) { - // logger.d("Attachment Message With Reply"); + print("Reply With File"); Utils.showLoading(context); dynamic value = await uploadAttachments(AppState().chatDetails!.response!.id.toString(), selectedFile); String? ext = getFileExtension(selectedFile.path); Utils.hideLoading(context); sendChatToServer( - chatEventId: 2, - fileTypeId: getFileType(ext.toString()), - targetUserId: targetUserId, - targetUserName: targetUserName, - isAttachment: true, - chatReplyId: repliedMsg.first.userChatHistoryId, - isReply: true, - ); + chatEventId: 2, + fileTypeId: getFileType(ext.toString()), + targetUserId: targetUserId, + targetUserName: targetUserName, + isAttachment: true, + chatReplyId: repliedMsg.first.userChatHistoryId, + isReply: true, + isImageLoaded: true, + image: selectedFile.readAsBytesSync()); } } @@ -697,9 +594,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future favoriteUser({required int userID, required int targetUserID}) async { - Response response = - await ApiClient().postJsonForResponse("${ApiConsts.chatServerBaseApiUrl}FavUser/addFavUser", {"targetUserId": targetUserID, "userId": userID}, token: AppState().chatDetails!.response!.token); - fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); + fav.FavoriteChatUser favoriteChatUser = await ChatApiClient().favUser(userID: userID, targetUserID: targetUserID); if (favoriteChatUser.response != null) { for (ChatUser user in searchedChats!) { if (user.id == favoriteChatUser.response!.targetUserId!) { @@ -712,21 +607,24 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } Future unFavoriteUser({required int userID, required int targetUserID}) async { - Response response = await ApiClient() - .postJsonForResponse("${ApiConsts.chatServerBaseApiUrl}FavUser/deleteFavUser", {"targetUserId": targetUserID, "userId": userID}, token: AppState().chatDetails!.response!.token); - fav.FavoriteChatUser favoriteChatUser = fav.FavoriteChatUser.fromRawJson(response.body); + fav.FavoriteChatUser favoriteChatUser = await ChatApiClient().unFavUser(userID: userID, targetUserID: targetUserID); + if (favoriteChatUser.response != null) { - for (var user in searchedChats!) { + for (ChatUser user in searchedChats!) { if (user.id == favoriteChatUser.response!.targetUserId!) { user.isFav = favoriteChatUser.response!.isFav; } } - favUsersList.removeWhere((ChatUser element) => element.id == targetUserID); + favUsersList.removeWhere( + (ChatUser element) => element.id == targetUserID, + ); } + notifyListeners(); } void clearSelections() { + print("Hereee i am "); searchedChats = pChatHistory; search.clear(); isChatScreenActive = false; @@ -735,6 +633,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { isFileSelected = false; repliedMsg = []; sFileType = ""; + isMsgReply = false; notifyListeners(); } @@ -767,10 +666,28 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { // } void msgScroll() { - scrollController.animateTo( - scrollController.position.minScrollExtent - 100, - duration: const Duration(milliseconds: 500), - curve: Curves.easeIn, - ); - } + // scrollController.animateTo( + // // index: 150, + // duration: Duration(seconds: 2), + // curve: Curves.easeInOutCubic); + // scrollController.animateTo( + // scrollController.position.minScrollExtent - 100, + // duration: const Duration(milliseconds: 500), + // curve: Curves.easeIn, + // ); + } + +// Future getDownLoadFile(String fileName) async { +// var data = await ChatApiClient().downloadURL(fileName: "data"); +// Image.memory(data); +// } + +// void getUserChatHistoryNotDeliveredAsync({required int userId}) async { +// try { +// await hubConnection.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); +// } finally { +// hubConnection.off("GetUserChatHistoryNotDeliveredAsync", method: chatNotDelivered); +// } +// } + } diff --git a/lib/provider/dashboard_provider_model.dart b/lib/provider/dashboard_provider_model.dart index 948e5b4..59b62b6 100644 --- a/lib/provider/dashboard_provider_model.dart +++ b/lib/provider/dashboard_provider_model.dart @@ -1,13 +1,17 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/api/chat/chat_api_client.dart'; import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/chat/chat_count_conversation_model.dart'; +import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart'; import 'package:mohem_flutter_app/models/dashboard/drawer_menu_item_model.dart'; import 'package:mohem_flutter_app/models/dashboard/get_accrual_balances_list_model.dart'; import 'package:mohem_flutter_app/models/dashboard/get_attendance_tracking_list_model.dart'; @@ -21,6 +25,7 @@ import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/itg/itg_response_model.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; +import 'package:signalr_netcore/signalr_client.dart'; /// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool // ignore: prefer_mixin @@ -37,6 +42,7 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { //Chat bool isChatCounterLoding = true; + bool isChatHubLoding = true; int chatUConvCounter = 0; //Misssing Swipe @@ -97,6 +103,7 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { leaveBalanceAccrual = null; isChatCounterLoding = true; + isChatHubLoding = true; chatUConvCounter = 0; ticketBalance = 0; @@ -287,6 +294,26 @@ class DashboardProviderModel with ChangeNotifier, DiagnosticableTreeMixin { } } + Future getUserAutoLoginToken() async { + UserAutoLoginModel userLoginResponse = await ChatApiClient().getUserLoginToken(); + if (userLoginResponse.response != null) { + AppState().setchatUserDetails = userLoginResponse; + } else { + Utils.showToast( + userLoginResponse.errorResponses!.first.fieldName.toString() + " Erorr", + ); + } + } + + Future getHubConnection() async { + HubConnection hub; + HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hub = HubConnectionBuilder() + .withUrl(ApiConsts.chatHubConnectionUrl + "?UserId=${AppState().chatDetails!.response!.id}&source=Web&access_token=${AppState().chatDetails!.response!.token}", options: httpOp) + .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]).build(); + isChatHubLoding = false; + return hub; + } void notify() { notifyListeners(); } diff --git a/lib/ui/attendance/add_vacation_rule_screen.dart b/lib/ui/attendance/add_vacation_rule_screen.dart index caa1ee3..5d4bf75 100644 --- a/lib/ui/attendance/add_vacation_rule_screen.dart +++ b/lib/ui/attendance/add_vacation_rule_screen.dart @@ -316,7 +316,7 @@ class _AddVacationRuleScreenState extends State { 12.height, PopupMenuButton( child: DynamicTextFieldWidget( - "Notification", + LocaleKeys.notification.tr(), selectedItemTypeNotification == null ? LocaleKeys.selectNotification.tr() : selectedItemTypeNotification!.nOTIFICATIONDISPLAYNAME!, isEnable: false, isPopup: true, diff --git a/lib/ui/chat/call/chat_incoming_call_screen.dart b/lib/ui/chat/call/chat_incoming_call_screen.dart index a0b8247..1b6a5aa 100644 --- a/lib/ui/chat/call/chat_incoming_call_screen.dart +++ b/lib/ui/chat/call/chat_incoming_call_screen.dart @@ -91,6 +91,8 @@ class _IncomingCallState extends State with SingleTickerProviderSt mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceAround, children: const [ + + // todo @aamir, need to use extension mehtods Text( "Aamir Saleem Ahmad", style: TextStyle( diff --git a/lib/ui/chat/chat_bubble.dart b/lib/ui/chat/chat_bubble.dart index 1696a49..8995da1 100644 --- a/lib/ui/chat/chat_bubble.dart +++ b/lib/ui/chat/chat_bubble.dart @@ -1,150 +1,237 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/api/api_client.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/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:sizer/sizer.dart'; +import 'package:mohem_flutter_app/main.dart'; +import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; +import 'package:mohem_flutter_app/ui/chat/chat_full_image_preview.dart'; +import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; + +// todo: @aamir use extension methods, and use correct widgets. class ChatBubble extends StatelessWidget { - const ChatBubble( - {Key? key, - required this.text, - required this.replyText, - required this.isCurrentUser, - required this.isSeen, - required this.isDelivered, - required this.dateTime, - required this.isReplied, - required this.userName}) - : super(key: key); - final String text; - final String replyText; - final bool isCurrentUser; - final bool isSeen; - final bool isDelivered; + ChatBubble({Key? key, required this.dateTime, required this.cItem}) : super(key: key); final String dateTime; - final bool isReplied; - final String userName; + final SingleUserChatModel cItem; + + bool isCurrentUser = false; + bool isSeen = false; + bool isReplied = false; + int? fileTypeID; + + String? fileTypeDescription; + bool isDelivered = false; + String userName = ''; + + void makeAssign() { + isCurrentUser = cItem.currentUserId == AppState().chatDetails!.response!.id ? true : false; + isSeen = cItem.isSeen == true ? true : false; + isReplied = cItem.userChatReplyResponse != null ? true : false; + fileTypeID = cItem.fileTypeId; + fileTypeDescription = cItem.fileTypeResponse != null ? cItem.fileTypeResponse!.fileTypeDescription : ""; + isDelivered = cItem.currentUserId == AppState().chatDetails!.response!.id && cItem.isDelivered == true ? true : false; + userName = AppState().chatDetails!.response!.userName == cItem.currentUserName.toString() ? "You" : cItem.currentUserName.toString(); + } @override Widget build(BuildContext context) { - return Padding( - // padding: EdgeInsets.zero, - padding: EdgeInsets.only( - left: isCurrentUser ? 110 : 20, - right: isCurrentUser ? 20 : 110, - bottom: 9, - ), + makeAssign(); + return isCurrentUser ? currentUser(context) : receiptUser(context); + } - child: Align( - alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft, - child: DecoratedBox( - decoration: BoxDecoration( - color: MyColors.white, - gradient: isCurrentUser - ? null - : const LinearGradient( - transform: GradientRotation( - .46, - ), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor, + Widget currentUser(context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isReplied) + ClipRRect( + borderRadius: BorderRadius.circular(5.0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border( + left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white), + ), + color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), + ), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (userName).toText12(color: MyColors.gradiantStartColor, isBold: false).paddingOnly(right: 5, top: 5, bottom: 0, left: 5), + (cItem.userChatReplyResponse != null ? cItem.userChatReplyResponse!.contant.toString() : "") + .toText10(color: isCurrentUser ? MyColors.grey71Color : MyColors.white.withOpacity(0.5), isBold: false, maxlines: 4) + .paddingOnly(right: 5, top: 5, bottom: 8, left: 5), ], - ), - borderRadius: BorderRadius.circular( - 10, - ), - ), - child: Padding( - padding: EdgeInsets.only( - top: isReplied ? 8 : 5, - right: 8, - left: 8, - bottom: 5, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - if (isReplied) - ClipRRect( - borderRadius: BorderRadius.circular( - 5.0, - ), - child: Container( - decoration: BoxDecoration( - border: Border( - left: BorderSide( - width: 6, - color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white, - ), - ), - color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), - ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (userName) - .toText12( - color: MyColors.gradiantStartColor, - isBold: false, - ) - .paddingOnly( - right: 5, - top: 5, - bottom: 0, - left: 5, - ), - replyText - .toText10( - color: isCurrentUser ? MyColors.grey71Color : MyColors.white.withOpacity(0.5), - isBold: false, - maxlines: 4, - ) - .paddingOnly( - right: 5, - top: 5, - bottom: 8, - left: 5, - ), - ], - ), - ), - ], + ).expanded, + if (cItem.userChatReplyResponse != null && cItem.userChatReplyResponse!.fileTypeId == 12 || + cItem.userChatReplyResponse!.fileTypeId == 3 || + cItem.userChatReplyResponse!.fileTypeId == 4) + // Container( + // padding: EdgeInsets.all(0), // Border width + // decoration: BoxDecoration(color: Colors.red, borderRadius: const BorderRadius.all(Radius.circular(8))), + // child: ClipRRect( + // borderRadius: const BorderRadius.all( + // Radius.circular(8), + // ), + // child: SizedBox.fromSize( + // size: Size.fromRadius(8), // Image radius + // child: showImage( + // isReplyPreview: true, + // fileName: cItem.userChatReplyResponse!.contant!, + // fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg"), + // ), + // ), + // ), + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: SizedBox( + height: 32, + width: 32, + child: showImage( + isReplyPreview: true, + fileName: cItem.userChatReplyResponse!.contant!, + fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg") + .paddingOnly(left: 10, right: 10, bottom: 16, top: 16), ), ), - ), - if (isReplied) 8.height, - text.toText12( - color: isCurrentUser ? MyColors.grey57Color : MyColors.white, + ], + ), + ), + ).paddingOnly(right: 5, bottom: 7), + if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) + showImage(isReplyPreview: false, fileName: cItem.contant!, fileTypeDescription: cItem.fileTypeResponse!.fileTypeDescription).paddingOnly(right: 5).onPress(() { + showDialog(context: context, builder: (BuildContext context) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!)); + }), + cItem.contant!.toText12(), + Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + dateTime.toText10( + color: MyColors.grey41Color.withOpacity(.5), + ), + 7.width, + Icon(isDelivered ? Icons.done_all : Icons.done_all, color: isSeen ? MyColors.textMixColor : MyColors.grey9DColor, size: 14), + ], + ), + ), + ], + ).paddingOnly(top: 11, left: 13, right: 7, bottom: 5).objectContainerView(disablePadding: true).paddingOnly(left: MediaQuery.of(context).size.width * 0.3); + } + + Widget receiptUser(BuildContext context) { + return Container( + padding: const EdgeInsets.only(top: 11, left: 13, right: 7, bottom: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + gradient: const LinearGradient( + transform: GradientRotation(.83), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isReplied) + ClipRRect( + borderRadius: BorderRadius.circular(5.0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border(left: BorderSide(width: 6, color: isCurrentUser ? MyColors.gradiantStartColor : MyColors.white)), + color: isCurrentUser ? MyColors.black.withOpacity(0.10) : MyColors.black.withOpacity(0.30), ), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - dateTime.toText12( - color: isCurrentUser ? MyColors.grey41Color.withOpacity(.5) : MyColors.white.withOpacity(0.7), - ), - if (isCurrentUser) 5.width, - // if (isCurrentUser) - // Icon( - // isDelivered ? Icons.done_all : Icons.done_all, - // color: isSeen ? MyColors.textMixColor : MyColors.grey9DColor, - // size: 14, - // ), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (userName).toText12(color: MyColors.gradiantStartColor, isBold: false).paddingOnly(right: 5, top: 5, bottom: 0, left: 5), + (cItem.userChatReplyResponse != null ? cItem.userChatReplyResponse!.contant.toString() : "") + .toText10(color: isCurrentUser ? MyColors.grey71Color : MyColors.white.withOpacity(0.5), isBold: false, maxlines: 4) + .paddingOnly(right: 5, top: 5, bottom: 8, left: 5), + ], + ).expanded, + if (cItem.userChatReplyResponse != null && cItem.userChatReplyResponse!.fileTypeId == 12 || + cItem.userChatReplyResponse!.fileTypeId == 3 || + cItem.userChatReplyResponse!.fileTypeId == 4) + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: SizedBox( + height: 32, + width: 32, + child: showImage( + isReplyPreview: true, + fileName: cItem.userChatReplyResponse!.contant!, + fileTypeDescription: cItem.userChatReplyResponse!.fileTypeResponse!.fileTypeDescription ?? "image/jpg")), + ).paddingOnly(left: 10, right: 10, bottom: 16, top: 16) ], ), - ], - ), + ), + ).paddingOnly(right: 5, bottom: 7), + if (fileTypeID == 12 || fileTypeID == 4 || fileTypeID == 3) + showImage(isReplyPreview: false, fileName: cItem.contant!, fileTypeDescription: cItem.fileTypeResponse!.fileTypeDescription ?? "image/jpg").paddingOnly(right: 5).onPress(() { + showDialog(context: context, builder: (BuildContext context) => ChatImagePreviewScreen(imgTitle: cItem.contant!, img: cItem.image!)); + }) + else + (cItem.contant! ?? "").toText12(color: Colors.white), + Align( + alignment: Alignment.centerRight, + child: dateTime.toText10(color: Colors.white.withOpacity(.71)), ), - ), + ], ), - ); + ).paddingOnly(right: MediaQuery.of(context).size.width * 0.3); + } + + Widget showImage({required bool isReplyPreview, required String fileName, required String fileTypeDescription}) { + if (cItem.isImageLoaded! && cItem.image != null) { + return Image.memory( + cItem.image!, + height: isReplyPreview ? 32 : 140, + width: isReplyPreview ? 32 : 227, + fit: BoxFit.cover, + ); + } else { + return FutureBuilder( + future: ChatApiClient().downloadURL(fileName: fileName, fileTypeDescription: fileTypeDescription), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState != ConnectionState.waiting) { + if (snapshot.data == null) { + return SizedBox(); + } else { + cItem.image = snapshot.data; + cItem.isImageLoaded = true; + return Image.memory( + snapshot.data, + height: isReplyPreview ? 32 : 140, + width: isReplyPreview ? 32 : 227, + fit: BoxFit.cover, + ); + } + } else { + return SizedBox( + height: isReplyPreview ? 32 : 140, + width: isReplyPreview ? 32 : 227, + child: const Center(child: CircularProgressIndicator()), + ); + } + }, + ); + } } } diff --git a/lib/ui/chat/chat_detailed_screen.dart b/lib/ui/chat/chat_detailed_screen.dart index 4a8e70c..1d220bc 100644 --- a/lib/ui/chat/chat_detailed_screen.dart +++ b/lib/ui/chat/chat_detailed_screen.dart @@ -4,7 +4,6 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:mohem_flutter_app/api/chat/chat_provider_model.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; @@ -13,13 +12,17 @@ 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/call.dart'; +import 'package:mohem_flutter_app/models/chat/get_single_user_chat_list_model.dart'; +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 { @@ -38,18 +41,17 @@ class _ChatDetailScreenState extends State { void getMoreChat() async { if (userDetails != null) { data.paginationVal = data.paginationVal + 10; - if (userDetails != null) + if (userDetails != null) { data.getSingleUserChatHistory( senderUID: AppState().chatDetails!.response!.id!.toInt(), receiverUID: userDetails["targetUser"].id, loadMore: true, isNewChat: false, ); + } } await Future.delayed( - const Duration( - milliseconds: 1000, - ), + const Duration(milliseconds: 1000), ); _rc.loadComplete(); } @@ -68,284 +70,146 @@ class _ChatDetailScreenState extends State { } return Scaffold( - backgroundColor: const Color(0xFFF8F8F8), - appBar: AppBarWidget(context, - title: userDetails["targetUser"].userName.toString().replaceAll(".", " ").capitalizeFirstofEach, - showHomeButton: false, - image: userDetails["targetUser"].image, - actions: [ - IconButton( - onPressed: () { - // makeCall( - // callType: "AUDIO", - // con: data.hubConnection, - // ); - }, - icon: SvgPicture.asset( - "assets/icons/chat/call.svg", - width: 22, - height: 22, - ), - ), - IconButton( - onPressed: () { - // makeCall( - // callType: "VIDEO", - // con: data.hubConnection, - // ); - }, - icon: SvgPicture.asset( - "assets/icons/chat/video_call.svg", - width: 20, - height: 20, - ), - ), - 10.width, - ]), + backgroundColor: MyColors.backgroundColor, + appBar: AppBarWidget( + context, + title: userDetails["targetUser"].userName.toString().replaceAll(".", " ").capitalizeFirstofEach, + showHomeButton: false, + image: userDetails["targetUser"].image, + actions: [ + SvgPicture.asset("assets/icons/chat/call.svg", width: 21, height: 23).onPress(() { + // makeCall(callType: "AUDIO", con: hubConnection); + }), + 24.width, + SvgPicture.asset("assets/icons/chat/video_call.svg", width: 21, height: 18).onPress(() { + // makeCall(callType: "VIDEO", con: hubConnection); + }), + 21.width, + ], + ), body: Consumer( builder: (BuildContext context, ChatProviderModel m, Widget? child) { return (m.isLoading ? ChatHomeShimmer() : Column( children: [ - Expanded( - flex: 2, - child: SmartRefresher( - enablePullDown: false, - enablePullUp: true, - onLoading: () { - getMoreChat(); - }, - header: const MaterialClassicHeader( - color: MyColors.gradiantEndColor, - ), - controller: _rc, + SmartRefresher( + enablePullDown: false, + enablePullUp: true, + onLoading: () { + getMoreChat(); + }, + header: const MaterialClassicHeader( + color: MyColors.gradiantEndColor, + ), + controller: _rc, + reverse: true, + child: ListView.separated( + controller: m.scrollController, + shrinkWrap: true, + physics: const BouncingScrollPhysics(), reverse: true, - child: ListView.builder( - controller: m.scrollController, - shrinkWrap: true, - physics: const BouncingScrollPhysics(), - reverse: true, - itemCount: m.userChatHistory.length, - padding: const EdgeInsets.only(top: 20), - itemBuilder: (BuildContext context, int i) { - return SwipeTo( - iconColor: MyColors.lightGreenColor, - child: ChatBubble( - text: m.userChatHistory[i].contant.toString(), - replyText: m.userChatHistory[i].userChatReplyResponse != null ? m.userChatHistory[i].userChatReplyResponse!.contant.toString() : "", - isSeen: m.userChatHistory[i].isSeen == true ? true : false, - isCurrentUser: m.userChatHistory[i].currentUserId == AppState().chatDetails!.response!.id ? true : false, - isDelivered: m.userChatHistory[i].currentUserId == AppState().chatDetails!.response!.id && m.userChatHistory[i].isDelivered == true ? true : false, - dateTime: m.dateFormte(m.userChatHistory[i].createdDate!), - isReplied: m.userChatHistory[i].userChatReplyResponse != null ? true : false, - userName: AppState().chatDetails!.response!.userName == m.userChatHistory[i].currentUserName.toString() ? "You" : m.userChatHistory[i].currentUserName.toString(), - ), - onRightSwipe: () { - m.chatReply( - m.userChatHistory[i], - ); - }, - ); - }, - ), + itemCount: m.userChatHistory.length, + padding: const EdgeInsets.all(21), + separatorBuilder: (cxt, index) => 8.height, + itemBuilder: (BuildContext context, int i) { + return SwipeTo( + iconColor: MyColors.lightGreenColor, + child: ChatBubble( + dateTime: m.dateFormte(m.userChatHistory[i].createdDate!), + cItem: m.userChatHistory[i], + ), + onRightSwipe: () { + m.chatReply( + m.userChatHistory[i], + ); + }, + ).onPress(() { + logger.d(jsonEncode(m.userChatHistory[i])); + }); + }, ), - ), + ).expanded, if (m.isMsgReply) - Row( - children: [ - Container( - height: 80, - color: MyColors.textMixColor, - width: 6, - ), - Expanded( - child: Container( - height: 80, - color: MyColors.black.withOpacity(0.10), - child: ListTile( - title: (AppState().chatDetails!.response!.userName == m.repliedMsg.first.currentUserName.toString() - ? "You" - : m.repliedMsg.first.currentUserName.toString().replaceAll(".", " ")) - .toText14(color: MyColors.lightGreenColor), - subtitle: (m.repliedMsg.isNotEmpty ? m.repliedMsg.first.contant! : "").toText12( - color: MyColors.white, - maxLine: 2, - ), - trailing: GestureDetector( - onTap: m.closeMe, - child: Container( - decoration: BoxDecoration( - color: MyColors.white.withOpacity(0.5), - borderRadius: const BorderRadius.all( - Radius.circular(20), - ), - ), - child: const Icon( - Icons.close, - size: 23, - color: MyColors.white, - ), - ), - ), + SizedBox( + height: 82, + child: Row( + children: [ + Container(height: 82, color: MyColors.textMixColor, width: 6), + Container( + color: MyColors.darkTextColor.withOpacity(0.10), + padding: const EdgeInsets.only(top: 11, left: 14, bottom: 14, right: 21), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (AppState().chatDetails!.response!.userName == m.repliedMsg.first.currentUserName.toString() + ? "You" + : m.repliedMsg.first.currentUserName.toString().replaceAll(".", " ")) + .toText14(color: MyColors.lightGreenColor), + (m.repliedMsg.isNotEmpty ? m.repliedMsg.first.contant! : "").toText12(color: MyColors.grey71Color, maxLine: 2) + ], + ).expanded, + 12.width, + if (m.isMsgReply && m.repliedMsg.isNotEmpty) showReplyImage(m.repliedMsg), + 12.width, + const Icon(Icons.cancel, size: 23, color: MyColors.grey7BColor).onPress(m.closeMe), + ], ), - ), - ), - ], + ).expanded, + ], + ), ), if (m.isFileSelected && m.sFileType == ".png" || m.sFileType == ".jpeg" || m.sFileType == ".jpg") - Card( - margin: EdgeInsets.zero, - elevation: 0, - child: Padding( - padding: const EdgeInsets.only( - left: 20, - right: 20, - top: 20, - bottom: 0, - ), - child: Card( - margin: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(0), - ), - elevation: 0, - child: Container( - height: 200, - decoration: BoxDecoration( - image: DecorationImage( - image: FileImage( - m.selectedFile, - ), - fit: BoxFit.cover, - ), - borderRadius: const BorderRadius.all( - Radius.circular(0), - ), - ), - child: const SizedBox( - width: double.infinity, - height: 200, - ), - ), - ), + SizedBox(height: 200, width: double.infinity, child: Image.file(m.selectedFile, fit: BoxFit.cover)).paddingOnly(left: 21, right: 21, top: 21), + TextField( + controller: m.message, + decoration: InputDecoration( + hintText: m.isFileSelected ? m.selectedFile.path.split("/").last : LocaleKeys.typeheretoreply.tr(), + hintStyle: TextStyle(color: m.isFileSelected ? MyColors.darkTextColor : MyColors.grey98Color, fontSize: 14), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + filled: true, + fillColor: MyColors.white, + contentPadding: const EdgeInsets.only( + left: 21, + top: 20, + bottom: 20, ), - ), - Card( - margin: EdgeInsets.zero, - child: TextField( - controller: m.message, - decoration: InputDecoration( - hintText: m.isFileSelected ? m.selectedFile.path.split("/").last : LocaleKeys.typeheretoreply.tr(), - hintStyle: TextStyle( - color: m.isFileSelected ? MyColors.darkTextColor : MyColors.grey98Color, - fontSize: 14, - ), - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - contentPadding: EdgeInsets.only( - left: m.sFileType.isNotEmpty ? 10 : 20, - right: m.sFileType.isNotEmpty ? 0 : 5, - top: 20, - bottom: 20, - ), - prefixIcon: m.sFileType.isNotEmpty - ? Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, + prefixIconConstraints: const BoxConstraints(), + prefixIcon: m.sFileType.isNotEmpty + ? SvgPicture.asset(m.getType(m.sFileType), height: 30, width: 22, alignment: Alignment.center, fit: BoxFit.cover).paddingOnly(left: 21, right: 15) + : null, + suffixIcon: SizedBox( + width: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, // added line + children: [ + if (m.sFileType.isNotEmpty) + Row( children: [ - SvgPicture.asset( - m.getType(m.sFileType), - height: 30, - width: 25, - alignment: Alignment.center, - fit: BoxFit.cover, - ).paddingOnly(left: 20), + const Icon(Icons.cancel, size: 15, color: MyColors.redA3Color).paddingOnly(right: 5), + ("Clear").toText11(color: MyColors.redA3Color, isUnderLine: true).paddingOnly(left: 0), ], - ) - : null, - suffixIcon: SizedBox( - width: 96, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, // added line - children: [ - if (m.sFileType.isNotEmpty) - IconButton( - padding: EdgeInsets.zero, - alignment: Alignment.centerRight, - icon: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.max, - children: [ - Container( - decoration: const BoxDecoration( - color: MyColors.redA3Color, - borderRadius: BorderRadius.all( - Radius.circular(20), - ), - ), - child: const Icon( - Icons.close, - size: 15, - color: MyColors.white, - ), - ), - ("Clear") - .toText11( - color: MyColors.redA3Color, - ) - .paddingOnly( - left: 4, - ), - ], - ), - onPressed: () async { - m.removeAttachment(); - }, - ), - if (m.sFileType.isEmpty) - RotationTransition( - turns: const AlwaysStoppedAnimation(45 / 360), - child: IconButton( - padding: EdgeInsets.zero, - alignment: Alignment.topRight, - icon: const Icon( - Icons.attach_file_rounded, - size: 26, - color: MyColors.grey3AColor, - ), - onPressed: () async { - m.selectImageToUpload(context); - }, - ), - ), - IconButton( - alignment: Alignment.centerRight, - padding: EdgeInsets.zero, - icon: SvgPicture.asset( - "assets/icons/chat/chat_send_icon.svg", - height: 26, - width: 26, + ).onPress(() => m.removeAttachment()).paddingOnly(right: 25), + if (m.sFileType.isEmpty) + RotationTransition( + turns: const AlwaysStoppedAnimation(45 / 360), + child: const Icon(Icons.attach_file_rounded, size: 26, color: MyColors.grey3AColor).onPress( + () => m.selectImageToUpload(context), ), - onPressed: () { - m.sendChatMessage( - userDetails["targetUser"].id, - userDetails["targetUser"].userName, - context, - ); - }, - ) - ], - ), - ).paddingOnly( - right: 20, + ).paddingOnly(right: 25), + SvgPicture.asset("assets/icons/chat/chat_send_icon.svg", height: 26, width: 26).onPress( + () => m.sendChatMessage(userDetails["targetUser"].id, userDetails["targetUser"].userName, context), + ), + ], ), - ), + ).paddingOnly(right: 21), ), ), ], @@ -355,40 +219,56 @@ class _ChatDetailScreenState extends State { ); } + Widget showReplyImage(List data) { + if (data.first.isImageLoaded! && data.first.image != null) { + return Container( + width: 43, + height: 43, + decoration: BoxDecoration( + border: Border.all(color: MyColors.darkGrey3BColor, width: 1), borderRadius: BorderRadius.circular(10.0), image: DecorationImage(image: MemoryImage(data.first.image!), fit: BoxFit.cover)), + ); + } else { + return const SizedBox(); + } + } + void makeCall({required String callType, required HubConnection con}) async { print("================== Make call Triggered ============================"); - logger.d(jsonEncode(AppState().chatDetails!.response)); Map json = { "callerID": AppState().chatDetails!.response!.id!.toString(), - "callReciverID": userDetails["targetUser"].id.toString(), + "callReceiverID": userDetails["targetUser"].id.toString(), "notification_foreground": "true", - "message": "Aamir is calling ", + "message": "Aamir is calling", "title": "Video Call", "type": callType == "VIDEO" ? "Video" : "Audio", - "identity": "Aamir.Muhammad", - "name": "Aamir Saleem Ahmad", + "identity": AppState().chatDetails!.response!.userName, + "name": AppState().chatDetails!.response!.title, "is_call": "true", "is_webrtc": "true", - "contant": "Start video Call Aamir.Muhammad", + "contant": "Start video Call ${AppState().chatDetails!.response!.userName}", "contantNo": "775d1f11-62d9-6fcc-91f6-21f8c14559fb", "chatEventId": "3", "fileTypeId": null, - "currentUserId": "266642", + "currentUserId": AppState().chatDetails!.response!.id!.toString(), "chatSource": "1", "userChatHistoryLineRequestList": [ - {"isSeen": false, "isDelivered": false, "targetUserId": 341682, "targetUserStatus": 4} + { + "isSeen": false, + "isDelivered": false, + "targetUserId": userDetails["targetUser"].id, + "targetUserStatus": 4, + } ], // "server": "https://192.168.8.163:8086", "server": "https://livecareturn.hmg.com:8086", }; - - CallDataModel incomingCallData = CallDataModel.fromJson(json); + CallDataModel callData = CallDataModel.fromJson(json); await Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => OutGoingCall( isVideoCall: callType == "VIDEO" ? true : false, - OutGoingCallData: incomingCallData, + OutGoingCallData: callData, ), ), ); diff --git a/lib/ui/chat/chat_full_image_preview.dart b/lib/ui/chat/chat_full_image_preview.dart new file mode 100644 index 0000000..26364c5 --- /dev/null +++ b/lib/ui/chat/chat_full_image_preview.dart @@ -0,0 +1,41 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; + +class ChatImagePreviewScreen extends StatelessWidget { + const ChatImagePreviewScreen({Key? key, required this.imgTitle, required this.img}) : super(key: key); + + final String imgTitle; + final Uint8List img; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.all(10), + child: Stack( + alignment: Alignment.center, + children: [ + Image.memory( + img, + fit: BoxFit.cover, + height: 400, + width: double.infinity, + ).paddingAll(10), + const Positioned( + right: 0, + top: 0, + child: Icon(Icons.cancel, color: MyColors.redA3Color, size: 35), + ) + ], + ), + ), + ); + } +} diff --git a/lib/ui/chat/chat_home.dart b/lib/ui/chat/chat_home.dart index b2db98a..3bf8cda 100644 --- a/lib/ui/chat/chat_home.dart +++ b/lib/ui/chat/chat_home.dart @@ -1,19 +1,13 @@ -import 'dart:convert'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:mohem_flutter_app/api/chat/chat_provider_model.dart'; -import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.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/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/ui/chat/chat_home_screen.dart'; import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart'; -import 'package:mohem_flutter_app/ui/screens/items_for_sale/fragments/items_for_sale.dart'; -import 'package:mohem_flutter_app/ui/screens/items_for_sale/fragments/my_posted_ads_fragment.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:provider/provider.dart'; @@ -31,32 +25,21 @@ class _ChatHomeState extends State { @override void initState() { - // TODO: implement initState super.initState(); data = Provider.of(context, listen: false); - data.getUserAutoLoginToken(context).whenComplete(() { - data.getUserRecentChats(); - }); } @override void dispose() { super.dispose(); data.clearAll(); - if (data.hubConInitialized) { - data.hubConnection.stop(); - } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MyColors.white, - appBar: AppBarWidget( - context, - title: LocaleKeys.chat.tr(), - showHomeButton: true, - ), + appBar: AppBarWidget(context, title: LocaleKeys.chat.tr(), showHomeButton: true), body: Column( children: [ Container( diff --git a/lib/ui/chat/chat_home_screen.dart b/lib/ui/chat/chat_home_screen.dart index 816aaec..cd2245d 100644 --- a/lib/ui/chat/chat_home_screen.dart +++ b/lib/ui/chat/chat_home_screen.dart @@ -1,14 +1,15 @@ +import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:mohem_flutter_app/api/chat/chat_provider_model.dart'; import 'package:mohem_flutter_app/app_state/app_state.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/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheets/search_employee_bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; @@ -21,6 +22,16 @@ class ChatHomeScreen extends StatefulWidget { class _ChatHomeScreenState extends State { TextEditingController search = TextEditingController(); + late ChatProviderModel data; + + @override + void initState() { + // TODO: implement initState + super.initState(); + data = Provider.of(context, listen: false); + data.registerEvents(); + data.getUserRecentChats(); + } @override void dispose() { @@ -36,110 +47,87 @@ class _ChatHomeScreenState extends State { builder: (BuildContext context, ChatProviderModel m, Widget? child) { return m.isLoading ? ChatHomeShimmer() - : ListView( - shrinkWrap: true, - physics: const AlwaysScrollableScrollPhysics(), + : Column( children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 20, - horizontal: 20, - ), - child: TextField( - controller: m.search, - onChanged: (String val) { - m.filter(val); - }, - decoration: InputDecoration( - border: fieldBorder( - radius: 5, - color: 0xFFE5E5E5, - ), - focusedBorder: fieldBorder( - radius: 5, - color: 0xFFE5E5E5, - ), - enabledBorder: fieldBorder( - radius: 5, - color: 0xFFE5E5E5, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 15, - vertical: 10, - ), - hintText: LocaleKeys.searchfromchat.tr(), - hintStyle: const TextStyle( - color: MyColors.lightTextColor, - fontStyle: FontStyle.italic, - ), - filled: true, - fillColor: const Color( - 0xFFF7F7F7, - ), - suffixIcon: m.search.text.isNotEmpty - ? IconButton( - onPressed: () { - m.clearSelections(); - }, - icon: const Icon( - Icons.clear, - size: 22, - ), - color: MyColors.redA3Color, - ) - : null, - ), + TextField( + controller: m.search, + style: const TextStyle(color: MyColors.darkTextColor, fontWeight: FontWeight.w500, fontSize: 12), + onChanged: (String val) { + m.filter(val); + }, + decoration: InputDecoration( + border: fieldBorder(radius: 5, color: 0xFFE5E5E5), + focusedBorder: fieldBorder(radius: 5, color: 0xFFE5E5E5), + enabledBorder: fieldBorder(radius: 5, color: 0xFFE5E5E5), + contentPadding: const EdgeInsets.all(11), + hintText: LocaleKeys.searchfromchat.tr(), + hintStyle: const TextStyle(color: MyColors.lightTextColor, fontStyle: FontStyle.italic, fontWeight: FontWeight.w500, fontSize: 12), + filled: true, + fillColor: const Color(0xFFF7F7F7), + suffixIconConstraints: const BoxConstraints(), + suffixIcon: m.search.text.isNotEmpty + ? IconButton( + constraints: const BoxConstraints(), + onPressed: () { + m.clearSelections(); + }, + icon: const Icon(Icons.clear, size: 22), + color: MyColors.redA3Color, + ) + : null, ), - ), + ).paddingOnly(top: 20, bottom: 14), if (m.searchedChats != null) ListView.separated( itemCount: m.searchedChats!.length, - padding: const EdgeInsets.only( - bottom: 80, - ), shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + physics: const ClampingScrollPhysics(), itemBuilder: (BuildContext context, int index) { + // todo @aamir, remove list tile, make a custom ui instead return SizedBox( height: 55, - child: ListTile( - leading: Stack( - children: [ - SvgPicture.asset( - "assets/images/user.svg", - height: 48, - width: 48, - ), - Positioned( - right: 5, - bottom: 1, - child: Container( - width: 10, - height: 10, - decoration: BoxDecoration( - color: m.searchedChats![index].userStatus == 1 ? MyColors.green2DColor : Colors.red, - borderRadius: const BorderRadius.all( - Radius.circular(10), + child: Row( + children: [ + Stack( + children: [ + SvgPicture.asset( + "assets/images/user.svg", + height: 48, + width: 48, + ), + Positioned( + right: 5, + bottom: 1, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: m.searchedChats![index].userStatus == 1 ? MyColors.green2DColor : Colors.red, + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), ), ), - ), - ) - ], - ), - title: (m.searchedChats![index].userName!.replaceFirst(".", " ").capitalizeFirstofEach ?? "").toText14(color: MyColors.darkTextColor), - // subtitle: (m.searchedChats![index].isTyping == true ? "Typing ..." : "").toText11(color: MyColors.normalTextColor), - trailing: SizedBox( - width: 60, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.max, - children: [ - if (m.searchedChats![index].unreadMessageCount! > 0) - Flexible( - child: Container( - padding: EdgeInsets.zero, - alignment: Alignment.centerRight, + ) + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (m.searchedChats![index].userName!.replaceFirst(".", " ").capitalizeFirstofEach ?? "").toText14(color: MyColors.darkTextColor).paddingOnly(left: 11, top: 13), + ], + ).expanded, + SizedBox( + width: 60, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + if (m.searchedChats![index].unreadMessageCount! > 0) + Container( + alignment: Alignment.center, width: 18, height: 18, decoration: const BoxDecoration( @@ -153,17 +141,12 @@ class _ChatHomeScreenState extends State { color: MyColors.white, ) .center, - ), - ), - Flexible( - child: IconButton( - alignment: Alignment.centerRight, - padding: EdgeInsets.zero, - icon: Icon( - m.searchedChats![index].isFav != null && m.searchedChats![index].isFav == false ? Icons.star_sharp : Icons.star_sharp, - ), + ).paddingOnly(right: 10).center, + Icon( + m.searchedChats![index].isFav != null && m.searchedChats![index].isFav == false ? Icons.star_sharp : Icons.star_sharp, color: m.searchedChats![index].isFav != null && m.searchedChats![index].isFav == true ? MyColors.yellowColor : MyColors.grey35Color, - onPressed: () { + ).onPress( + () { if (m.searchedChats![index].isFav == null || m.searchedChats![index].isFav == false) { m.favoriteUser( userID: AppState().chatDetails!.response!.id!, @@ -181,40 +164,28 @@ class _ChatHomeScreenState extends State { ); } }, - ), - ) - ], + ).center + ], + ), ), - ), - minVerticalPadding: 0, - onTap: () { - Navigator.pushNamed( - context, - AppRoutes.chatDetailed, - arguments: {"targetUser": m.searchedChats![index], "isNewChat": false}, - ).then((Object? value) { - // m.GetUserChatHistoryNotDeliveredAsync(userId: int.parse(AppState().chatDetails!.response!.id.toString())); - m.clearSelections(); - m.notifyListeners(); - }); - }, + ], ), - ); + ).onPress(() { + Navigator.pushNamed( + context, + AppRoutes.chatDetailed, + arguments: {"targetUser": m.searchedChats![index], "isNewChat": false}, + ).then((Object? value) { + // m.GetUserChatHistoryNotDeliveredAsync(userId: int.parse(AppState().chatDetails!.response!.id.toString())); + m.clearSelections(); + m.notifyListeners(); + }); + }); }, - separatorBuilder: (BuildContext context, int index) => const Padding( - padding: EdgeInsets.only( - right: 10, - left: 70, - ), - child: Divider( - color: Color( - 0xFFE5E5E5, - ), - ), - ), - ), + separatorBuilder: (BuildContext context, int index) => const Divider(color: MyColors.lightGreyE5Color).paddingOnly(left: 59), + ).paddingOnly(bottom: 70).expanded, ], - ); + ).paddingOnly(left: 21, right: 21); }, ), floatingActionButton: FloatingActionButton( @@ -240,6 +211,9 @@ class _ChatHomeScreenState extends State { ), ), onPressed: () async { + // String plainText = 'Muhamad.Alam@cloudsolutions.com.sa'; + // String key = "PeShVmYp"; + // passEncrypt(plainText, "PeShVmYp"); showMyBottomSheet( context, callBackFunc: () {}, @@ -267,4 +241,127 @@ class _ChatHomeScreenState extends State { ), ); } + // + // void passEncrypt(String text, String pass) async { + // var salt = randomUint8List(8); + // var keyndIV = deriveKeyAndIV(pass, salt); + // var key = encrypt.Key(keyndIV.item1); + // var iv = encrypt.IV(keyndIV.item2); + // var encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7")); + // var encrypted = encrypter.encrypt(text, iv: iv); + // Uint8List encryptedBytesWithSalt = Uint8List.fromList(createUint8ListFromString("Salted__") + salt + encrypted.bytes); + // var resulttt = base64.encode(encryptedBytesWithSalt); + // print("Enc : " + resulttt); + // + // decryptAESCryptoJS(resulttt, pass); + // } + // + // Uint8List randomUint8List(int length) { + // assert(length > 0); + // var random = Random(); + // var ret = Uint8List(length); + // for (var i = 0; i < length; i++) { + // ret[i] = random.nextInt(256); + // } + // return ret; + // } + // + // void decryptAESCryptoJS(String encrypted, String passphrase) { + // try { + // Uint8List encryptedBytesWithSalt = base64.decode(encrypted); + // Uint8List encryptedBytes = encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length); + // var salt = encryptedBytesWithSalt.sublist(8, 16); + // var keyndIV = deriveKeyAndIV(passphrase, salt); + // var key = encrypt.Key(keyndIV.item1); + // var iv = encrypt.IV(keyndIV.item2); + // var encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7")); + // var decrypted = encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv); + // print("Dec : " + decrypted); + // // return decrypted; + // } catch (error) { + // throw error; + // } + // } + + void enc(String input) { + var ekey = "PeShVmYp"; + var eIV = "j70IbWYn"; + List eByt = utf8.encode(ekey); + List eIvByt = utf8.encode(eIV); + List iByt = utf8.encode(input); + + + + } + + // ///Accepts encrypted data and decrypt it. Returns plain text + // String decryptWithAES(String key, Encrypted encryptedData) { + // var cipherKey = encrypt.Key.fromUtf8(key); + // var encryptService = Encrypter(AES(cipherKey, mode: AESMode.cbc,padding: null)); + // var initVector = IV.fromUtf8(key.substring(0, 16)); + // return encryptService.decrypt(encryptedData, iv: initVector); + // } + // + // ///Encrypts the given plainText using the key. Returns encrypted data + // Encrypted encryptWithAES(String key, String plainText) { + // var cipherKey = encrypt.Key.fromUtf8(key); + // var encryptService = Encrypter(AES(cipherKey, mode: AESMode.cbc,padding: null)); + // var initVector = IV.fromUtf8("j70IbWYn"); + // Encrypted encryptedData = encryptService.encrypt(plainText, iv: initVector); + // print(encryptedData.base64); + // return encryptedData; + // } + // + // Tuple2 deriveKeyAndIV(String passphrase, Uint8List salt) { + // var password = createUint8ListFromString(passphrase); + // Uint8List concatenatedHashes = Uint8List(0); + // Uint8List currentHash = Uint8List(0); + // bool enoughBytesForKey = false; + // Uint8List preHash = Uint8List(0); + // + // while (!enoughBytesForKey) { + // int preHashLength = currentHash.length + password.length + salt.length; + // if (currentHash.length > 0) + // preHash = Uint8List.fromList(currentHash + password + salt); + // else + // preHash = Uint8List.fromList(password + salt); + // + // currentHash = preHash; + // concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash); + // if (concatenatedHashes.length >= 48) enoughBytesForKey = true; + // } + // + // var keyBtyes = concatenatedHashes.sublist(0, 32); + // var ivBtyes = concatenatedHashes.sublist(32, 48); + // return new Tuple2(keyBtyes, ivBtyes); + // } + // + // Uint8List createUint8ListFromString(String s) { + // var ret = new Uint8List(s.length); + // for (var i = 0; i < s.length; i++) { + // ret[i] = s.codeUnitAt(i); + // } + // return ret; + // } + // + // Uint8List genRandomWithNonZero(int seedLength) { + // var random = Random.secure(); + // const int randomMax = 245; + // Uint8List uint8list = Uint8List(seedLength); + // for (int i = 0; i < seedLength; i++) { + // uint8list[i] = random.nextInt(randomMax) + 1; + // } + // return uint8list; + // } + // + // + // + // void test(String text, String kk) { + // Uint8List key = Uint8List.fromList(utf8.encode(kk)); + // PaddedBlockCipher cipher = exp.PaddedBlockCipherImpl(exp.PKCS7Padding(), exp.ECBBlockCipher(exp.AESEngine())); + // cipher.init(true, PaddedBlockCipherParameters(KeyParameter(key), null)); + // var byte = Uint8List.fromList(utf8.encode(text)); + // var data = cipher.process(byte); + // print(data); + // } } diff --git a/lib/ui/chat/favorite_users_screen.dart b/lib/ui/chat/favorite_users_screen.dart index 7ef0f84..6bc0040 100644 --- a/lib/ui/chat/favorite_users_screen.dart +++ b/lib/ui/chat/favorite_users_screen.dart @@ -1,7 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:mohem_flutter_app/api/chat/chat_provider_model.dart'; +import 'package:mohem_flutter_app/main.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/classes/colors.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; @@ -26,81 +27,80 @@ class ChatFavoriteUsersScreen extends StatelessWidget { return m.favUsersList != null && m.favUsersList.isNotEmpty ? ListView.separated( itemCount: m.favUsersList!.length, - padding: const EdgeInsets.only(top: 20), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int index) { return SizedBox( height: 55, - child: ListTile( - leading: Stack( - children: [ - SvgPicture.asset( - "assets/images/user.svg", - height: 48, - width: 48, - ), - Positioned( - right: 5, - bottom: 1, - child: Container( - width: 10, - height: 10, - decoration: BoxDecoration( - color: m.favUsersList![index].userStatus == 1 ? MyColors.green2DColor : Colors.red, - borderRadius: const BorderRadius.all( - Radius.circular(10), + child: Row( + children: [ + Stack( + children: [ + SvgPicture.asset( + "assets/images/user.svg", + height: 48, + width: 48, + ), + Positioned( + right: 5, + bottom: 1, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: m.favUsersList![index].userStatus == 1 ? MyColors.green2DColor : Colors.red, + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), ), ), - ), - ) - ], - ), - title: (m.favUsersList![index].userName!.replaceFirst(".", " ").capitalizeFirstofEach ?? "").toText14( - color: MyColors.darkTextColor, - ), - trailing: IconButton( - alignment: Alignment.centerRight, - padding: EdgeInsets.zero, - icon: Icon( - m.favUsersList![index].isFav! ? Icons.star : Icons.star_border, + ) + ], ), - color: m.favUsersList![index].isFav! ? MyColors.yellowColor : MyColors.grey35Color, - onPressed: () { - if (m.favUsersList![index].isFav!) - m.unFavoriteUser( - userID: AppState().chatDetails!.response!.id!, - targetUserID: m.favUsersList![index].id!, - ); - }, - ), - minVerticalPadding: 0, - onTap: () { - Navigator.pushNamed( - context, - AppRoutes.chatDetailed, - arguments: {"targetUser": m.favUsersList![index], "isNewChat": false}, - ).then( - (Object? value) { - m.clearSelections(); - }, - ); - }, + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (m.favUsersList![index].userName!.replaceFirst(".", " ").capitalizeFirstofEach ?? "").toText14(color: MyColors.darkTextColor).paddingOnly(left: 11, top: 13), + ], + ).expanded, + SizedBox( + width: 60, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + m.favUsersList![index].isFav! ? Icons.star : Icons.star_border, + color: m.favUsersList![index].isFav! ? MyColors.yellowColor : MyColors.grey35Color, + ).onPress(() { + if (m.favUsersList![index].isFav!) { + m.unFavoriteUser( + userID: AppState().chatDetails!.response!.id!, + targetUserID: m.favUsersList![index].id!, + ); + } + }).center, + ], + ), + ), + ], ), - ); + ).onPress(() { + Navigator.pushNamed( + context, + AppRoutes.chatDetailed, + arguments: {"targetUser": m.favUsersList![index], "isNewChat": false}, + ).then( + (Object? value) { + m.clearSelections(); + }, + ); + }); }, - separatorBuilder: (BuildContext context, int index) => const Padding( - padding: EdgeInsets.only( - right: 10, - left: 70, - ), - child: Divider( - color: Color( - 0xFFE5E5E5, - ), - ), - ), - ) + separatorBuilder: (BuildContext context, int index) => const Divider(color: MyColors.lightGreyE5Color).paddingOnly(left: 70), + ).paddingAll(21) : Column( children: [ Utils.getNoDataWidget(context).expanded, diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 130c75b..7cf28cf 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; @@ -13,12 +14,12 @@ import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; -import 'package:mohem_flutter_app/main.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/landing/widget/app_drawer.dart'; import 'package:mohem_flutter_app/ui/landing/widget/menus_widget.dart'; import 'package:mohem_flutter_app/ui/landing/widget/services_widget.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart'; @@ -26,6 +27,9 @@ import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart' import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:signalr_netcore/signalr_client.dart'; + +late HubConnection hubConnection; class DashboardScreen extends StatefulWidget { DashboardScreen({Key? key}) : super(key: key); @@ -38,6 +42,7 @@ class DashboardScreen extends StatefulWidget { class _DashboardScreenState extends State { late DashboardProviderModel data; + late MarathonProvider marathonProvider; final GlobalKey _scaffoldState = GlobalKey(); final RefreshController _refreshController = RefreshController(initialRefresh: false); @@ -49,13 +54,27 @@ class _DashboardScreenState extends State { super.initState(); scheduleMicrotask(() { data = Provider.of(context, listen: false); + marathonProvider = Provider.of(context, listen: false); + _bHubCon(); _onRefresh(); }); } + void buildHubConnection() async { + hubConnection = await data.getHubConnection(); + await hubConnection.start(); + } + @override void dispose() { super.dispose(); + hubConnection.stop(); + } + + void _bHubCon() { + data.getUserAutoLoginToken().whenComplete(() { + buildHubConnection(); + }); } void _onRefresh() async { @@ -72,6 +91,7 @@ class _DashboardScreenState extends State { data.fetchLeaveTicketBalance(context, DateTime.now()); data.fetchMenuEntries(); data.getCategoryOffersListAPI(context); + marathonProvider.getMarathonDetailsFromApi(); data.fetchChatCounts(); _refreshController.refreshCompleted(); } @@ -84,29 +104,29 @@ class _DashboardScreenState extends State { // actions: [ // IconButton( // onPressed: () { - // data.getITGNotification().then((value) { - // print("--------------------detail_1-----------------"); - // if (value!.result!.data != null) { - // print(value.result!.data!.notificationMasterId); - // print(value.result!.data!.notificationType); - // if (value.result!.data!.notificationType == "Survey") { - // Navigator.pushNamed(context, AppRoutes.survey, arguments: value.result!.data); + // data.getITGNotification().then((val) { + // if (val!.result!.data != null) { + // if (val.result!.data!.notificationType == "Survey") { + // Navigator.pushNamed(context, AppRoutes.survey, arguments: val.result!.data); // } else { - // DashboardApiClient().getAdvertisementDetail(value.result!.data!.notificationMasterId ?? "").then( + // DashboardApiClient().getAdvertisementDetail(val.result!.data!.notificationMasterId ?? "").then( // (value) { // if (value!.mohemmItgResponseItem!.statusCode == 200) { // if (value.mohemmItgResponseItem!.result!.data != null) { - // String? image64 = value.mohemmItgResponseItem!.result!.data!.advertisement!.viewAttachFileColl!.first.base64String; - // print(image64); - // var sp = image64!.split("base64,"); - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => MovieTheaterBody( - // encodedBytes: sp[1], - // ), - // ), - // ); + // Navigator.pushNamed(context, AppRoutes.advertisement, arguments: { + // "masterId": val.result!.data!.notificationMasterId, + // "advertisement": value.mohemmItgResponseItem!.result!.data!.advertisement, + // }); + // + // // Navigator.push( + // // context, + // // MaterialPageRoute( + // // builder: (BuildContext context) => ITGAdsScreen( + // // addMasterId: val.result!.data!.notificationMasterId!, + // // advertisement: value.mohemmItgResponseItem!.result!.data!.advertisement!, + // // ), + // // ), + // // ); // } // } // }, @@ -217,11 +237,14 @@ class _DashboardScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ 9.height, - CountdownTimer( - endTime: model.endTime, - onEnd: null, - endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), - textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + Directionality( + textDirection: ui.TextDirection.ltr, + child: CountdownTimer( + endTime: model.endTime, + onEnd: null, + endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), + textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + ), ), LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), 9.height, @@ -298,7 +321,7 @@ class _DashboardScreenState extends State { ), ], ).paddingOnly(left: 21, right: 21, top: 7), - const MarathonBanner().paddingAll(20), + context.watch().isLoading ? MarathonBannerShimmer().paddingAll(20) : MarathonBanner().paddingAll(20), ServicesWidget(), // 8.height, Container( @@ -344,7 +367,7 @@ class _DashboardScreenState extends State { ], ).paddingOnly(left: 21, right: 21), Consumer( - builder: (context, model, child) { + builder: (BuildContext context, DashboardProviderModel model, Widget? child) { return SizedBox( height: 103 + 33, child: ListView.separated( diff --git a/lib/ui/landing/itg/its_add_screen_video_image.dart b/lib/ui/landing/itg/its_add_screen_video_image.dart new file mode 100644 index 0000000..a71a71b --- /dev/null +++ b/lib/ui/landing/itg/its_add_screen_video_image.dart @@ -0,0 +1,135 @@ +import 'dart:convert'; +import 'dart:io' as Io; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:just_audio/just_audio.dart'; +import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/main.dart'; +import 'package:mohem_flutter_app/models/itg/advertisement.dart' as ads; +import 'package:path_provider/path_provider.dart'; +import 'package:video_player/video_player.dart'; + +class ITGAdsScreen extends StatefulWidget { + const ITGAdsScreen({Key? key}) : super(key: key); + + @override + _ITGAdsScreenState createState() => _ITGAdsScreenState(); +} + +class _ITGAdsScreenState extends State { + late Future _futureController; + late VideoPlayerController _controller; + bool skip = false; + bool isVideo = false; + bool isImage = false; + String ext = ''; + late File imageFile; + ads.Advertisement? advertisementData; + dynamic data; + String? masterID; + + void checkFileType() async { + String? rFile = advertisementData!.viewAttachFileColl!.first.base64String; + String? rFileExt = advertisementData!.viewAttachFileColl!.first.fileName; + ext = "." + rFileExt!.split(".").last.toLowerCase(); + if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif") { + await processImage(rFile!); + isImage = true; + } else { + isVideo = true; + _futureController = createVideoPlayer(rFile!); + } + setState(() {}); + initTimer(); + } + + Future processImage(String encodedBytes) async { + try { + Uint8List decodedBytes = base64Decode(encodedBytes.split("base64,").last); + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); // 1 + imageFile = Io.File("${appDocumentsDirectory.path}/addImage$ext"); + imageFile.writeAsBytesSync(decodedBytes); + } catch (e) { + logger.d(e); + } + } + + Future createVideoPlayer(String encodedBytes) async { + try { + Uint8List decodedBytes = base64Decode(encodedBytes.split("base64,").last); + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); // 1 + File file = Io.File("${appDocumentsDirectory.path}/myAdsVideo.mp4"); + file.writeAsBytesSync(decodedBytes); + VideoPlayerController controller = VideoPlayerController.file(file); + await controller.initialize(); + await controller.play(); + await controller.setVolume(1.0); + await controller.setLooping(false); + return controller; + } catch (e) { + return new VideoPlayerController.asset("dataSource"); + } + } + + void initTimer() { + Future.delayed(const Duration(seconds: 5), () { + skip = true; + setState(() {}); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + data = ModalRoute.of(context)!.settings.arguments; + if (advertisementData == null) advertisementData = data["advertisement"] as ads.Advertisement; + if (masterID == null) masterID = data["masterId"]; + if (advertisementData != null) { + checkFileType(); + } + double height = MediaQuery.of(context).size.height * .25; + return Scaffold( + body: Column( + children: [ + if (isVideo) + SizedBox( + height: MediaQuery.of(context).size.height * .3, + child: FutureBuilder( + future: _futureController, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) { + _controller = snapshot.data as VideoPlayerController; + return AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + ), + ), + if (isImage) Image.file(imageFile), + if (skip) + ElevatedButton( + onPressed: () async { + // DashboardApiClient().setAdvertisementViewed(widget.addMasterId, widget.advertisement!.advertisementId!).then((value) { + // logger.d(value); + // }); + }, + child: const Text("Go To Dashboard"), + ) + ], + ), + ); + } +} diff --git a/lib/ui/landing/itg/video_page.dart b/lib/ui/landing/itg/video_page.dart deleted file mode 100644 index 657d714..0000000 --- a/lib/ui/landing/itg/video_page.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'dart:convert'; -import 'dart:io' as Io; - -import 'package:flutter/material.dart'; -import 'package:video_player/video_player.dart'; - -class MovieTheaterBody extends StatefulWidget { - final String encodedBytes; - - const MovieTheaterBody({required this.encodedBytes}); - - @override - _MovieTheaterBodyState createState() => _MovieTheaterBodyState(); -} - -class _MovieTheaterBodyState extends State { - late Future _futureController; - late VideoPlayerController _controller; - - Future createVideoPlayer() async { - try { - var decodedBytes = base64Decode(widget.encodedBytes); - - var file = Io.File("decodedBezkoder.mp4"); - file.writeAsBytesSync(decodedBytes); - - VideoPlayerController controller = VideoPlayerController.file(file); - await controller.initialize(); - await controller.setLooping(true); - return controller; - } catch (e) { - print("object0000000"); - print(e); - return new VideoPlayerController.asset("dataSource"); - } - } - - @override - void initState() { - _futureController = createVideoPlayer(); - super.initState(); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Expanded( - child: FutureBuilder( - future: _futureController, - builder: (context, snapshot) { - //UST: 05/2021 - MovieTheaterBody - id:11 - 2pts - Criação - if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) { - _controller = snapshot.data as VideoPlayerController; - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - ), - const SizedBox( - height: 50, - ), - FloatingActionButton( - onPressed: () { - setState(() { - if (_controller.value.isPlaying) { - _controller.pause(); - } else { - // If the video is paused, play it. - _controller.play(); - } - }); - }, - backgroundColor: Colors.green[700], - child: Icon( - _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, - ), - ) - ], - ); - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ), - ), - ); - } -} diff --git a/lib/ui/landing/today_attendance_screen2.dart b/lib/ui/landing/today_attendance_screen2.dart index ada652c..f0228eb 100644 --- a/lib/ui/landing/today_attendance_screen2.dart +++ b/lib/ui/landing/today_attendance_screen2.dart @@ -99,10 +99,13 @@ class _TodayAttendanceScreenState extends State { child: CountdownTimer( endTime: model.endTime, widgetBuilder: (context, v) { - return AutoSizeText( - getValue(v?.hours) + " : " + getValue(v?.min) + " : " + getValue(v?.sec), - maxLines: 1, - style: const TextStyle(color: Colors.white, fontSize: 42, letterSpacing: -1.92, fontWeight: FontWeight.bold, height: 1), + return Directionality( + textDirection: TextDirection.ltr, + child: AutoSizeText( + getValue(v?.hours) + " : " + getValue(v?.min) + " : " + getValue(v?.sec), + maxLines: 1, + style: const TextStyle(color: Colors.white, fontSize: 42, letterSpacing: -1.92, fontWeight: FontWeight.bold, height: 1), + ), ); }, onEnd: null, @@ -116,7 +119,7 @@ class _TodayAttendanceScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - LocaleKeys.shiftTime.tr().tr().toTextAuto(color: MyColors.greyACColor, fontSize: 18, maxLine: 1).paddingOnly(left: 21,right: 21), + LocaleKeys.shiftTime.tr().tr().toTextAuto(color: MyColors.greyACColor, fontSize: 18, maxLine: 1).paddingOnly(left: 21, right: 21), (model.attendanceTracking!.pShtName ?? "00:00:00").toString().toTextAuto(color: Colors.white, isBold: true, fontSize: 26, maxLine: 1), ], ), diff --git a/lib/ui/leave_balance/add_leave_balance_screen.dart b/lib/ui/leave_balance/add_leave_balance_screen.dart index b2b2843..c879f9c 100644 --- a/lib/ui/leave_balance/add_leave_balance_screen.dart +++ b/lib/ui/leave_balance/add_leave_balance_screen.dart @@ -139,8 +139,10 @@ class _AddLeaveBalanceScreenState extends State { Utils.hideLoading(context); - await Navigator.pushNamed(context, AppRoutes.requestSubmitScreen, arguments: RequestSubmitScreenParams(LocaleKeys.submit.tr(), submit.pTRANSACTIONID!, "", "add_leave_balance")); - Utils.showLoading(context); + var res = await Navigator.pushNamed(context, AppRoutes.requestSubmitScreen, arguments: RequestSubmitScreenParams(LocaleKeys.submit.tr(), submit.pTRANSACTIONID!, "", "add_leave_balance")); + if (res != null && res == true) { + Utils.showLoading(context); + } await LeaveBalanceApiClient().cancelHrTransaction(submit.pTRANSACTIONID!); Utils.hideLoading(context); } catch (ex) { diff --git a/lib/ui/login/login_screen.dart b/lib/ui/login/login_screen.dart index 20fa8bc..e6b4838 100644 --- a/lib/ui/login/login_screen.dart +++ b/lib/ui/login/login_screen.dart @@ -128,6 +128,7 @@ class _LoginScreenState extends State { Navigator.pushNamed(context, AppRoutes.verifyLogin, arguments: "$firebaseToken"); } + Utils.saveStringFromPrefs(SharedPrefsConsts.password, password.text); } catch (ex) { Utils.hideLoading(context); Utils.handleException(ex, context, (msg) { @@ -142,8 +143,8 @@ class _LoginScreenState extends State { isAppOpenBySystem = (ModalRoute.of(context)!.settings.arguments ?? true) as bool; if (!kReleaseMode) { // username.text = "15444"; // Maha User - // username.text = "15153"; // Tamer User - // password.text = "Abcd@12345"; + username.text = "15153"; // Tamer User + password.text = "Abcd@1234"; // username.text = "206535"; // Hashim User // password.text = "Namira786"; diff --git a/lib/ui/marathon/marathon_intro_screen.dart b/lib/ui/marathon/marathon_intro_screen.dart index f836229..d85a82f 100644 --- a/lib/ui/marathon/marathon_intro_screen.dart +++ b/lib/ui/marathon/marathon_intro_screen.dart @@ -1,9 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/date_uitl.dart'; import 'package:mohem_flutter_app/classes/decorations_helper.dart'; import 'package:mohem_flutter_app/classes/lottie_consts.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; @@ -15,8 +18,6 @@ import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:provider/provider.dart'; -final int dummyEndTime = DateTime.now().millisecondsSinceEpoch + 1000 * 30; - class MarathonIntroScreen extends StatelessWidget { const MarathonIntroScreen({Key? key}) : super(key: key); @@ -25,26 +26,18 @@ class MarathonIntroScreen extends StatelessWidget { MarathonProvider provider = context.watch(); return Scaffold( appBar: AppBarWidget(context, title: LocaleKeys.brainMarathon.tr()), - body: Stack( + body: Column( children: [ - SingleChildScrollView( - child: Column( - children: [ - MarathonDetailsCard(provider: provider).paddingAll(15), - MarathonTimerCard( - provider: provider, - timeToMarathon: dummyEndTime, - ).paddingOnly(left: 15, right: 15, bottom: 15), - const SizedBox( - height: 100, - ), - ], - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: MarathonFooter(provider: provider), - ), + ListView( + padding: const EdgeInsets.all(21), + children: [ + MarathonDetailsCard(provider: provider), + 10.height, + MarathonTimerCard(provider: provider, timeToMarathon: DateTime.parse(provider.marathonDetailModel.startTime!).millisecondsSinceEpoch,), + ], + ).expanded, + 1.divider, + MarathonFooter(provider: provider), ], ), ); @@ -61,7 +54,7 @@ class MarathonDetailsCard extends StatelessWidget { return Container( width: double.infinity, decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 14), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -70,37 +63,44 @@ class MarathonDetailsCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.contestTopicAbout.tr().toText16(color: MyColors.grey77Color), - "Saudi Arabia".toText20(color: MyColors.textMixColor, isBold: true), + "${AppState().isArabic(context) ? provider.marathonDetailModel.titleAr : provider.marathonDetailModel.titleEn}".toText20(color: MyColors.textMixColor, isBold: true), Row( children: [ Flexible( - child: "Nam suscipit turpis in pharetra euismsdef. Duis rutrum at nulla id aliquam".toText14(color: MyColors.grey77Color), + child: "${AppState().isArabic(context) ? provider.marathonDetailModel.descAr : provider.marathonDetailModel.descEn}".toText14(color: MyColors.grey77Color), ) ], ), - if (provider.itsMarathonTime) ...[ + 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.prize.tr().toText16(color: MyColors.grey77Color, isBold: true), - " LED 55\" Android TV".toText16(color: MyColors.greenColor, isBold: true), - ], - ), - Row( - children: [ - LocaleKeys.sponsoredBy.tr().toText16(color: MyColors.grey77Color), - " Extra".toText16(color: MyColors.darkTextColor, isBold: true), + "${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.asset( - "assets/images/logos/main_mohemm_logo.png", + Image.network( + provider.marathonDetailModel.sponsors!.first.image!, height: 40, - fit: BoxFit.fill, width: 150, + fit: BoxFit.fill, + errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) { + return const Center(); + }, ) ], ), @@ -128,30 +128,23 @@ class MarathonTimerCard extends StatelessWidget { return Container( width: double.infinity, decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 14), child: Column( children: [ Row( children: [ - LocaleKeys.gameDate.tr().toText16(color: MyColors.grey77Color), - " 10 Oct, 2022".toText16(color: MyColors.darkTextColor, isBold: true), + "${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), - " 3:00pm".toText16(color: MyColors.darkTextColor, isBold: true), + "${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, - ), + Lottie.asset(MyLottieConsts.hourGlassLottie, height: 200), + BuildCountdownTimer(timeToMarathon: timeToMarathon, provider: provider, screenFlag: 1), ], ), ); @@ -172,38 +165,19 @@ class MarathonFooter extends StatelessWidget { children: [ TextSpan( text: LocaleKeys.note.tr(), - style: const TextStyle( - color: MyColors.darkTextColor, - fontSize: 17, - letterSpacing: -0.64, - fontWeight: FontWeight.bold, - ), + 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, - ), + 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, - ), + 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, - ), + style: const TextStyle(color: MyColors.grey77Color, fontSize: 17, letterSpacing: -0.64, fontWeight: FontWeight.w500), ) ], ), @@ -212,10 +186,21 @@ class MarathonFooter extends StatelessWidget { @override Widget build(BuildContext context) { - return provider.itsMarathonTime + return !provider.itsMarathonTime ? DefaultButton( LocaleKeys.joinMarathon.tr(), - () => Navigator.pushNamed(context, AppRoutes.marathonScreen), + () async { + Utils.showLoading(context); + try { + provider.resetValues(); + await provider.connectSignalrAndJoinMarathon(context); + } catch (e, s) { + Utils.confirmDialog(context, e.toString()); + print(s); + } + Utils.hideLoading(context); + Navigator.pushNamed(context, AppRoutes.marathonScreen); + }, ).insideContainer : Container( color: Colors.white, @@ -225,7 +210,9 @@ class MarathonFooter extends StatelessWidget { buildNoteForDemo(), DefaultButton( LocaleKeys.joinDemoMarathon.tr(), - () {}, + () { + provider.connectSignalrAndJoinMarathon(context); + }, color: MyColors.yellowColorII, ).insideContainer, ], diff --git a/lib/ui/marathon/marathon_provider.dart b/lib/ui/marathon/marathon_provider.dart index 5a03b74..b629b36 100644 --- a/lib/ui/marathon/marathon_provider.dart +++ b/lib/ui/marathon/marathon_provider.dart @@ -3,12 +3,64 @@ import 'dart:async'; import 'package:appinio_swiper/appinio_swiper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/api/marathon/marathon_api_client.dart'; +import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; class MarathonProvider extends ChangeNotifier { final AppinioSwiperController swiperController = AppinioSwiperController(); + MarathonDetailModel marathonDetailModel = MarathonDetailModel(); + List cardContentList = []; + QuestionModel currentQuestion = QuestionModel(); + + QuestionCardStatus questionCardStatus = QuestionCardStatus.question; + + int? selectedOptionIndex; + int currentQuestionTime = 0; + + void onNewQuestionReceived(QuestionModel newQuestion) { + if (currentQuestionNumber > 0) { + swipeCardLeft(); + } + selectedOptionIndex = null; + currentQuestionNumber++; + currentQuestion = newQuestion; + cardContentList.add(const CardContent()); + currentQuestionTime = newQuestion.questionTime!; + questionCardStatus = QuestionCardStatus.question; + notifyListeners(); + } + + void addItemToList(CardContent value) { + cardContentList.add(value); + notifyListeners(); + } + + void updateCurrentQuestionOptionStatus(QuestionsOptionStatus status, int index) { + for (int i = 0; i < currentQuestion.questionOptions!.length; i++) { + currentQuestion.questionOptions![i].optionStatus = QuestionsOptionStatus.unSelected; + } + currentQuestion.questionOptions![index].optionStatus = status; + selectedOptionIndex = index; + notifyListeners(); + } + + void updateQuestionCardStatus(QuestionCardStatus status) { + questionCardStatus = status; + notifyListeners(); + } + + bool _isLoading = false; + + bool get isLoading => _isLoading; + + set isLoading(bool value) { + _isLoading = value; + notifyListeners(); + } + bool _itsMarathonTime = false; bool get itsMarathonTime => _itsMarathonTime; @@ -27,14 +79,7 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } - void swipeCardLeft() { - currentQuestionNumber = currentQuestionNumber + 1; - swiperController.swipeLeft(); - notifyListeners(); - } - - int _currentQuestionNumber = 1; - final int totalQuestions = 10; + int _currentQuestionNumber = 0; int get currentQuestionNumber => _currentQuestionNumber; @@ -43,44 +88,73 @@ class MarathonProvider extends ChangeNotifier { notifyListeners(); } - void resetAll() { - isSelectedOptions[0] = false; - isSelectedOptions[1] = false; - isSelectedOptions[2] = false; - isSelectedOptions[3] = false; + int _totalMarathoners = 23; + + int get totalMarathoners => _totalMarathoners; + + set totalMarathoners(int value) { + _totalMarathoners = value; + notifyListeners(); + } + + void swipeCardLeft() { + swiperController.swipeLeft(); + notifyListeners(); + } + + void getCorrectAnswerAndUpdateAnswerColor() { + if (selectedOptionIndex != null) { + if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { + updateCurrentQuestionOptionStatus(QuestionsOptionStatus.correct, selectedOptionIndex!); + } else { + updateCurrentQuestionOptionStatus(QuestionsOptionStatus.wrong, selectedOptionIndex!); + } + } + } + + void updateCardStatusToAnswer() { + if (currentQuestionNumber == 0) { + return; + } + + if (selectedOptionIndex != null) { + if (currentQuestion.questionOptions![selectedOptionIndex!].isCorrectOption!) { + updateQuestionCardStatus(QuestionCardStatus.correctAnswer); + } else { + updateQuestionCardStatus(QuestionCardStatus.wrongAnswer); + } + } else { + updateQuestionCardStatus(QuestionCardStatus.skippedAnswer); + } } Timer timerU = Timer.periodic(const Duration(seconds: 1), (Timer timer) {}); - int start = 8; void startTimer(BuildContext context) { - start = 8; const Duration oneSec = Duration(seconds: 1); timerU = Timer.periodic( oneSec, (Timer timer) async { - if (start == 0) { - if (currentQuestionNumber == 9) { - timer.cancel(); - cancelTimer(); - isMarathonCompleted = true; - await Future.delayed(const Duration(seconds: 3)).whenComplete( - () => Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ), - ); - - resetValues(); - - return; - } - resetAll(); - timer.cancel(); - cancelTimer(); - swipeCardLeft(); + if (currentQuestionTime == 2) { + getCorrectAnswerAndUpdateAnswerColor(); + } + if (currentQuestionTime == 0) { + updateCardStatusToAnswer(); + // if (currentQuestionNumber == 9) { + // timer.cancel(); + // cancelTimer(); + // isMarathonCompleted = true; + // await Future.delayed(const Duration(seconds: 2)).whenComplete( + // () => Navigator.pushReplacementNamed(context, AppRoutes.marathonWinnerSelection), + // ); + // + // resetValues(); + // + // return; + // } + // timer.cancel(); } else { - start--; + currentQuestionTime--; } notifyListeners(); }, @@ -88,9 +162,12 @@ class MarathonProvider extends ChangeNotifier { } void resetValues() { + _currentQuestionNumber = 0; + cardContentList.clear(); timerU.cancel(); _isMarathonCompleted = false; - _currentQuestionNumber = 1; + currentQuestionTime = 0; + currentQuestion = QuestionModel(); notifyListeners(); } @@ -98,4 +175,18 @@ class MarathonProvider extends ChangeNotifier { timerU.cancel(); notifyListeners(); } + + Future getMarathonDetailsFromApi() async { + isLoading = true; + notifyListeners(); + await MarathonApiClient().getMarathonToken().whenComplete(() async { + marathonDetailModel = await MarathonApiClient().getMarathonDetails(); + isLoading = false; + notifyListeners(); + }); + } + + Future connectSignalrAndJoinMarathon(BuildContext context) async { + await MarathonApiClient().buildHubConnection(context); + } } diff --git a/lib/ui/marathon/marathon_screen.dart b/lib/ui/marathon/marathon_screen.dart index a02733f..79d61fe 100644 --- a/lib/ui/marathon/marathon_screen.dart +++ b/lib/ui/marathon/marathon_screen.dart @@ -6,7 +6,6 @@ import 'package:lottie/lottie.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/decorations_helper.dart'; import 'package:mohem_flutter_app/classes/lottie_consts.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; @@ -14,10 +13,9 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/custom_status_widget.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; +import 'package:mohem_flutter_app/ui/marathon/widgets/question_card_builder.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:provider/provider.dart'; -import 'package:sizer/sizer.dart'; -import 'package:steps_indicator/steps_indicator.dart'; class MarathonScreen extends StatelessWidget { const MarathonScreen({Key? key}) : super(key: key); @@ -25,48 +23,52 @@ class MarathonScreen extends StatelessWidget { @override Widget build(BuildContext context) { MarathonProvider provider = context.watch(); - return Scaffold( - appBar: AppBarWidget(context, title: LocaleKeys.brainMarathon.tr()), - body: SingleChildScrollView( - child: Column( - children: [ - 20.height, - MarathonProgressContainer(provider: provider).paddingOnly(left: 21, right: 21), - if (provider.isMarathonCompleted) - InkWell( - onTap: () { - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - }, - child: CustomStatusWidget( - asset: Lottie.asset( - MyLottieConsts.allQuestions, - height: 200, - ), - title: Text( - LocaleKeys.congrats.tr(), - style: const TextStyle( - height: 23 / 24, - color: MyColors.greenColor, - fontSize: 27, - letterSpacing: -1, - fontWeight: FontWeight.w600, - ), - ), - subTitle: Text( - LocaleKeys.allQuestionsCorrect.tr(), - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: -1.08), - ), - ).paddingOnly(top: 12, left: 21, right: 21), - ) - else - QuestionCard(provider: provider).paddingOnly(top: 12, left: 21, right: 21), - ], + return WillPopScope( + child: Scaffold( + appBar: AppBarWidget(context, title: LocaleKeys.brainMarathon.tr()), + body: SingleChildScrollView( + child: Column( + children: [ + 20.height, + MarathonProgressContainer(provider: provider).paddingOnly(left: 21, right: 21), + QuestionCardBuilder( + onQuestion: (BuildContext context) => QuestionCard(provider: provider), + onCompleted: (BuildContext context) => CustomStatusWidget( + asset: Lottie.asset(MyLottieConsts.allQuestions, height: 200), + title: LocaleKeys.congrats.tr().toText22(color: MyColors.greenColor), + subTitle: LocaleKeys.allQuestionsCorrect.toText18(color: MyColors.darkTextColor), + ), + onCorrectAnswer: (BuildContext context) => CustomStatusWidget( + asset: Lottie.asset(MyLottieConsts.allQuestions, height: 200), + title: LocaleKeys.congrats.tr().toText22(color: MyColors.greenColor), + subTitle: LocaleKeys.yourAnswerCorrect.toText18(color: MyColors.darkTextColor), + ), + onWinner: (BuildContext context) => QuestionCard(provider: provider), + onWrongAnswer: (BuildContext context) => CustomStatusWidget( + asset: Image.asset(MyLottieConsts.wrongAnswerGif, height: 200), + title: const Text(""), + subTitle: LocaleKeys.wrongAnswer.tr().toText18(color: MyColors.darkTextColor), + ), + onSkippedAnswer: (BuildContext context) => CustomStatusWidget( + asset: Image.asset(MyLottieConsts.wrongAnswerGif, height: 200), + title: const Text(""), + subTitle: LocaleKeys.youMissedTheQuestion.tr().toText18(color: MyColors.darkTextColor), + ), + questionCardStatus: provider.questionCardStatus, + onFindingWinner: (BuildContext context) => CustomStatusWidget( + asset: Lottie.asset(MyLottieConsts.winnerLottie, height: 168), + title: LocaleKeys.fingersCrossed.tr().toText22(color: MyColors.greenColor), + subTitle: LocaleKeys.winnerSelectedRandomly.tr().toText18(color: MyColors.darkTextColor), + ), + ).paddingOnly(top: 12, left: 21, right: 21), + ], + ), ), ), + onWillPop: () { + provider.resetValues(); + return Future.value(true); + }, ); } } @@ -91,7 +93,6 @@ class _MarathonProgressContainerState extends State { @override void dispose() { - widget.provider.cancelTimer(); super.dispose(); } @@ -100,7 +101,7 @@ class _MarathonProgressContainerState extends State { return Container( width: double.infinity, decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.all(21), + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 13), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -108,47 +109,90 @@ class _MarathonProgressContainerState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( - decoration: BoxDecoration( - color: MyColors.greenColor, - borderRadius: BorderRadius.circular(5), - ), + decoration: BoxDecoration(color: MyColors.greenColor, borderRadius: BorderRadius.circular(5)), padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8), - child: "${widget.provider.currentQuestionNumber.toString()} / ${widget.provider.totalQuestions.toString()} ${LocaleKeys.question.tr()}".toText12(color: MyColors.white), + child: "${widget.provider.currentQuestionNumber.toString()} / ${widget.provider.marathonDetailModel.totalQuestions.toString()} ${LocaleKeys.question.tr()}" + .toText12(color: MyColors.white), ), - "23 ${LocaleKeys.marathoners.tr()}".toText14(), - "00:${widget.provider.start < 10 ? "0${widget.provider.start}" : widget.provider.start}".toText18(), + "${widget.provider.totalMarathoners} ${LocaleKeys.marathoners.tr()}".toText14(), + "00:${widget.provider.currentQuestionTime < 10 ? "0${widget.provider.currentQuestionTime}" : widget.provider.currentQuestionTime}" + .toText18(color: widget.provider.currentQuestionTime < 5 ? MyColors.redColor : MyColors.black), ], ), - 15.height, - StepsIndicator( - lineLength: SizerUtil.deviceType == DeviceType.tablet ? MediaQuery.of(context).size.width * 0.077 : MediaQuery.of(context).size.width * 0.054, - nbSteps: 10, - selectedStep: widget.provider.currentQuestionNumber, - doneLineColor: MyColors.greenColor, - doneStepColor: MyColors.greenColor, - doneLineThickness: 6, - undoneLineThickness: 6, - selectedStepSize: 10, - unselectedStepSize: 10, - doneStepSize: 10, - selectedStepBorderSize: 0, - unselectedStepBorderSize: 0, - selectedStepColorIn: MyColors.greenColor, - selectedStepColorOut: MyColors.greenColor, - unselectedStepColorIn: MyColors.lightGreyDeColor, - unselectedStepColorOut: MyColors.lightGreyDeColor, - undoneLineColor: MyColors.lightGreyDeColor, - enableLineAnimation: false, - enableStepAnimation: false, - ), 12.height, + stepper(widget.provider.currentQuestionNumber), + 8.height, Row( children: [ - "${widget.provider.currentQuestionNumber * 10}% ${LocaleKeys.completed.tr()}".toText14(isBold: true), + "${((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_winner_selection.dart b/lib/ui/marathon/marathon_winner_selection.dart index 8462ab4..1f56801 100644 --- a/lib/ui/marathon/marathon_winner_selection.dart +++ b/lib/ui/marathon/marathon_winner_selection.dart @@ -29,7 +29,7 @@ class MarathonWinnerSelection extends StatelessWidget { children: [ 20.height, QualifiersContainer(provider: provider).paddingOnly(left: 21, right: 21), - 20.height, + 12.height, InkWell( onTap: () { Navigator.pushNamed(context, AppRoutes.marathonWinnerScreen); @@ -52,8 +52,8 @@ class MarathonWinnerSelection extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Muhammad Shrouff".toText18(isBold: true, color: MyColors.white), - "837436".toText18(isBold: true, color: MyColors.white), + "Muhammad Shrouff".toText17(isBold: true, color: MyColors.white), + "837436".toText17(isBold: true, color: MyColors.white), ], ), ), @@ -67,10 +67,10 @@ class MarathonWinnerSelection extends StatelessWidget { title: Text( LocaleKeys.fingersCrossed.tr(), style: const TextStyle( - height: 23 / 24, + height: 27 / 27, color: MyColors.greenColor, fontSize: 27, - letterSpacing: -1, + letterSpacing: -1.08, fontWeight: FontWeight.w600, ), ), @@ -78,9 +78,9 @@ class MarathonWinnerSelection extends StatelessWidget { LocaleKeys.winnerSelectedRandomly.tr(), textAlign: TextAlign.center, style: const TextStyle( - color: MyColors.grey77Color, - fontSize: 16, - letterSpacing: -0.64, + color: MyColors.darkTextColor, + fontSize: 18, + letterSpacing: -0.72, fontWeight: FontWeight.w600, ), )).paddingOnly(left: 21, right: 21, top: 20, bottom: 20), @@ -108,14 +108,14 @@ class _QualifiersContainerState extends State { @override void initState() { scheduleMicrotask(() { - widget.provider.startTimer(context); + // widget.provider.startTimer(context); }); super.initState(); } @override void dispose() { - widget.provider.cancelTimer(); + // widget.provider.cancelTimer(); super.dispose(); } @@ -124,22 +124,22 @@ class _QualifiersContainerState extends State { return Container( width: double.infinity, decoration: MyDecorations.shadowDecoration, - padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), + 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().toText18(isBold: true, color: MyColors.grey3AColor), - "00:${widget.provider.start < 10 ? "0${widget.provider.start}" : widget.provider.start}".toText18(isBold: true, color: MyColors.redColor), + 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 ".toText32(color: MyColors.greenColor), - LocaleKeys.qualifiers.tr().toText20(color: MyColors.greenColor), + "18".toText30(color: MyColors.greenColor, isBold: true),2.width, + LocaleKeys.qualifiers.tr().toText16(color: MyColors.greenColor), ], ), ], diff --git a/lib/ui/marathon/widgets/countdown_timer.dart b/lib/ui/marathon/widgets/countdown_timer.dart index 6ab8dba..cfa3b1b 100644 --- a/lib/ui/marathon/widgets/countdown_timer.dart +++ b/lib/ui/marathon/widgets/countdown_timer.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:auto_size_text/auto_size_text.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -29,7 +30,7 @@ class BuildCountdownTimer extends StatelessWidget { ); final TextStyle styleDigitHome = const TextStyle( - height: 23 / 27, + height: 22 / 27, color: MyColors.white, fontStyle: FontStyle.italic, letterSpacing: -1.44, @@ -53,79 +54,83 @@ class BuildCountdownTimer extends StatelessWidget { ); Widget buildEmptyWidget() { - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.days.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.hours.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.minutes.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.seconds.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - ], + return Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + // todo @faiz: Make a separate method and pass string , so we can minimize code replication + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.days.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.hours.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.minutes.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.seconds.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + ], + ), ); } @@ -148,107 +153,111 @@ class BuildCountdownTimer extends StatelessWidget { return buildEmptyWidget(); } - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - time.days == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.days! < 10 ? "0${time.days.toString()}" : time.days.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.days.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - time.hours == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.hours! < 10 ? "0${time.hours.toString()}" : time.hours.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.hours.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - time.min == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.min! < 10 ? "0${time.min.toString()}" : time.min.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.minutes.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - buildSeparator(), - Column( - children: [ - time.sec == null - ? AutoSizeText( - "00", - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ) - : AutoSizeText( - time.sec! < 10 ? "0${time.sec.toString()}" : time.sec.toString(), - maxFontSize: 24, - minFontSize: 20, - style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, - ), - AutoSizeText( - LocaleKeys.seconds.tr(), - minFontSize: 7, - maxFontSize: 8, - style: screenFlag == 0 ? styleTextHome : styleTextMarathon, - ), - ], - ), - ], + return Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + // todo @faiz: Make a separate method and pass value and string , so we can minimize code replication + time.days == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.days! < 10 ? "0${time.days.toString()}" : time.days.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.days.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + time.hours == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.hours! < 10 ? "0${time.hours.toString()}" : time.hours.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.hours.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + time.min == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.min! < 10 ? "0${time.min.toString()}" : time.min.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.minutes.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + buildSeparator(), + Column( + children: [ + time.sec == null + ? AutoSizeText( + "00", + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ) + : AutoSizeText( + time.sec! < 10 ? "0${time.sec.toString()}" : time.sec.toString(), + maxFontSize: 24, + minFontSize: 20, + style: screenFlag == 0 ? styleDigitHome : styleDigitMarathon, + ), + AutoSizeText( + LocaleKeys.seconds.tr(), + minFontSize: 7, + maxFontSize: 8, + style: screenFlag == 0 ? styleTextHome : styleTextMarathon, + ), + ], + ), + ], + ), ); } diff --git a/lib/ui/marathon/widgets/custom_status_widget.dart b/lib/ui/marathon/widgets/custom_status_widget.dart index 4fde6cc..8287bb4 100644 --- a/lib/ui/marathon/widgets/custom_status_widget.dart +++ b/lib/ui/marathon/widgets/custom_status_widget.dart @@ -18,6 +18,7 @@ class CustomStatusWidget extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, + height: 440, decoration: MyDecorations.shadowDecoration, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), child: Column( diff --git a/lib/ui/marathon/widgets/marathon_banner.dart b/lib/ui/marathon/widgets/marathon_banner.dart index 55ff715..e711319 100644 --- a/lib/ui/marathon/widgets/marathon_banner.dart +++ b/lib/ui/marathon/widgets/marathon_banner.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:auto_size_text/auto_size_text.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -9,11 +11,9 @@ import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; -import 'package:mohem_flutter_app/ui/marathon/marathon_intro_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/countdown_timer.dart'; import 'package:provider/provider.dart'; -import 'dart:math' as math; class MarathonBanner extends StatelessWidget { const MarathonBanner({Key? key}) : super(key: key); @@ -21,142 +21,179 @@ class MarathonBanner extends StatelessWidget { @override Widget build(BuildContext context) { MarathonProvider provider = context.read(); - return Container( - decoration: MyDecorations.shadowDecoration, - height: MediaQuery.of(context).size.height * 0.11, - clipBehavior: Clip.antiAlias, - child: Stack( - children: [ - Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - AppState().isArabic(context) ? math.pi : 0, - ), - child: SvgPicture.asset( - "assets/images/marathon_banner_bg.svg", - fit: BoxFit.fill, - width: double.infinity, - ), - ), - Positioned( - left: -20, - top: -10, - child: Transform.rotate( - angle: 15, - child: Container( - width: 65, - height: 32, - color: MyColors.darkDigitColor, - ), - ), - ), - SizedBox( - width: double.infinity, - height: double.infinity, - child: Row( + return provider.marathonDetailModel.startTime != null + ? Container( + decoration: MyDecorations.shadowDecoration, + height: MediaQuery.of(context).size.height * 0.11, + clipBehavior: Clip.antiAlias, + child: Stack( children: [ - const Expanded( - flex: 3, - child: SizedBox( + Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + AppState().isArabic(context) ? math.pi : 0, + ), + child: SvgPicture.asset( + "assets/images/marathon_banner_bg.svg", + fit: BoxFit.fill, width: double.infinity, - height: double.infinity, ), ), - Expanded( - flex: 5, - child: SizedBox( - width: double.infinity, - height: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - AppState().isArabic(context) ? 0.height : 5.height, - AutoSizeText( - LocaleKeys.getReadyForContest.tr(), - minFontSize: 08, - maxFontSize: 11, - style: TextStyle( - fontStyle: FontStyle.italic, - fontWeight: FontWeight.w600, - color: MyColors.white.withOpacity(0.83), - letterSpacing: -0.4, + AppState().isArabic(context) + ? Positioned( + right: -15, + top: -10, + child: Transform.rotate( + angle: 10, + child: Container( + width: 65, + height: 32, + color: MyColors.darkDigitColor, + ), + ), + ) + : Positioned( + left: -20, + top: -10, + child: Transform.rotate( + angle: 15, + child: Container( + width: 65, + height: 32, + color: MyColors.darkDigitColor, + ), + ), + ), + SizedBox( + width: double.infinity, + height: double.infinity, + child: Row( + children: [ + const Expanded( + flex: 3, + child: SizedBox( + width: double.infinity, + height: double.infinity, + ), + ), + Expanded( + flex: AppState().isArabic(context) ? 4 : 5, + child: SizedBox( + width: double.infinity, + height: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AppState().isArabic(context) ? 0.height : 5.height, + AutoSizeText( + LocaleKeys.getReadyForContest.tr(), + minFontSize: 08, + maxFontSize: 11, + style: TextStyle( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w600, + color: MyColors.white.withOpacity(0.83), + letterSpacing: -0.4, + ), + ), + AutoSizeText( + AppState().isArabic(context) ? provider.marathonDetailModel.titleAr ?? "" : provider.marathonDetailModel.titleEn ?? "", + style: TextStyle( + fontStyle: FontStyle.italic, + fontSize: 19, + fontWeight: FontWeight.bold, + color: MyColors.white.withOpacity(0.83), + height: 32 / 22, + ), + ), + 3.height, + BuildCountdownTimer( + timeToMarathon: DateTime.parse(provider.marathonDetailModel.startTime!).millisecondsSinceEpoch, + provider: provider, + screenFlag: 0, + ), + ], + ).paddingOnly( + left: AppState().isArabic(context) ? 12 : 3, + right: AppState().isArabic(context) ? 3 : 12, + ) + ], + ), + ), + ), + ], + ), + ), + AppState().isArabic(context) + ? Align( + alignment: Alignment.topRight, + child: SizedBox( + height: 20, + width: 35, + child: Transform.rotate( + angle: math.pi / 4.5, + child: Text( + LocaleKeys.brainMarathon.tr(), + textAlign: TextAlign.center, + maxLines: 2, + style: const TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + fontSize: 6, + height: 1.2, ), ), - AutoSizeText( - "Saudi Arabia", - style: TextStyle( - fontStyle: FontStyle.italic, - fontSize: 19, + ), + ), + ).paddingOnly(top: 5) + : Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 20, + width: 35, + child: Transform.rotate( + angle: -math.pi / 4.5, + child: Text( + LocaleKeys.brainMarathon.tr(), + textAlign: TextAlign.center, + maxLines: 2, + style: const TextStyle( + color: MyColors.kWhiteColor, fontWeight: FontWeight.bold, - color: MyColors.white.withOpacity(0.83), - height: 32 / 22, + fontSize: 6, + height: 1.2, ), ), - 3.height, - BuildCountdownTimer( - timeToMarathon: dummyEndTime, - provider: provider, - screenFlag: 0, - ), - ], - ).paddingOnly( - left: AppState().isArabic(context) ? 12 : 3, - right: AppState().isArabic(context) ? 3 : 12, - ) - ], - ), - ), - ), + ), + ), + ).paddingOnly(top: 5), + !AppState().isArabic(context) + ? Positioned( + right: 0, + bottom: 0, + child: RotatedBox( + quarterTurns: 4, + child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), + ).paddingAll(15), + ) + : Positioned( + bottom: 0, + left: 0, + child: RotatedBox( + quarterTurns: 2, + child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), + ).paddingAll(15), + ), ], + ).onPress( + () => Navigator.pushNamed(context, AppRoutes.marathonIntroScreen), ), - ), - Align( - alignment: Alignment.topLeft, - child: SizedBox( - height: 20, - width: 35, - child: Transform.rotate( - angle: -math.pi / 4.5, - child: Text( - LocaleKeys.brainMarathon.tr(), - textAlign: TextAlign.center, - maxLines: 2, - style: const TextStyle( - color: MyColors.kWhiteColor, - fontWeight: FontWeight.bold, - fontSize: 6, - height: 1.2, - ), - ), - ), - ), - ).paddingOnly(top: 5), - !AppState().isArabic(context) - ? Positioned( - right: 0, - bottom: 0, - child: RotatedBox( - quarterTurns: 4, - child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), - ).paddingAll(15), - ) - : Positioned( - bottom: 0, - left: 0, - child: RotatedBox( - quarterTurns: 2, - child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.whiteColor), - ).paddingAll(15), - ), - ], - ).onPress( - () => Navigator.pushNamed(context, AppRoutes.marathonIntroScreen), - ), - ); + ) + : const SizedBox(); } } diff --git a/lib/ui/marathon/widgets/marathon_header.dart b/lib/ui/marathon/widgets/marathon_header.dart index 0cc863b..fed6caa 100644 --- a/lib/ui/marathon/widgets/marathon_header.dart +++ b/lib/ui/marathon/widgets/marathon_header.dart @@ -28,8 +28,6 @@ class MarathonHeader extends StatelessWidget { color: MyColors.black, constraints: const BoxConstraints(), onPressed: () { - Provider.of(context, listen: false) - .resetValues(); Navigator.pop(context); }, ) diff --git a/lib/ui/marathon/widgets/question_card.dart b/lib/ui/marathon/widgets/question_card.dart index a3fe720..af538f8 100644 --- a/lib/ui/marathon/widgets/question_card.dart +++ b/lib/ui/marathon/widgets/question_card.dart @@ -1,89 +1,55 @@ import 'package:appinio_swiper/appinio_swiper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/decorations_helper.dart'; -import 'package:mohem_flutter_app/config/routes.dart'; +import 'package:mohem_flutter_app/classes/lottie_consts.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; -import 'package:mohem_flutter_app/models/marathon_question_model.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:provider/provider.dart'; -List isSelectedOptions = [ - false, - false, - false, - false, -]; - -class QuestionCard extends StatefulWidget { +class QuestionCard extends StatelessWidget { final MarathonProvider provider; const QuestionCard({Key? key, required this.provider}) : super(key: key); - @override - State createState() => _QuestionCardState(); -} - -class _QuestionCardState extends State { - final List questionCards = []; - - @override - void initState() { - _loadCards(); - super.initState(); - } - - void _loadCards() { - for (DummyQuestionModel question in questions) { - questionCards.add( - CardContent( - question: question, - provider: widget.provider, - ), - ); - } - } - @override Widget build(BuildContext context) { return CupertinoPageScaffold( - child: SizedBox( - height: 440, - width: double.infinity, - child: Consumer( - builder: (BuildContext context, MarathonProvider provider, _) { - return AppinioSwiper( - padding: EdgeInsets.zero, - isDisabled: true, - controller: provider.swiperController, - unswipe: (int index, AppinioSwiperDirection direction) {}, - cards: questionCards, - onSwipe: (int index, AppinioSwiperDirection direction) { - if (direction == AppinioSwiperDirection.left) { - provider.startTimer(context); - } - }, - ); - }, - ), - ), + child: provider.cardContentList.isEmpty + ? Lottie.asset(MyLottieConsts.hourGlassLottie, height: 250).paddingOnly(top: 50) + : SizedBox( + height: 440, + width: double.infinity, + child: Consumer( + builder: (BuildContext context, MarathonProvider provider, _) { + return AppinioSwiper( + duration: const Duration(milliseconds: 400), + padding: EdgeInsets.zero, + isDisabled: true, + controller: provider.swiperController, + unswipe: (int index, AppinioSwiperDirection direction) {}, + onSwipe: (int index, AppinioSwiperDirection direction) {}, + cards: provider.cardContentList, + ); + }, + ), + ), ); } } class CardContent extends StatelessWidget { - final DummyQuestionModel question; - final MarathonProvider provider; - - const CardContent({ - Key? key, - required this.question, - required this.provider, - }) : super(key: key); + const CardContent({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), @@ -118,12 +84,12 @@ class CardContent extends StatelessWidget { topRight: Radius.circular(10), ), ), - child: const Center( + child: Center( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 13), + padding: const EdgeInsets.symmetric(horizontal: 13), child: Text( - "What is the capital of Saudi Arabia?", - style: TextStyle( + AppState().isArabic(context) ? provider.currentQuestion.titleAr ?? "" : provider.currentQuestion.titleEn ?? "", + style: const TextStyle( color: MyColors.white, fontSize: 16, fontWeight: FontWeight.w600, @@ -132,49 +98,21 @@ class CardContent extends StatelessWidget { ), ), ), - AnswerContent(question: question, provider: provider), + const AnswerContent(), ], ), ); } } -class AnswerContent extends StatefulWidget { - final DummyQuestionModel question; - final MarathonProvider provider; - - const AnswerContent({Key? key, required this.question, required this.provider}) : super(key: key); - - @override - State createState() => _AnswerContentState(); -} - -class _AnswerContentState extends State { - void updateOption(int index, bool value) { - isSelectedOptions[0] = false; - isSelectedOptions[1] = false; - isSelectedOptions[2] = false; - isSelectedOptions[3] = false; - isSelectedOptions[index] = value; - setState(() {}); - } - - Decoration getContainerColor(int index) { - if (!isSelectedOptions[index]) { - return MyDecorations.getContainersDecoration(MyColors.greyF7Color); - } - if (isSelectedOptions[index] && context.watch().start > 0) { - return MyDecorations.getContainersDecoration(MyColors.yellowColorII); - } - return MyDecorations.getContainersDecoration( - isSelectedOptions[index] ? MyColors.greenColor : MyColors.greyF7Color, - ); - } +class AnswerContent extends StatelessWidget { + const AnswerContent({Key? key}) : super(key: key); @override Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); return Container( - padding: const EdgeInsets.all(13), + padding: const EdgeInsets.symmetric(vertical: 31, horizontal: 13), decoration: const BoxDecoration( color: MyColors.kWhiteColor, borderRadius: BorderRadius.only( @@ -182,127 +120,48 @@ class _AnswerContentState extends State { bottomRight: Radius.circular(10), ), ), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - return; - } - updateOption(0, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(0), - child: Center( - child: Text( - widget.question.opt1!, - style: TextStyle( - color: isSelectedOptions[0] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - return; - } - updateOption(1, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(1), - child: Center( - child: Text( - widget.question.opt2!, - style: TextStyle( - color: isSelectedOptions[1] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, - ); - return; - } - updateOption(2, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(2), - child: Center( - child: Text( - widget.question.opt3!, - style: TextStyle( - color: isSelectedOptions[2] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - if (widget.provider.currentQuestionNumber == 9) { - widget.provider.cancelTimer(); - widget.provider.resetValues(); - Navigator.pushReplacementNamed( - context, - AppRoutes.marathonWinnerSelection, + child: provider.currentQuestion.questionOptions != null + ? ListView.separated( + itemCount: provider.currentQuestion.questionOptions!.length, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + return AnswerTileForText( + index: index, + onAnswerTapped: () { + provider.updateCurrentQuestionOptionStatus(QuestionsOptionStatus.selected, index); + }, ); - return; - } - updateOption(3, true); - }, - child: Container( - alignment: Alignment.centerLeft, - decoration: getContainerColor(3), - child: Center( - child: Text( - widget.question.opt3!, - style: TextStyle( - color: isSelectedOptions[3] ? MyColors.white : MyColors.darkTextColor, - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ).paddingOnly(top: 17, bottom: 17), - ), - ), - ), - ], + }, + separatorBuilder: (BuildContext context, int index) => 15.height, + ) + : const SizedBox(), + ); + } +} + +class AnswerTileForText extends StatelessWidget { + final int index; + final Function() onAnswerTapped; + + const AnswerTileForText({Key? key, required this.index, required this.onAnswerTapped}) : super(key: key); + + @override + Widget build(BuildContext context) { + MarathonProvider provider = context.watch(); + return InkWell( + onTap: () { + onAnswerTapped(); + }, + child: Container( + alignment: Alignment.centerLeft, + decoration: MyDecorations.getAnswersContainerColor(provider.currentQuestion.questionOptions![index].optionStatus!), + child: Center( + child: (AppState().isArabic(context) ? provider.currentQuestion.questionOptions![index].titleAr! : provider.currentQuestion.questionOptions![index].titleEn!) + .toText16( + color: provider.currentQuestion.questionOptions![index].optionStatus == QuestionsOptionStatus.unSelected ? MyColors.darkTextColor : MyColors.white, + ) + .paddingOnly(top: 17, bottom: 17), + ), ), ); } diff --git a/lib/ui/marathon/widgets/question_card_builder.dart b/lib/ui/marathon/widgets/question_card_builder.dart new file mode 100644 index 0000000..e4bce29 --- /dev/null +++ b/lib/ui/marathon/widgets/question_card_builder.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:mohem_flutter_app/models/marathon/question_model.dart'; + +class QuestionCardBuilder extends StatelessWidget { + final WidgetBuilder onQuestion; + final WidgetBuilder onCompleted; + final WidgetBuilder onWrongAnswer; + final WidgetBuilder onCorrectAnswer; + final WidgetBuilder onWinner; + final WidgetBuilder onSkippedAnswer; + final WidgetBuilder onFindingWinner; + final QuestionCardStatus questionCardStatus; + + const QuestionCardBuilder({ + Key? key, + required this.onQuestion, + required this.onCompleted, + required this.onCorrectAnswer, + required this.onWinner, + required this.onSkippedAnswer, + required this.onWrongAnswer, + required this.onFindingWinner, + required this.questionCardStatus, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + switch (questionCardStatus) { + case QuestionCardStatus.question: + return onQuestion(context); + + case QuestionCardStatus.wrongAnswer: + return onWrongAnswer(context); + + case QuestionCardStatus.correctAnswer: + return onCorrectAnswer(context); + + case QuestionCardStatus.completed: + return onCompleted(context); + + case QuestionCardStatus.winnerFound: + return onWinner(context); + + case QuestionCardStatus.findingWinner: + return onFindingWinner(context); + + case QuestionCardStatus.skippedAnswer: + return onSkippedAnswer(context); + } + } +} diff --git a/lib/ui/misc/request_submit_screen.dart b/lib/ui/misc/request_submit_screen.dart index a12bedb..1d95efd 100644 --- a/lib/ui/misc/request_submit_screen.dart +++ b/lib/ui/misc/request_submit_screen.dart @@ -71,7 +71,7 @@ class _RequestSubmitScreenState extends State { } void submitRequest() async { - try { + try { Utils.showLoading(context); List> list = []; if (attachmentFiles.isNotEmpty) { @@ -133,7 +133,7 @@ class _RequestSubmitScreenState extends State { params!.pItemId, params!.transactionId, ); - }else if (params!.approvalFlag == 'endEmployment') { + } else if (params!.approvalFlag == 'endEmployment') { await TerminationDffApiClient().startTermApprovalProcess( "SUBMIT", comments.text, diff --git a/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart b/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart index 6f02be4..a953be3 100644 --- a/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart +++ b/lib/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart @@ -96,9 +96,11 @@ class _DynamicInputScreenState extends State { SubmitEITTransactionList submitEITTransactionList = await MyAttendanceApiClient().submitEitTransaction(dESCFLEXCONTEXTCODE, dynamicParams!.dynamicId, values, empID: dynamicParams!.selectedEmp ?? ''); Utils.hideLoading(context); - await Navigator.pushNamed(context, AppRoutes.requestSubmitScreen, + var res = await Navigator.pushNamed(context, AppRoutes.requestSubmitScreen, arguments: RequestSubmitScreenParams(LocaleKeys.submit.tr(), submitEITTransactionList.pTRANSACTIONID!, submitEITTransactionList.pITEMKEY!, 'eit')); - Utils.showLoading(context); + if (res != null && res == true) { + Utils.showLoading(context); + } await LeaveBalanceApiClient().cancelHrTransaction(submitEITTransactionList.pTRANSACTIONID!); Utils.hideLoading(context); } catch (ex) { diff --git a/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart b/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart index 444b83e..749599c 100644 --- a/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart +++ b/lib/ui/profile/dynamic_screens/dynamic_input_basic_details_screen.dart @@ -74,9 +74,9 @@ class _DynamicInputScreenState extends State { getBasicDetColsStructureList?.forEach((GetBasicDetColsStructureList element) { element.userBasicDetail = dynamicParams!.getEmployeeBasicDetailsList!.singleWhere((GetEmployeeBasicDetailsList userDetail) => userDetail.aPPLICATIONCOLUMNNAME == element.aPPLICATIONCOLUMNNAME); - if (element.objectValuesList != null) { - ObjectValuesList dropDownListValue = element.objectValuesList!.singleWhere((ObjectValuesList dropdown) => dropdown.cODE == element.userBasicDetail!.vARCHAR2VALUE); - element.userBasicDetail!.sEGMENTVALUEDSP = dropDownListValue.mEANING; + if (element.objectValuesList != null && element.userBasicDetail?.vARCHAR2VALUE != '') { + ObjectValuesList dropDownListValue = element.objectValuesList!.singleWhere((ObjectValuesList dropdown) => dropdown.cODE == element.userBasicDetail?.vARCHAR2VALUE); + element.userBasicDetail?.sEGMENTVALUEDSP = dropDownListValue.mEANING; } }); } else { @@ -93,9 +93,9 @@ class _DynamicInputScreenState extends State { getBasicDetColsStructureList?.forEach((GetBasicDetColsStructureList element) { element.userBasicDetail = dynamicParams!.getEmployeeBasicDetailsList!.singleWhere((GetEmployeeBasicDetailsList userDetail) => userDetail.aPPLICATIONCOLUMNNAME == element.aPPLICATIONCOLUMNNAME); - if (element.objectValuesList != null) { + if (element.objectValuesList != null && element.userBasicDetail!.vARCHAR2VALUE != '') { ObjectValuesList dropDownListValue = element.objectValuesList!.singleWhere((ObjectValuesList dropdown) => dropdown.cODE == element.userBasicDetail!.vARCHAR2VALUE); - element.userBasicDetail!.sEGMENTVALUEDSP = dropDownListValue.mEANING; + element.userBasicDetail?.sEGMENTVALUEDSP = dropDownListValue.mEANING; } }); } @@ -262,7 +262,7 @@ class _DynamicInputScreenState extends State { return PopupMenuButton( child: DynamicTextFieldWidget( (model.sEGMENTPROMPT ?? "") + (model.rEQUIREDFLAG == "Y" ? "*" : ""), - getBasicDetColsStructureList![index].userBasicDetail!.sEGMENTVALUEDSP ?? "", + getBasicDetColsStructureList![index].userBasicDetail?.sEGMENTVALUEDSP ?? "", isEnable: false, isPopup: true, ).paddingOnly(bottom: 12), @@ -363,7 +363,7 @@ class _DynamicInputScreenState extends State { Utils.showLoading(context); int numberValue = 0; List> values = getBasicDetDffStructureList!.map((e) { - String tempVar = e.userBasicDetail!.vARCHAR2VALUE ?? ""; + String tempVar = e.userBasicDetail?.vARCHAR2VALUE ?? ""; if (e.fORMATTYPE == "X") { // for date format type, date format is changed diff --git a/lib/ui/work_list/itg_fragments/approval_level_fragment.dart b/lib/ui/work_list/itg_fragments/approval_level_fragment.dart index 52915ab..09e44d4 100644 --- a/lib/ui/work_list/itg_fragments/approval_level_fragment.dart +++ b/lib/ui/work_list/itg_fragments/approval_level_fragment.dart @@ -1,11 +1,13 @@ 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/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/itg_forms_models/wf_history_model.dart'; +import 'package:mohem_flutter_app/ui/work_list/sheets/delegate_sheet.dart'; import 'package:mohem_flutter_app/ui/work_list/sheets/selected_itg_item_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/circular_avatar.dart'; @@ -109,6 +111,19 @@ class ApprovalLevelfragment extends StatelessWidget { }).expanded, Container(width: 1, height: 30, color: MyColors.lightGreyEFColor), LocaleKeys.delegate.tr().toText12(color: MyColors.gradiantEndColor).center.paddingOnly(top: 6, bottom: 6).onPress(() { + if (history.employeeID == AppState().memberInformationList?.eMPLOYEENUMBER) { + showMyBottomSheet(context, + callBackFunc: voidCallback, + child: DelegateSheet( + title: LocaleKeys.delegate.tr(), + apiMode: "Delegate", + notificationID: null, + actionHistoryList: null, + wFHistory: wFHistory, + callBackFunc: voidCallback, + )); + return; + } showMyBottomSheet( context, callBackFunc: voidCallback, @@ -135,11 +150,7 @@ class ApprovalLevelfragment extends StatelessWidget { return MyColors.yellowColor; } else if (code.toLowerCase() == "not doable" || code.toLowerCase() == "rejected") { return MyColors.redColor; - } else if (code.toLowerCase() == "approved" || - code.toLowerCase() == "auto-approve" || - code.toLowerCase() == "auto-approved" || - code.toLowerCase() == "doable" || - code.toLowerCase() == "answer") { + } else if (code.toLowerCase() == "approved" || code.toLowerCase() == "auto-approve" || code.toLowerCase() == "auto-approved" || code.toLowerCase() == "doable" || code.toLowerCase() == "answer") { return MyColors.greenColor; } else if (code.toLowerCase() == "requested information" || code.toLowerCase() == "assign" || code.toLowerCase() == "reassign") { return MyColors.orange; diff --git a/lib/ui/work_list/worklist_detail_screen.dart b/lib/ui/work_list/worklist_detail_screen.dart index 824edc3..5d6727c 100644 --- a/lib/ui/work_list/worklist_detail_screen.dart +++ b/lib/ui/work_list/worklist_detail_screen.dart @@ -191,169 +191,187 @@ class _WorkListDetailScreenState extends State { return Scaffold( appBar: AppBarWidget(context, title: LocaleKeys.details.tr()), backgroundColor: Colors.white, - body: Stack( - children: [ - Column( - children: [ - Container( - padding: const EdgeInsets.only(left: 21, right: 21, top: 16, bottom: 16), - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(25), - bottomRight: Radius.circular(25), - ), - gradient: LinearGradient( - transform: GradientRotation(.83), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - MyColors.gradiantEndColor, - MyColors.gradiantStartColor, - ], - ), - ), - child: Row( - children: [ - myTab(LocaleKeys.info.tr(), 0), - (workListData!.iTEMTYPE == "HRSSA" || workListData!.iTEMTYPE == "STAMP") ? myTab(LocaleKeys.details.tr(), 1) : myTab(LocaleKeys.request.tr(), 1), - myTab(LocaleKeys.actions.tr(), 2), - myTab(LocaleKeys.attachments.tr(), 3), - ], - ), - ), - if ((workListData?.sUBJECT ?? "").isNotEmpty) workListData!.sUBJECT!.toText14().paddingOnly(top: 20, right: 21, left: 21), - PageView( - controller: controller, - onPageChanged: (pageIndex) { - setState(() { - tabIndex = pageIndex; - }); - }, - children: [ - InfoFragment( - poHeaderList: getPoNotificationBody?.pOHeader ?? [], - workListData: workListData, - itemCreationHeader: getItemCreationNtfBody?.itemCreationHeader ?? [], - getStampMsNotifications: getStampMsNotifications, - getStampNsNotifications: getStampNsNotifications, - getEitCollectionNotificationBodyList: getEitCollectionNotificationBodyList, - getPhonesNotificationBodyList: getPhonesNotificationBodyList, - getBasicDetNtfBodyList: getBasicDetNtfBodyList, - getAbsenceCollectionNotificationBodyList: getAbsenceCollectionNotificationBodyList, - getContactNotificationBodyList: getContactNotificationBodyList, - getPrNotificationBodyList: getPrNotificationBody, - ), - (workListData!.iTEMTYPE == "HRSSA" || workListData!.iTEMTYPE == "STAMP") - ? DetailFragment(workListData, memberInformationListModel) - : RequestFragment( - moNotificationBodyList: getMoNotificationBodyList, - poLinesList: getPoNotificationBody?.pOLines ?? [], - itemCreationLines: getItemCreationNtfBody?.itemCreationLines ?? [], - prLinesList: getPrNotificationBody?.pRLines ?? [], - ), - isActionHistoryLoaded - ? actionHistoryList.isEmpty - ? Utils.getNoDataWidget(context) - : ActionsFragment( - workListData!.nOTIFICATIONID, - actionHistoryList, - voidCallback: reloadWorkList, - ) - : showLoadingAnimation(), - isAttachmentLoaded - ? getAttachmentList.isEmpty - ? Utils.getNoDataWidget(context) - : AttachmentsFragment(getAttachmentList) - : showLoadingAnimation(), - ], - ).expanded, - if (isApproveAvailable || isRejectAvailable || isCloseAvailable) + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + switchInCurve: Curves.easeInToLinear, + transitionBuilder: (Widget child, Animation animation) { + Animation custom = Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation); + return ClipRect( + child: SlideTransition( + position: custom, + child: child, + // textDirection: TextDirection.ltr, + ), + ); + }, + child: Stack( + key: ValueKey(AppState().workListIndex ?? 0), + children: [ + Column( + children: [ Container( - padding: const EdgeInsets.only(top: 14, bottom: 14, left: 21, right: 21), + padding: const EdgeInsets.only(left: 21, right: 21, top: 16, bottom: 16), decoration: const BoxDecoration( - color: Colors.white, - border: Border( - top: BorderSide(color: MyColors.lightGreyEFColor, width: 1.0), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(25), + bottomRight: Radius.circular(25), + ), + gradient: LinearGradient( + transform: GradientRotation(.83), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ], ), ), child: Row( children: [ - if (isRejectAvailable) - DefaultButton( - LocaleKeys.reject.tr(), - () => performAction("REJECTED"), - colors: const [Color(0xffE47A7E), Color(0xffDE6D71)], - ).expanded, - if (isApproveAvailable && isRejectAvailable) 8.width, - if (isApproveAvailable) - DefaultButton( - LocaleKeys.approve.tr(), - () => performAction("APPROVED"), - colors: const [Color(0xff28C884), Color(0xff1BB271)], - ).expanded, - if (isCloseAvailable) - DefaultButton( - LocaleKeys.ok.tr(), - () => performAction("CLOSE"), - colors: const [Color(0xff32D892), Color(0xff1AB170)], - ).expanded, - 8.width, - Container( - height: 43, - width: 43, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: MyColors.lightGreyE6Color, - ), - child: Icon(showFabOptions ? Icons.more_vert_rounded : Icons.more_horiz_rounded, color: MyColors.darkIconColor), - ).onPress(() { - setState(() { - showFabOptions = true; - }); - }) + myTab(LocaleKeys.info.tr(), 0), + (workListData!.iTEMTYPE == "HRSSA" || workListData!.iTEMTYPE == "STAMP") ? myTab(LocaleKeys.details.tr(), 1) : myTab(LocaleKeys.request.tr(), 1), + myTab(LocaleKeys.actions.tr(), 2), + myTab(LocaleKeys.attachments.tr(), 3), ], ), - ) - ], - ), - IgnorePointer( - ignoring: !showFabOptions, - child: AnimatedOpacity( - opacity: showFabOptions ? 1 : 0, - duration: const Duration(milliseconds: 250), - child: Container( - padding: const EdgeInsets.only(left: 21, right: 21, bottom: 75 - 12), - width: double.infinity, - height: double.infinity, - color: Colors.white.withOpacity(.67), - alignment: Alignment.bottomRight, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, + ), + if ((workListData?.sUBJECT ?? "").isNotEmpty) workListData!.sUBJECT!.toText14().paddingOnly(top: 20, right: 21, left: 21), + PageView( + controller: controller, + onPageChanged: (int pageIndex) { + setState(() { + tabIndex = pageIndex; + }); + }, children: [ - myFab(LocaleKeys.skip.tr(), "assets/images/skip.svg").onPress(() { - if (AppState().workList!.length - 1 > AppState().workListIndex!) { - AppState().setWorkListIndex = AppState().workListIndex! + 1; - workListData = null; - showFabOptions = false; - tabIndex = 0; - getDataFromState(); - } else if (AppState().workList!.length - 1 == AppState().workListIndex!) { - Navigator.pop(context); - } - }), - 12.height, - ...viewApiButtonsList(notificationButtonsList), + InfoFragment( + poHeaderList: getPoNotificationBody?.pOHeader ?? [], + workListData: workListData, + itemCreationHeader: getItemCreationNtfBody?.itemCreationHeader ?? [], + getStampMsNotifications: getStampMsNotifications, + getStampNsNotifications: getStampNsNotifications, + getEitCollectionNotificationBodyList: getEitCollectionNotificationBodyList, + getPhonesNotificationBodyList: getPhonesNotificationBodyList, + getBasicDetNtfBodyList: getBasicDetNtfBodyList, + getAbsenceCollectionNotificationBodyList: getAbsenceCollectionNotificationBodyList, + getContactNotificationBodyList: getContactNotificationBodyList, + getPrNotificationBodyList: getPrNotificationBody, + ), + (workListData!.iTEMTYPE == "HRSSA" || workListData!.iTEMTYPE == "STAMP") + ? DetailFragment(workListData, memberInformationListModel) + : RequestFragment( + moNotificationBodyList: getMoNotificationBodyList, + poLinesList: getPoNotificationBody?.pOLines ?? [], + itemCreationLines: getItemCreationNtfBody?.itemCreationLines ?? [], + prLinesList: getPrNotificationBody?.pRLines ?? [], + ), + isActionHistoryLoaded + ? actionHistoryList.isEmpty + ? Utils.getNoDataWidget(context) + : ActionsFragment( + workListData!.nOTIFICATIONID, + actionHistoryList, + voidCallback: reloadWorkList, + ) + : showLoadingAnimation(), + isAttachmentLoaded + ? getAttachmentList.isEmpty + ? Utils.getNoDataWidget(context) + : AttachmentsFragment(getAttachmentList) + : showLoadingAnimation(), ], + ).expanded, + if (isApproveAvailable || isRejectAvailable || isCloseAvailable) + Container( + padding: const EdgeInsets.only(top: 14, bottom: 14, left: 21, right: 21), + decoration: const BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide(color: MyColors.lightGreyEFColor, width: 1.0), + ), + ), + child: Row( + children: [ + if (isRejectAvailable) + DefaultButton( + LocaleKeys.reject.tr(), + () => performAction("REJECTED"), + colors: const [Color(0xffE47A7E), Color(0xffDE6D71)], + ).expanded, + if (isApproveAvailable && isRejectAvailable) 8.width, + if (isApproveAvailable) + DefaultButton( + LocaleKeys.approve.tr(), + () => performAction("APPROVED"), + colors: const [Color(0xff28C884), Color(0xff1BB271)], + ).expanded, + if (isCloseAvailable) + DefaultButton( + LocaleKeys.ok.tr(), + () => performAction("CLOSE"), + colors: const [Color(0xff32D892), Color(0xff1AB170)], + ).expanded, + 8.width, + Container( + height: 43, + width: 43, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: MyColors.lightGreyE6Color, + ), + child: Icon(showFabOptions ? Icons.more_vert_rounded : Icons.more_horiz_rounded, color: MyColors.darkIconColor), + ).onPress(() { + setState(() { + showFabOptions = true; + }); + }) + ], + ), + ) + ], + ), + IgnorePointer( + ignoring: !showFabOptions, + child: AnimatedOpacity( + opacity: showFabOptions ? 1 : 0, + duration: const Duration(milliseconds: 250), + child: Container( + padding: const EdgeInsets.only(left: 21, right: 21, bottom: 75 - 12), + width: double.infinity, + height: double.infinity, + color: Colors.white.withOpacity(.67), + alignment: Alignment.bottomRight, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + myFab(LocaleKeys.skip.tr(), "assets/images/skip.svg").onPress(() { + if (AppState().workList!.length - 1 > AppState().workListIndex!) { + AppState().setWorkListIndex = AppState().workListIndex! + 1; + workListData = null; + showFabOptions = false; + tabIndex = 0; + getDataFromState(); + } else if (AppState().workList!.length - 1 == AppState().workListIndex!) { + Navigator.pop(context); + } + }), + 12.height, + ...viewApiButtonsList(notificationButtonsList), + ], + ), ), - ), - ).onPress(() { - setState(() { - showFabOptions = false; - }); - }), - ), - ], + ).onPress(() { + setState(() { + showFabOptions = false; + }); + }), + ), + ], + ), ), floatingActionButton: (!isApproveAvailable && !isRejectAvailable && !isCloseAvailable) ? Container( @@ -546,7 +564,7 @@ class _WorkListDetailScreenState extends State { Future performNetworkCall(BuildContext context, {String? email, String? userId}) async { showDialog( context: context, - builder: (cxt) => ConfirmDialog( + builder: (BuildContext cxt) => ConfirmDialog( message: LocaleKeys.wantToReject.tr(), okTitle: LocaleKeys.reject.tr(), onTap: () async { @@ -629,12 +647,12 @@ class _WorkListDetailScreenState extends State { print(actionMode); showDialog( context: context, - builder: (cxt) => AcceptRejectInputDialog( + builder: (BuildContext cxt) => AcceptRejectInputDialog( message: title != null ? null : LocaleKeys.requestedItems.tr(), title: title, notificationGetRespond: notificationNoteInput, actionMode: actionMode, - onTap: (note) { + onTap: (String note) { Map payload = { "P_ACTION_MODE": actionMode, "P_APPROVER_INDEX": null, @@ -915,9 +933,9 @@ class _WorkListDetailScreenState extends State { apiCallCount++; notificationButtonsList = await WorkListApiClient().getNotificationButtons(workListData!.nOTIFICATIONID!); if (notificationButtonsList.isNotEmpty) { - isCloseAvailable = notificationButtonsList.any((element) => element.bUTTONACTION == "CLOSE"); - isApproveAvailable = notificationButtonsList.any((element) => element.bUTTONACTION == "APPROVED"); - isRejectAvailable = notificationButtonsList.any((element) => element.bUTTONACTION == "REJECTED"); + isCloseAvailable = notificationButtonsList.any((GetNotificationButtonsList element) => element.bUTTONACTION == "CLOSE"); + isApproveAvailable = notificationButtonsList.any((GetNotificationButtonsList element) => element.bUTTONACTION == "APPROVED"); + isRejectAvailable = notificationButtonsList.any((GetNotificationButtonsList element) => element.bUTTONACTION == "REJECTED"); } apiCallCount--; if (apiCallCount == 0) { diff --git a/lib/ui/work_list/worklist_fragments/actions_fragment.dart b/lib/ui/work_list/worklist_fragments/actions_fragment.dart index 58e9ac7..98b5b72 100644 --- a/lib/ui/work_list/worklist_fragments/actions_fragment.dart +++ b/lib/ui/work_list/worklist_fragments/actions_fragment.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/src/public_ext.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/date_uitl.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; @@ -7,6 +8,7 @@ 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/get_action_history_list_model.dart'; +import 'package:mohem_flutter_app/ui/work_list/sheets/delegate_sheet.dart'; import 'package:mohem_flutter_app/ui/work_list/sheets/selected_item_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/circular_avatar.dart'; @@ -109,6 +111,18 @@ class ActionsFragment extends StatelessWidget { }).expanded, Container(width: 1, height: 30, color: MyColors.lightGreyEFColor), LocaleKeys.delegate.tr().toText12(color: MyColors.gradiantEndColor).center.paddingOnly(top: 6, bottom: 6).onPress(() { + if (actionHistory.uSERNAME == AppState().memberInformationList?.eMPLOYEENUMBER) { + showMyBottomSheet(context, + callBackFunc: voidCallback, + child: DelegateSheet( + title: LocaleKeys.delegate.tr(), + apiMode: "DELEGATE", + notificationID: notificationID, + actionHistoryList: actionHistoryList, + callBackFunc: voidCallback, + )); + return; + } showMyBottomSheet( context, callBackFunc: voidCallback, @@ -132,13 +146,16 @@ class ActionsFragment extends StatelessWidget { String getActionDuration(int index) { if (actionHistoryList[index].aCTIONCODE == "SUBMIT") { return ""; - } else if(actionHistoryList[index].aCTIONCODE == "PENDING") { - DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(actionHistoryList[++index].nOTIFICATIONDATE!); + } else if (actionHistoryList[index].aCTIONCODE == "PENDING") { + if (actionHistoryList[index + 1].nOTIFICATIONDATE!.isEmpty) { + return ""; + } + DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(actionHistoryList[index + 1].nOTIFICATIONDATE!); Duration duration = DateTime.now().difference(dateTimeFrom); return "Action duration: " + DateUtil.formatDuration(duration); } else { DateTime dateTimeTo = DateUtil.convertSimpleStringDateToDate(actionHistoryList[index].nOTIFICATIONDATE!); - DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(actionHistoryList[++index].nOTIFICATIONDATE!); + DateTime dateTimeFrom = DateUtil.convertSimpleStringDateToDate(actionHistoryList[index + 1].nOTIFICATIONDATE!); Duration duration = dateTimeTo.difference(dateTimeFrom); return "Action duration: " + DateUtil.formatDuration(duration); } diff --git a/lib/widgets/app_bar_widget.dart b/lib/widgets/app_bar_widget.dart index c2129c1..fa59d3a 100644 --- a/lib/widgets/app_bar_widget.dart +++ b/lib/widgets/app_bar_widget.dart @@ -20,7 +20,7 @@ AppBar AppBarWidget(BuildContext context, children: [ GestureDetector( behavior: HitTestBehavior.opaque, - onTap: Feedback.wrapForTap(() => Navigator.maybePop(context), context), + onTap: Feedback.wrapForTap(() => Navigator.maybePop(context, true), context), child: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor), ), 4.width, @@ -59,7 +59,7 @@ AppBar AppBarWidget(BuildContext context, }, icon: const Icon(Icons.people, color: MyColors.textMixColor), ), - ...actions??[] + ...actions ?? [] ], ); } diff --git a/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart b/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart index b88b96f..32b501c 100644 --- a/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart @@ -5,7 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:mohem_flutter_app/api/chat/chat_provider_model.dart'; +import 'package:mohem_flutter_app/api/chat/chat_api_client.dart'; import 'package:mohem_flutter_app/api/worklist/worklist_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; @@ -88,7 +88,12 @@ class _SearchEmployeeBottomSheetState extends State { void fetchChatUser({bool isNeedLoading = true}) async { try { Utils.showLoading(context); - chatUsersList = await ChatProviderModel().getChatMemberFromSearch(searchText, int.parse(AppState().chatDetails!.response!.id.toString())); + chatUsersList = await ChatApiClient().getChatMemberFromSearch( + searchText, + int.parse( + AppState().chatDetails!.response!.id.toString(), + ), + ); Utils.hideLoading(context); setState(() {}); } catch (e) { @@ -236,7 +241,6 @@ class _SearchEmployeeBottomSheetState extends State { arguments: {"targetUser": chatUsersList![index], "isNewChat": true}, ); }, - ), ); }, diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index f92f9f4..69bf7e4 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -20,7 +20,7 @@ class ImageOptions { if (Platform.isAndroid) { cameraImageAndroid(image); } else { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); + File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 10))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -33,7 +33,7 @@ class ImageOptions { if (Platform.isAndroid) { galleryImageAndroid(image); } else { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 20))?.path ?? ""); + File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 10))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -74,7 +74,7 @@ class ImageOptions { if (Platform.isAndroid) { galleryImageAndroid(image); } else { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 20))?.path ?? ""); + File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 10))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -91,7 +91,7 @@ class ImageOptions { if (Platform.isAndroid) { cameraImageAndroid(image); } else { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); + File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 10))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -114,7 +114,7 @@ class ImageOptions { } void galleryImageAndroid(Function(String, File) image) async { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 20))?.path ?? ""); + File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.gallery, imageQuality: 10))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); @@ -124,7 +124,7 @@ void galleryImageAndroid(Function(String, File) image) async { } void cameraImageAndroid(Function(String, File) image) async { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); + File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 10))?.path ?? ""); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); diff --git a/lib/widgets/mark_attendance_widget.dart b/lib/widgets/mark_attendance_widget.dart index c41b89c..a6b6cfc 100644 --- a/lib/widgets/mark_attendance_widget.dart +++ b/lib/widgets/mark_attendance_widget.dart @@ -14,6 +14,7 @@ import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/ui/dialogs/success_dialog.dart'; +import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; import 'package:mohem_flutter_app/widgets/dialogs/dialogs.dart'; import 'package:mohem_flutter_app/widgets/location/Location.dart'; import 'package:mohem_flutter_app/widgets/nfc/nfc_reader_sheet.dart'; @@ -144,14 +145,28 @@ class _MarkAttendanceWidgetState extends State { Utils.showLoading(context); try { GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId, isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); - bool status = await model.fetchAttendanceTracking(context); - Utils.hideLoading(context); - showMDialog( - context, - backgroundColor: Colors.transparent, - isDismissable: false, - child: SuccessDialog(widget.isFromDashboard), - ); + if(g?.messageStatus != 1) { + Utils.hideLoading(context); + showDialog( + context: context, + builder: (cxt) => ConfirmDialog( + message: g?.errorEndUserMessage ?? "Unexpected error occurred", + onTap: () { + Navigator.pop(context); + }, + ), + ); + } else { + bool status = await model.fetchAttendanceTracking(context); + Utils.hideLoading(context); + showMDialog( + context, + backgroundColor: Colors.transparent, + isDismissable: false, + child: SuccessDialog(widget.isFromDashboard), + ); + } + } catch (ex) { print(ex); Utils.hideLoading(context); @@ -166,14 +181,27 @@ class _MarkAttendanceWidgetState extends State { Utils.showLoading(context); try { GenericResponseModel? g = await DashboardApiClient().markAttendance(pointType: 2, nfcValue: nfcId ?? "", isGpsRequired: isNfcLocationEnabled, lat: lat, long: lng); - bool status = await model.fetchAttendanceTracking(context); - Utils.hideLoading(context); - showMDialog( - context, - backgroundColor: Colors.transparent, - isDismissable: false, - child: SuccessDialog(widget.isFromDashboard), - ); + if(g?.messageStatus != 1) { + Utils.hideLoading(context); + showDialog( + context: context, + builder: (cxt) => ConfirmDialog( + message: g?.errorEndUserMessage ?? "Unexpected error occurred", + onTap: () { + Navigator.pop(context); + }, + ), + ); + } else { + bool status = await model.fetchAttendanceTracking(context); + Utils.hideLoading(context); + showMDialog( + context, + backgroundColor: Colors.transparent, + isDismissable: false, + child: SuccessDialog(widget.isFromDashboard), + ); + } } catch (ex) { print(ex); Utils.hideLoading(context); diff --git a/lib/widgets/shimmer/dashboard_shimmer_widget.dart b/lib/widgets/shimmer/dashboard_shimmer_widget.dart index 369b808..6fe5a03 100644 --- a/lib/widgets/shimmer/dashboard_shimmer_widget.dart +++ b/lib/widgets/shimmer/dashboard_shimmer_widget.dart @@ -188,6 +188,53 @@ class ServicesMenuShimmer extends StatelessWidget { } } + +class MarathonBannerShimmer extends StatelessWidget { + const MarathonBannerShimmer({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: const Color(0xff000000).withOpacity(.05), + blurRadius: 26, + offset: const Offset(0, -3), + ), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset("assets/images/monthly_attendance.svg").toShimmer(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + "Attendan".toText11(isBold: false).toShimmer(), + 5.height, + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: LocaleKeys.attendance.tr().toText11(isBold: false).toShimmer(), + ), + 6.width, + SvgPicture.asset("assets/images/arrow_next.svg").paddingOnly(bottom: 4).toShimmer() + ], + ), + ], + ) + ], + ).paddingOnly(left: 10, right: 10, bottom: 10, top: 12), + ); + } +} + class ChatHomeShimmer extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/pubspec.yaml b/pubspec.yaml index f5742bb..a2efb36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,13 +79,12 @@ dependencies: pull_to_refresh: ^2.0.0 # lottie json animations lottie: any -# Steps Progress - steps_indicator: ^1.3.0 # Marathon Card Swipe appinio_swiper: ^1.1.1 expandable: ^5.0.1 - +# networkImage + cached_network_image: ^3.2.2 #Chat signalr_netcore: ^1.3.3 @@ -95,6 +94,9 @@ dependencies: camera: ^0.10.0+4 + #Encryption + cryptography: ^2.0.5 + cryptography_flutter: ^2.0.2 video_player: ^2.4.7