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/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/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/widgets/image_picker.dart'; import 'package:signalr_netcore/signalr_client.dart'; import 'package:uuid/uuid.dart'; class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { ScrollController scrollController = ScrollController(); TextEditingController message = TextEditingController(); TextEditingController search = TextEditingController(); List userChatHistory = []; List? pChatHistory, searchedChats; late HubConnection hubConnection; L.Logger logger = L.Logger(); bool hubConInitialized = false; bool isLoading = true; bool isChatScreenActive = false; late File selectedFile; bool isFileSelected = false; String sFileType = ""; bool isMsgReply = false; List repliedMsg = []; List favUsersList = []; int paginationVal = 0; //Scroll bool _firstAutoscrollExecuted = false; bool _shouldAutoscroll = false; Future getUserAutoLoginToken() async { Response response = await ApiClient().postJsonForResponse( "${ApiConsts.chatServerBaseApiUrl}user/externaluserlogin", { "employeeNumber": int.parse( 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; } } 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); if (favUList.response != null) { favUsersList = favUList.response!; favUsersList.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); for (dynamic user in recentChat.response!) { for (dynamic favUser in favUList.response!) { if (user.id == favUser.id) { user.isFav = favUser.isFav; } } } } pChatHistory = recentChat.response == null ? [] : recentChat.response; if (pChatHistory != null) pChatHistory!.sort((ChatUser a, ChatUser b) => a.userName!.toLowerCase().compareTo(b.userName!.toLowerCase())); searchedChats = pChatHistory; isLoading = false; notifyListeners(); } Future GetUserChatHistoryNotDeliveredAsync(int userId) async { await hubConnection.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); return ""; } void getSingleUserChatHistory({required int senderUID, required int receiverUID, required bool loadMore, bool isNewChat = false}) async { isLoading = true; 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, ); if (response.statusCode == 204) { if (isNewChat) { userChatHistory = []; } else if (loadMore) { // userChatHistory = []; Utils.showToast("No More Data To Load"); } } else { if (loadMore) { List temp = getSingleUserChatModel(response.body).reversed.toList(); userChatHistory.addAll(temp); } else { userChatHistory = getSingleUserChatModel(response.body).reversed.toList(); } } isLoading = false; await GetUserChatHistoryNotDeliveredAsync(senderUID); notifyListeners(); } void updateUserChatHistoryStatusAsync(List data) { hubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); } List getSingleUserChatModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); ChatUserModel userToList(String str) => ChatUserModel.fromJson(json.decode(str)); 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(); if (response.statusCode == 200) { result = jsonDecode(await response.stream.bytesToString()); } else { result = []; } } catch (e) { if (kDebugMode) { print(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(); 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); } } void updateUserChatStatus(List? args) { dynamic items = args!.toList(); for (dynamic cItem in items[0]) { for (SingleUserChatModel chat in userChatHistory) { if (chat.userChatHistoryId.toString() == cItem["userChatHistoryId"].toString()) { chat.isSeen = cItem["isSeen"]; chat.isDelivered = cItem["isDelivered"]; notifyListeners(); } } } } void onChatSeen(List? args) { dynamic items = args!.toList(); logger.d("---------------------------------Chat Seen -------------------------------------"); logger.d(items); // for (var user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // notifyListeners(); } void userCountAsync(List? args) { dynamic items = args!.toList(); // logger.d(items); //logger.d("---------------------------------User Count Async -------------------------------------"); //logger.d(items); // for (var user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // notifyListeners(); } void updateChatHistoryWindow(List? args) { dynamic items = args!.toList(); print("---------------------------------Update Chat History Windows Async -------------------------------------"); logger.d(items); // for (var user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // notifyListeners(); } void chatNotDelivered(List? args) { dynamic items = args!.toList(); for (dynamic item in items[0]) { dynamic data = [ { "userChatHistoryId": item["userChatHistoryId"], "TargetUserId": item["targetUserId"], "isDelivered": true, "isSeen": true, } ]; updateUserChatHistoryStatusAsync(data); } logger.d(items); // for (var user in searchedChats!) { // if (user.id == items.first["id"]) { // user.userStatus = items.first["userStatus"]; // } // } // notifyListeners();2 } 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"]; } } notifyListeners(); } void filter(String value) async { List? tmp = []; if (value.isEmpty || value == "") { tmp = pChatHistory; } else { for (ChatUser element in pChatHistory!) { if (element.userName!.toLowerCase().contains(value.toLowerCase())) { tmp.add(element); } } } searchedChats = tmp; notifyListeners(); } Future onMsgReceived(List? parameters) async { print("msg Received"); List data = []; List temp = []; for (dynamic msg in parameters!) { data = getSingleUserChatModel(jsonEncode(msg)); temp = getSingleUserChatModel(jsonEncode(msg)); data.first.targetUserId = temp.first.currentUserId; data.first.targetUserName = temp.first.currentUserName; data.first.currentUserId = temp.first.targetUserId; data.first.currentUserName = temp.first.targetUserName; } 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; // } // }); logger.d(jsonEncode(data)); var list = [ { "userChatHistoryId": data.first.userChatHistoryId, "TargetUserId": data.first.targetUserId, "isDelivered": true, "isSeen": isChatScreenActive ? true : false, } ]; updateUserChatHistoryStatusAsync(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?; Future.delayed( const Duration(seconds: 2), () { user.isTyping = false; notifyListeners(); }, ); } } notifyListeners(); } int getFileType(String value) { switch (value) { case ".pdf": return 1; case ".png": return 3; case ".txt": return 5; case ".jpg": return 12; case ".jpeg": return 4; case ".xls": return 7; case ".xlsx": return 7; case ".doc": return 6; case ".docx": return 6; case ".ppt": return 8; case ".pptx": return 8; case ".zip": return 2; case ".rar": return 2; default: return 0; } } String getFileTypeDescription(String value) { switch (value) { case ".pdf": return "application/pdf"; case ".png": return "image/png"; case ".txt": return "text/plain"; case ".jpg": return "image/jpg"; case ".jpeg": return "image/jpeg"; case ".ppt": return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; case ".pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; case ".doc": return "application/vnd.openxmlformats-officedocument.wordprocessingm"; case ".docx": return "application/vnd.openxmlformats-officedocument.wordprocessingm"; case ".xls": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; case ".xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; case ".zip": return "application/octet-stream"; case ".rar": return "application/octet-stream"; default: return ""; } } Future sendChatToServer( {required int chatEventId, required fileTypeId, required int targetUserId, required String targetUserName, required chatReplyId, required bool isAttachment, required bool isReply}) async { Uuid uuid = const Uuid(); SingleUserChatModel data = SingleUserChatModel( chatEventId: chatEventId, chatSource: 1, contant: message.text, contantNo: uuid.v4(), conversationId: uuid.v4(), 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, ); userChatHistory.insert(0, data); isFileSelected = false; isMsgReply = false; sFileType = ""; message.clear(); 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":"${uuid.v4()}"}'; await hubConnection.invoke("AddChatUserAsync", args: [json.decode(chatData)]); } void sendChatMessage(int targetUserId, String targetUserName, BuildContext context) async { dynamic contain = searchedChats!.where((ChatUser element) => element.id == targetUserId); if (contain.isEmpty) { searchedChats!.add( ChatUser( id: targetUserId, userName: targetUserName, ), ); notifyListeners(); } if (!isFileSelected && !isMsgReply) { logger.d("Normal Text Message"); if (message.text == null || message.text.isEmpty) { return; } sendChatToServer(chatEventId: 1, fileTypeId: null, targetUserId: targetUserId, targetUserName: targetUserName, isAttachment: false, chatReplyId: null, isReply: false); } if (isFileSelected && !isMsgReply) { Utils.showLoading(context); logger.d("Normal Attachment Message"); 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: null, isReply: false); } if (!isFileSelected && isMsgReply) { logger.d("Normal Text Message With 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); } if (isFileSelected && isMsgReply) { logger.d("Attachment Message With Reply"); 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, ); } } void selectImageToUpload(BuildContext context) { ImageOptions.showImageOptionsNew(context, true, (String image, File file) async { if (checkFileSize(file.path)) { selectedFile = file; isFileSelected = true; sFileType = getFileExtension(file.path)!; message.text = file.path.split("/").last; Navigator.of(context).pop(); } else { Utils.showToast("Max 1 mb size is allowed to upload"); } notifyListeners(); }); } void removeAttachment() { isFileSelected = false; sFileType = ""; message.text = ''; notifyListeners(); } String? getFileExtension(String fileName) { try { return "." + fileName.split('.').last; } catch (e) { return null; } } bool checkFileSize(String path) { int fileSizeLimit = 1024; File f = File(path); double fileSizeInKB = f.lengthSync() / 1024; double fileSizeInMB = fileSizeInKB / 1024; if (fileSizeInKB > fileSizeLimit) { return false; } else { return true; } } String getType(String type) { switch (type) { case ".pdf": return "assets/images/pdf.svg"; case ".png": return "assets/images/png.svg"; case ".txt": return "assets/icons/chat/txt.svg"; case ".jpg": return "assets/images/jpg.svg"; case ".jpeg": return "assets/images/jpg.svg"; case ".xls": return "assets/icons/chat/xls.svg"; case ".xlsx": return "assets/icons/chat/xls.svg"; case ".doc": return "assets/icons/chat/doc.svg"; case ".docx": return "assets/icons/chat/doc.svg"; case ".ppt": return "assets/icons/chat/ppt.svg"; case ".pptx": return "assets/icons/chat/ppt.svg"; case ".zip": return "assets/icons/chat/zip.svg"; case ".rar": return "assets/icons/chat/zip.svg"; default: return "assets/images/thumb.svg"; } } void chatReply(SingleUserChatModel data) { repliedMsg = []; data.isReplied = true; isMsgReply = true; repliedMsg.add(data); notifyListeners(); } void closeMe() { repliedMsg = []; isMsgReply = false; notifyListeners(); } String dateFormte(DateTime data) { DateFormat f = DateFormat('hh:mm a dd MMM yyyy'); f.format(data); return f.format(data); } 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); if (favoriteChatUser.response != null) { for (ChatUser user in searchedChats!) { if (user.id == favoriteChatUser.response!.targetUserId!) { user.isFav = favoriteChatUser.response!.isFav; favUsersList.add(user); } } } notifyListeners(); } 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); if (favoriteChatUser.response != null) { for (var user in searchedChats!) { if (user.id == favoriteChatUser.response!.targetUserId!) { user.isFav = favoriteChatUser.response!.isFav; } } favUsersList.removeWhere((ChatUser element) => element.id == targetUserID); } notifyListeners(); } void clearSelections() { searchedChats = pChatHistory; search.clear(); isChatScreenActive = false; paginationVal = 0; message.text = ''; isFileSelected = false; repliedMsg = []; sFileType = ""; notifyListeners(); } void clearAll() { searchedChats = pChatHistory; search.clear(); isChatScreenActive = false; paginationVal = 0; message.text = ''; isFileSelected = false; repliedMsg = []; sFileType = ""; } void scrollListener() { _firstAutoscrollExecuted = true; if (scrollController.hasClients && scrollController.position.pixels == scrollController.position.maxScrollExtent) { _shouldAutoscroll = true; } else { _shouldAutoscroll = false; } } void scrollToBottom() { scrollController.animateTo( scrollController.position.maxScrollExtent + 100, duration: const Duration(milliseconds: 500), curve: Curves.easeIn, ); } void msgScroll() { scrollController.animateTo( scrollController.position.minScrollExtent - 100, duration: const Duration(milliseconds: 500), curve: Curves.easeIn, ); } }