From b190a248bfc72b12b066ed1900ccc86bacc18bc3 Mon Sep 17 00:00:00 2001 From: "Aamir.Muhammad" Date: Thu, 13 Oct 2022 16:37:04 +0300 Subject: [PATCH] Chat Implementation Version 1 --- assets/icons/chat/chat.svg | 24 ++ assets/icons/chat/chat_active_icon.svg | 14 + assets/icons/chat/chat_send_icon.svg | 3 + assets/langs/ar-SA.json | 9 +- assets/langs/en-US.json | 5 +- lib/api/chat/chat_provider_model.dart | 213 ++++++++++++++ lib/classes/colors.dart | 2 + lib/classes/consts.dart | 18 +- lib/classes/utils.dart | 11 + lib/config/routes.dart | 10 + lib/generated/locale_keys.g.dart | 3 + lib/main.dart | 2 + .../chat/get_search_user_chat_model.dart | 82 ++++++ .../chat/get_single_user_chat_list_Model.dart | 119 ++++++++ lib/ui/chat/chat_bubble.dart | 88 ++++++ lib/ui/chat/chat_detailed_screen.dart | 86 ++++++ lib/ui/chat/chat_home.dart | 175 +++++++++++ lib/ui/landing/dashboard_screen.dart | 277 ++++++++++++++---- lib/widgets/app_bar_widget.dart | 23 +- .../search_employee_bottom_sheet.dart | 42 ++- .../shimmer/dashboard_shimmer_widget.dart | 67 +++++ pubspec.yaml | 7 + 22 files changed, 1197 insertions(+), 83 deletions(-) create mode 100644 assets/icons/chat/chat.svg create mode 100644 assets/icons/chat/chat_active_icon.svg create mode 100644 assets/icons/chat/chat_send_icon.svg create mode 100644 lib/api/chat/chat_provider_model.dart create mode 100644 lib/models/chat/get_search_user_chat_model.dart create mode 100644 lib/models/chat/get_single_user_chat_list_Model.dart create mode 100644 lib/ui/chat/chat_bubble.dart create mode 100644 lib/ui/chat/chat_detailed_screen.dart create mode 100644 lib/ui/chat/chat_home.dart diff --git a/assets/icons/chat/chat.svg b/assets/icons/chat/chat.svg new file mode 100644 index 0000000..29d4471 --- /dev/null +++ b/assets/icons/chat/chat.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/chat/chat_active_icon.svg b/assets/icons/chat/chat_active_icon.svg new file mode 100644 index 0000000..958d830 --- /dev/null +++ b/assets/icons/chat/chat_active_icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/chat/chat_send_icon.svg b/assets/icons/chat/chat_send_icon.svg new file mode 100644 index 0000000..6f1de72 --- /dev/null +++ b/assets/icons/chat/chat_send_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 3f1461b..1cf5730 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -394,7 +394,7 @@ "uploadFromGallery": "تحميل من\nملفات الجهاز", "name": "الأسم", "email": "ايميل", - "noHistoryAvailable":"لايوجد سجل بيانات سابقة ", + "noHistoryAvailable": "لايوجد سجل بيانات سابقة ", "purchaseRequisition": "طلب شراء", "moveOrder": "طلب تغيير", "humanResource": "الموارد البشريه", @@ -405,7 +405,7 @@ "addFavoriteList": "هل تريد اضافة {name} لقائمة المفضله", "feedbackUserExperience": "هذا للحصول على تعليقات حول تجربة المستخدم", "rateUI": ".1 كيف تريد تقييم التطبيق", - "submitSurvey":"ارسال الاستبيان", + "submitSurvey": "ارسال الاستبيان", "typeHere": "اكتب هنا", "profile": { "reset_password": { @@ -439,5 +439,8 @@ "female": "Hello girl :) {}" } }, - "reset_locale": "Reset Language" + "reset_locale": "Reset Language", + "chat": "دردشة", + "mychats": "دردشاتي", + "createNewChat": "Create New Chat" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 23b7d3e..c4453dd 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -439,5 +439,8 @@ "female": "Hello girl :) {}" } }, - "reset_locale": "Reset Language" + "reset_locale": "Reset Language", + "chat": "Chat", + "mychats": "My Chats", + "createNewChat": "Create New Chat" } \ No newline at end of file diff --git a/lib/api/chat/chat_provider_model.dart b/lib/api/chat/chat_provider_model.dart new file mode 100644 index 0000000..1b8c8b4 --- /dev/null +++ b/lib/api/chat/chat_provider_model.dart @@ -0,0 +1,213 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:http/http.dart'; +import 'package:logging/logging.dart'; +import 'package:mohem_flutter_app/api/api_client.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:signalr_netcore/hub_connection.dart'; +import 'package:signalr_netcore/signalr_client.dart'; +import 'package:logger/logger.dart' as L; + +class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { + List userChatHistory = []; + List? pChatHistory, searchedChats; + late HubConnection hubConnection; + L.Logger logger = L.Logger(); + TextEditingController message = TextEditingController(); + ScrollController scrollController = ScrollController(); + static String token = + "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiI0MjA2MiIsImVtYWlsIjoiYWFtaXIubXVoYW1tYWRAY2xvdWRzb2x1dGlvbnMuY29tLnNhIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy91c2VyZGF0YSI6ImFhbWlyLm11aGFtbWFkIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbW9iaWxlcGhvbmUiOiI5NjY1MzA4OTYwMTgiLCJuYmYiOjE2NjU2NDE4MTIsImV4cCI6MTY2NTcyODIxMiwiaWF0IjoxNjY1NjQxODEyfQ.dLMt_c0zXYYLvZtvK_5F2NrdgERlVXZSQpjZwMUWl7rsjZWMs03n1lVU_bWxvs5C_4luSlumwFNq1jSV9iunbQ"; + + bool isLoading = true; + + void getChatMemberFromSearch(String sName, int cUserId) async { + isLoading = true; + notifyListeners(); + Response response = await ApiClient().getJsonForResponse( + "${ApiConsts.chatSearchMember}$sName/$cUserId", + token: token, + ); + isLoading = false; + notifyListeners(); + } + + void getUserRecentChats() async { + Response response = await ApiClient().getJsonForResponse( + "${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatRecentUrl}", + token: token, + ); + ChatUserModel recentChat = userToList(response.body); + pChatHistory = recentChat.response; + searchedChats = pChatHistory; + isLoading = false; + notifyListeners(); + } + + void getSingleUserChatHistory({required String senderUID, required int receiverUID, required String pagination}) async { + isLoading = true; + Response response = await ApiClient().getJsonForResponse( + "${ApiConsts.chatServerBaseApiUrl}${ApiConsts.chatSingleUserHistoryUrl}/$senderUID/$receiverUID/$pagination", + token: token, + ); + userChatHistory = getSingleUserChatintoModel(response.body); + isLoading = false; + logger.d(jsonEncode(userChatHistory)); + notifyListeners(); + } + + List getSingleUserChatintoModel(String str) => List.from(json.decode(str).map((x) => SingleUserChatModel.fromJson(x))); + + ChatUserModel userToList(String str) => ChatUserModel.fromJson(json.decode(str)); + + void buildHubConnection() async { + HttpConnectionOptions httpOp = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); + hubConnection = await HubConnectionBuilder() + .withUrl(ApiConsts.chatHubConnectionUrl + "?UserId=42062&source=Web&access_token=$token", options: httpOp) + .withAutomaticReconnect(retryDelays: [2000, 5000, 10000, 20000]) + .configureLogging(Logger("Logs Enabled")) + .build(); + hubConnection.onclose( + ({Exception? error}) { + logger.d(error); + }, + ); + hubConnection.onreconnecting( + ({Exception? error}) { + logger.d(error); + logger.d("Reconnecting"); + }, + ); + hubConnection.onreconnected( + ({String? connectionId}) { + logger.d("Reconnected"); + }, + ); + if (hubConnection.state != HubConnectionState.Connected) { + await hubConnection.start(); + hubConnection.on("OnUpdateUserStatusAsync", changeStatus); + hubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); + // hubConnection.on("OnUserTypingAsync", onUserTyping); + //hubConnection.on("OnUserTypingAsync", changeTypingStatus); + } else { + hubConnection.on("OnUpdateUserStatusAsync", changeStatus); + hubConnection.on("OnDeliveredChatUserAsync", onMsgReceived); + // hubConnection.on("OnUserTypingAsync", onUserTyping); + //hubConnection.on("OnUserTypingAsync", changeTypingStatus); + } + isLoading = false; + notifyListeners(); + } + + void changeStatus(List? args) { + List items = args!.toList(); + for (var 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 (var element in pChatHistory!) { + if (element.userName!.toLowerCase().contains(value.toLowerCase())) { + tmp.add(element); + } + } + } + searchedChats = tmp; + notifyListeners(); + } + + Future onMsgReceived(List? parameters) async { + List data = []; + for (dynamic msg in parameters!) { + data = getSingleUserChatintoModel(jsonEncode(msg)); + logger.d(msg); + } + userChatHistory.add(data.first); + notifyListeners(); + scrollDown(); + } + + 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?; + } else { + Future.delayed( + const Duration(milliseconds: 500), + () { + user.isTyping = false; + }, + ); + } + } + notifyListeners(); + } + + void sendChatMessage(String chatMessage, int targetUserId, String targetUserName) async { + if (chatMessage == null || chatMessage.isEmpty) { + return; + } + String chatData = + '{"contant":"$chatMessage","contantNo":"8a129295-36d7-7185-5d34-cc4eec7bcba4","chatEventId":1,"fileTypeId":null,"currentUserId":42062,"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"conversationId":"715f8b13-96ee-cd36-cb07-5a982a219982"}'; + await hubConnection.invoke("AddChatUserAsync", args: [json.decode(chatData)]); + userChatHistory.add( + SingleUserChatModel( + chatEventId: 1, + chatSource: 1, + contant: chatMessage, + contantNo: "8a129295-36d7-7185-5d34-cc4eec7bcba4", + conversationId: "715f8b13-96ee-cd36-cb07-5a982a219982", + createdDate: DateTime.now(), + currentUserId: 42062, + currentUserName: "aamir.muhammad", + targetUserId: targetUserId, + targetUserName: targetUserName, + ), + ); + message.clear(); + notifyListeners(); + scrollDown(); + } + + void scrollDown() { + scrollController.animateTo( + scrollController.position.maxScrollExtent + 100, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 300), + ); + + // scrollController.animateTo(double.parse(userChatHistory.length.toString()), duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn); + + notifyListeners(); + } + +// void _scrollListener() { +// if (scrollController.position.extentAfter.toInt() <= 0 && canCallApi) { +// if (userChatHistory.length < _ayatTangheemTypeMapped.totalItemsCount) { +// currentPageNo++; +// if (widget.tangheemQuery == null) { +// getTangheemData(); +// } else { +// getTangheemDataByKeyword(); +// } +// } +// canCallApi = false; +// } +// } + +} diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart index 70e46da..82fd46f 100644 --- a/lib/classes/colors.dart +++ b/lib/classes/colors.dart @@ -9,6 +9,7 @@ class MyColors { static const Color gradiantEndColor = Color(0xff259db7); static const Color textMixColor = Color(0xff2BB8A6); static const Color backgroundColor = Color(0xffF8F8F8); + static const Color grey41Color = Color(0xff414141); static const Color grey57Color = Color(0xff575757); static const Color grey67Color = Color(0xff676767); static const Color grey77Color = Color(0xff777777); @@ -54,4 +55,5 @@ class MyColors { static const Color green2DColor = Color(0xff32D892); static const Color greyC4Color = Color(0xffC4C4C4); static const Color grey35Color = Color(0xff535353); + static const Color grey9DColor = Color(0xff9D9D9D); } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 564a61f..7609fdf 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,7 +1,9 @@ class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server - static String baseUrl = "https://hmgwebservices.com"; // Live server + static String baseUrl = "https://uat.hmgwebservices.com"; // UAT server + //15153 + //Abcd@12345 + //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/"; @@ -9,6 +11,16 @@ class ApiConsts { static String swpRest = baseUrlServices + "SWP.svc/REST/"; static String user = baseUrlServices + "api/User/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/"; + + 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"; +// 42062 is CurrentUserID and 36745 is targetUserID and 0 is For Pagination +// static String chatSearchMember = "https://apiderichat.hmg.com/api/user/getUserWithStatusAndFavAsync/aamir.muhammad/36239"; + } class SharedPrefsConsts { @@ -23,4 +35,4 @@ class SharedPrefsConsts { static String mohemmWifiSSID = "mohemmWifiSSID"; static String mohemmWifiPassword = "mohemmWifiPassword"; static String editItemForSale = "editItemForSale"; -} \ No newline at end of file +} diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 94bcc18..615cb60 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -137,6 +137,17 @@ class Utils { ).center; } + static Widget getNoChatWidget(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset('assets/images/not_found.svg', width: 110.0, height: 110.0), + LocaleKeys.noDataAvailable.tr().toText16().paddingOnly(top: 15), + ], + ).center; + } + static Uint8List getPostBytes(img) { try { var b64 = img.replaceFirst('data:image/png;base64,', ''); diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 099342b..e61e056 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -3,6 +3,8 @@ import 'package:mohem_flutter_app/ui/attendance/add_vacation_rule_screen.dart'; import 'package:mohem_flutter_app/ui/attendance/monthly_attendance_screen.dart'; import 'package:mohem_flutter_app/ui/attendance/vacation_rule_screen.dart'; import 'package:mohem_flutter_app/ui/bottom_sheets/attendence_details_bottom_sheet.dart'; +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/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/survey_screen.dart'; import 'package:mohem_flutter_app/ui/landing/today_attendance_screen.dart'; @@ -162,6 +164,10 @@ class AppRoutes { static const String changePassword = "/changePassword"; + //Chat + static const String chat = "/chat"; + static const String chatDetailed = "/chatDetailed"; + static final Map routes = { login: (context) => LoginScreen(), verifyLogin: (context) => VerifyLoginScreen(), @@ -255,5 +261,9 @@ class AppRoutes { subordinateLeave: (context) => SubordinateLeave(), changePassword: (context) => ChangePasswordScreen(), + + //Chat + chat: (context) => ChatHomeScreen(), + chatDetailed: (context) => ChatDetailScreen(), }; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 0934ff4..8864ea1 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -426,5 +426,8 @@ abstract class LocaleKeys { static const gender_with_arg = 'gender.with_arg'; static const gender = 'gender'; static const reset_locale = 'reset_locale'; + static const chat = 'chat'; + static const mychats = 'mychats'; + static const createNewChat = 'createNewChat'; } diff --git a/lib/main.dart b/lib/main.dart index d5869fb..0048020 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +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/app_state/app_state.dart'; import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/generated/codegen_loader.g.dart'; @@ -43,6 +44,7 @@ Future main() async { providers: [ ChangeNotifierProvider(create: (_) => DashboardProviderModel()), ChangeNotifierProvider(create: (_) => EITProviderModel()), + ChangeNotifierProvider(create: (_) => ChatProviderModel()) ], child: MyApp(), ), diff --git a/lib/models/chat/get_search_user_chat_model.dart b/lib/models/chat/get_search_user_chat_model.dart new file mode 100644 index 0000000..ceee0de --- /dev/null +++ b/lib/models/chat/get_search_user_chat_model.dart @@ -0,0 +1,82 @@ +class ChatUserModel { + ChatUserModel({ + this.response, + this.errorResponses, + }); + + List? response; + dynamic errorResponses; + + factory ChatUserModel.fromJson(Map json) => ChatUserModel( + response: json["response"] == null ? null : List.from(json["response"].map((x) => ChatUser.fromJson(x))), + errorResponses: json["errorResponses"], + ); + + Map toJson() => { + "response": response == null ? null : List.from(response!.map((x) => x.toJson())), + "errorResponses": errorResponses, + }; +} + +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, + }); + + int? id; + String? userName; + String? email; + dynamic? phone; + dynamic? title; + int? userStatus; + dynamic? image; + int? unreadMessageCount; + dynamic? userAction; + bool? isPin; + bool? isFav; + bool? isAdmin; + bool? isTyping; + + factory ChatUser.fromJson(Map json) => ChatUser( + id: json["id"] == null ? null : json["id"], + userName: json["userName"] == null ? null : json["userName"], + email: json["email"] == null ? null : json["email"], + phone: json["phone"], + title: json["title"], + userStatus: json["userStatus"] == null ? null : json["userStatus"], + image: json["image"], + unreadMessageCount: json["unreadMessageCount"] == null ? null : json["unreadMessageCount"], + userAction: json["userAction"], + isPin: json["isPin"] == null ? null : json["isPin"], + isFav: json["isFav"] == null ? null : json["isFav"], + isAdmin: json["isAdmin"] == null ? null : json["isAdmin"], + isTyping: false, + ); + + Map toJson() => { + "id": id == null ? null : id, + "userName": userName == null ? null : userName, + "email": email == null ? null : email, + "phone": phone, + "title": title, + "userStatus": userStatus == null ? null : userStatus, + "image": image, + "unreadMessageCount": unreadMessageCount == null ? null : unreadMessageCount, + "userAction": userAction, + "isPin": isPin == null ? null : isPin, + "isFav": isFav == null ? null : isFav, + "isAdmin": isAdmin == null ? null : isAdmin, + }; +} diff --git a/lib/models/chat/get_single_user_chat_list_Model.dart b/lib/models/chat/get_single_user_chat_list_Model.dart new file mode 100644 index 0000000..0e3cb22 --- /dev/null +++ b/lib/models/chat/get_single_user_chat_list_Model.dart @@ -0,0 +1,119 @@ +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, + }); + + int? userChatHistoryId; + int? userChatHistoryLineId; + String? contant; + String? contantNo; + int? currentUserId; + String? currentUserName; + int? targetUserId; + String? targetUserName; + dynamic encryptedTargetUserId; + dynamic encryptedTargetUserName; + int? chatEventId; + dynamic fileTypeId; + bool? isSeen; + bool? isDelivered; + DateTime? createdDate; + int? chatSource; + String? conversationId; + FileTypeResponse? fileTypeResponse; + dynamic userChatReplyResponse; + + 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"], + encryptedTargetUserName: 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"], + ); + + Map toJson() => { + "userChatHistoryId": userChatHistoryId == null ? null : userChatHistoryId, + "userChatHistoryLineId": userChatHistoryLineId == null ? null : userChatHistoryLineId, + "contant": contant == null ? null : contant, + "contantNo": contantNo == null ? null : contantNo, + "currentUserId": currentUserId == null ? null : currentUserId, + "currentUserName": currentUserName == null ? null : currentUserName, + "targetUserId": targetUserId == null ? null : targetUserId, + "targetUserName": targetUserName == null ? null : targetUserName, + "encryptedTargetUserId": encryptedTargetUserId, + "encryptedTargetUserName": encryptedTargetUserName, + "chatEventId": chatEventId == null ? null : chatEventId, + "fileTypeId": fileTypeId, + "isSeen": isSeen == null ? null : isSeen, + "isDelivered": isDelivered == null ? null : isDelivered, + "createdDate": createdDate == null ? null : createdDate!.toIso8601String(), + "chatSource": chatSource == null ? null : chatSource, + "conversationId": conversationId == null ? null : conversationId, + "fileTypeResponse": fileTypeResponse == null ? null : fileTypeResponse!.toJson(), + "userChatReplyResponse": userChatReplyResponse, + }; +} + +class FileTypeResponse { + FileTypeResponse({ + this.fileTypeId, + this.fileTypeName, + this.fileTypeDescription, + this.fileKind, + this.fileName, + }); + + int? fileTypeId; + dynamic fileTypeName; + dynamic fileTypeDescription; + dynamic fileKind; + dynamic fileName; + + factory FileTypeResponse.fromJson(Map json) => FileTypeResponse( + fileTypeId: json["fileTypeId"] == null ? null : json["fileTypeId"], + fileTypeName: json["fileTypeName"], + fileTypeDescription: json["fileTypeDescription"], + fileKind: json["fileKind"], + fileName: json["fileName"], + ); + + Map toJson() => { + "fileTypeId": fileTypeId == null ? null : fileTypeId, + "fileTypeName": fileTypeName, + "fileTypeDescription": fileTypeDescription, + "fileKind": fileKind, + "fileName": fileName, + }; +} diff --git a/lib/ui/chat/chat_bubble.dart b/lib/ui/chat/chat_bubble.dart new file mode 100644 index 0000000..74d2e2b --- /dev/null +++ b/lib/ui/chat/chat_bubble.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.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'; + +class ChatBubble extends StatelessWidget { + const ChatBubble( + {Key? key, + required this.text, + required this.isCurrentUser, + required this.isSeen, + required this.isDelivered, + required this.dateTime}) + : super(key: key); + final String text; + final bool isCurrentUser; + final bool isSeen; + final bool isDelivered; + final String dateTime; + + @override + Widget build(BuildContext context) { + return Padding( + // asymmetric padding + padding: EdgeInsets.fromLTRB( + isCurrentUser ? 64.0 : 16.0, + 4, + isCurrentUser ? 16.0 : 64.0, + 4, + ), + child: Align( + // align the child within the container + alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft, + child: DecoratedBox( + // chat bubble decoration + decoration: BoxDecoration( + color: Colors.white, + gradient: isCurrentUser + ? null + : LinearGradient( + transform: GradientRotation(.46), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ]), + borderRadius: BorderRadius.circular(10), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + text.toText12( + color: + isCurrentUser ? MyColors.grey57Color : MyColors.white), + 8.height, + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + dateTime.toText12( + color: isCurrentUser + ? MyColors.grey41Color.withOpacity(.5) + : Colors.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, + ) + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/ui/chat/chat_detailed_screen.dart b/lib/ui/chat/chat_detailed_screen.dart new file mode 100644 index 0000000..17ba325 --- /dev/null +++ b/lib/ui/chat/chat_detailed_screen.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +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/classes/colors.dart'; +import 'package:mohem_flutter_app/ui/chat/chat_bubble.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'; + +class ChatDetailScreen extends StatelessWidget { + dynamic userDetails; + late ChatProviderModel data; + + ChatDetailScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + userDetails = ModalRoute.of(context)!.settings.arguments; + data = Provider.of(context, listen: false); + data.getSingleUserChatHistory(senderUID: "42062", receiverUID: userDetails["targetUser"].id, pagination: "0"); + Timer(const Duration(seconds: 1), () => data.scrollDown()); + return Scaffold( + backgroundColor: const Color(0xFFF8F8F8), + appBar: AppBarWidget(context, title: userDetails["targetUser"].userName, showHomeButton: false, image: userDetails["targetUser"].image), + body: Consumer( + builder: (BuildContext context, ChatProviderModel m, Widget? child) { + return (m.isLoading + ? ChatHomeShimmer() + : Column( + children: [ + Expanded( + child: ListView.builder( + controller: m.scrollController, + shrinkWrap: true, + itemCount: m.userChatHistory.length, + padding: const EdgeInsets.symmetric(vertical: 10), + itemBuilder: (BuildContext context, int i) { + return ChatBubble( + text: m.userChatHistory[i].contant.toString(), + isSeen: m.userChatHistory[i].isSeen == true ? true : false, + isCurrentUser: m.userChatHistory[i].currentUserId == 42062 ? true : false, + isDelivered: m.userChatHistory[i].currentUserId == 42062 && m.userChatHistory[i].isDelivered == true ? true : false, + dateTime: m.userChatHistory[i].createdDate.toString(), + ); + }, + ), + ), + Card( + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TextField( + controller: m.message, + decoration: InputDecoration( + hintText: 'Type here to reply', + hintStyle: const TextStyle(color: MyColors.grey98Color), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + suffixIcon: IconButton( + icon: SvgPicture.asset( + "assets/icons/chat/chat_send_icon.svg", + height: 26, + width: 35, + ), + onPressed: () { + m.sendChatMessage(m.message.text, userDetails["targetUser"].id, userDetails["targetUser"].userName); + }, + ), + ), + ), + ), + ), + ], + )); + }, + ), + ); + } +} diff --git a/lib/ui/chat/chat_home.dart b/lib/ui/chat/chat_home.dart new file mode 100644 index 0000000..236a73f --- /dev/null +++ b/lib/ui/chat/chat_home.dart @@ -0,0 +1,175 @@ +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/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/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.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'; +import 'package:provider/provider.dart'; + +class ChatHomeScreen extends StatefulWidget { + const ChatHomeScreen({Key? key}) : super(key: key); + + @override + State createState() => _ChatHomeScreenState(); +} + +class _ChatHomeScreenState extends State { + TextEditingController search = new TextEditingController(); + late ChatProviderModel data; + + @override + void initState() { + super.initState(); + data = Provider.of(context, listen: false); + data.buildHubConnection(); + data.getUserRecentChats(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBarWidget(context, title: "My Chats", showHomeButton: false), + body: Consumer(builder: (BuildContext context, ChatProviderModel m, Widget? child) { + return m.isLoading + ? ChatHomeShimmer() + : ListView( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: TextField( + onChanged: (String val) { + m.filter(val); + }, + decoration: InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color(0xFFE5E5E5), + ), + ), + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + hintText: "Search from chat", + hintStyle: const TextStyle(color: MyColors.lightTextColor, fontStyle: FontStyle.italic), + filled: true, + fillColor: const Color(0xFFF7F7F7), + ), + ), + ), + if (m.searchedChats != null) + ListView.separated( + itemCount: m.searchedChats!.length, + padding: const EdgeInsets.only(top: 0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return 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), + ), + ), + ), + ) + ], + ), + title: (m.searchedChats![index].userName ?? "").toText14(color: MyColors.darkTextColor), + subtitle: (m.searchedChats![index].isTyping == true ? "Something is Typing" : "Last message text").toText11(color: MyColors.normalTextColor), + trailing: ("Today").toText10(color: MyColors.lightTextColor), + minVerticalPadding: 0, + onTap: () { + Navigator.pushNamed( + context, + AppRoutes.chatDetailed, + arguments: {"currentUser": "42062", "targetUser": m.searchedChats![index]}, + ); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) => const Padding( + padding: EdgeInsets.only(right: 10, left: 70), + child: Divider( + color: Color(0xFFE5E5E5), + ), + ), + ), + // if (searchedUsersList == null) Utils.getNoChatWidget(context), + ], + ); + }), + floatingActionButton: FloatingActionButton( + child: Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + transform: GradientRotation(.46), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + MyColors.gradiantEndColor, + MyColors.gradiantStartColor, + ], + ), + ), + child: const Icon( + Icons.add, + size: 30, + color: MyColors.white, + ), + ), + onPressed: () async { + // var userData = await ChatApiClient() + // .getChatMemberFromSearch("aamir.muhammad", 36239); + showMyBottomSheet( + context, + child: SearchEmployeeBottomSheet( + title: LocaleKeys.searchForEmployee.tr(), + apiMode: LocaleKeys.delegate.tr(), + onSelectEmployee: (_selectedEmployee) { + // Navigator.pop(context); + // selectedReplacementEmployee = _selectedEmployee; + setState(() {}); + }, + ), + ); + }, + ), + ); + } +} diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 1d8373e..19b1637 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -110,7 +110,9 @@ class _DashboardScreenState extends State { top: 0, child: Container( padding: const EdgeInsets.only(left: 5, right: 5), - decoration: BoxDecoration(color: MyColors.redColor, borderRadius: BorderRadius.circular(17)), + decoration: BoxDecoration( + color: MyColors.redColor, + borderRadius: BorderRadius.circular(17)), child: "3".toText12(color: Colors.white), ), ) @@ -151,7 +153,7 @@ class _DashboardScreenState extends State { child: Stack( alignment: Alignment.center, children: [ - if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), + if (model.isTimeRemainingInSeconds == 0)SvgPicture.asset("assets/images/thumb.svg"), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -161,65 +163,139 @@ class _DashboardScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), - if (model.isTimeRemainingInSeconds == 0) "01-02-2022".toText12(color: Colors.white), + if (model.isTimeRemainingInSeconds == 0)"01-02-2022".toText12(color: Colors.white), if (model.isTimeRemainingInSeconds != 0) Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: + MainAxisSize + .min, + crossAxisAlignment: + CrossAxisAlignment + .start, children: [ 9.height, CountdownTimer( - endTime: model.endTime, + endTime: model + .endTime, onEnd: null, - endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), - textStyle: TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + endWidget: "00:00:00".toText14( + color: Colors + .white, + isBold: + true), + textStyle: TextStyle( + color: Colors + .white, + fontSize: + 14, + letterSpacing: + -0.48, + fontWeight: + FontWeight.bold), ), - LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), + LocaleKeys + .timeLeftToday + .tr() + .toText12( + color: + Colors.white), 9.height, ClipRRect( - borderRadius: BorderRadius.all( - Radius.circular(20), + borderRadius: + BorderRadius + .all( + Radius.circular( + 20), ), - child: LinearProgressIndicator( - value: model.progress, - minHeight: 8, - valueColor: const AlwaysStoppedAnimation(Colors.white), - backgroundColor: const Color(0xff196D73), + child: + LinearProgressIndicator( + value: model + .progress, + minHeight: + 8, + valueColor: const AlwaysStoppedAnimation< + Color>( + Colors + .white), + backgroundColor: + const Color( + 0xff196D73), ), ), ], ), ], - ).paddingOnly(top: 12, right: 15, left: 12), + ).paddingOnly( + top: 12, + right: 15, + left: 12), ), Row( children: [ Expanded( child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: + MainAxisSize + .min, + crossAxisAlignment: + CrossAxisAlignment + .start, children: [ - LocaleKeys.checkIn.tr().toText12(color: Colors.white), - (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn) + LocaleKeys + .checkIn + .tr() + .toText12( + color: Colors + .white), + (model.attendanceTracking!.pSwipeIn == + null + ? "--:--" + : model + .attendanceTracking! + .pSwipeIn) .toString() - .toText14(color: Colors.white, isBold: true), + .toText14( + color: Colors + .white, + isBold: + true), 4.height, ], - ).paddingOnly(left: 12), + ).paddingOnly( + left: 12), ), Container( width: 45, height: 45, - padding: const EdgeInsets.only(left: 14, right: 14), - decoration: const BoxDecoration( - color: Color(0xff259EA4), - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(15), + padding: + const EdgeInsets + .only( + left: 14, + right: 14), + decoration: + const BoxDecoration( + color: Color( + 0xff259EA4), + borderRadius: + BorderRadius + .only( + bottomRight: + Radius + .circular( + 15), ), ), - child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/play.svg" : "assets/images/stop.svg"), + child: SvgPicture.asset( + model.isTimeRemainingInSeconds == + 0 + ? "assets/images/play.svg" + : "assets/images/stop.svg"), ).onPress(() { - showMyBottomSheet(context, child: MarkAttendanceWidget(model)); + showMyBottomSheet( + context, + child: + MarkAttendanceWidget( + model)); }), ], ), @@ -229,7 +305,8 @@ class _DashboardScreenState extends State { ), ).onPress( () { - Navigator.pushNamed(context, AppRoutes.todayAttendance); + Navigator.pushNamed(context, + AppRoutes.todayAttendance); }, )) .animatedSwither(); @@ -252,8 +329,11 @@ class _DashboardScreenState extends State { padding: const EdgeInsets.only(top: 31), decoration: BoxDecoration( color: Colors.white, - borderRadius: const BorderRadius.only(topRight: Radius.circular(50), topLeft: Radius.circular(50)), - border: Border.all(color: MyColors.lightGreyEDColor, width: 1), + borderRadius: const BorderRadius.only( + topRight: Radius.circular(50), + topLeft: Radius.circular(50)), + border: Border.all( + color: MyColors.lightGreyEDColor, width: 1), ), child: Column( mainAxisSize: MainAxisSize.min, @@ -269,22 +349,32 @@ class _DashboardScreenState extends State { LocaleKeys.offers.tr().toText12(), Row( children: [ - LocaleKeys.discounts.tr().toText24(isBold: true), + LocaleKeys.discounts + .tr() + .toText24(isBold: true), 6.width, Container( - padding: const EdgeInsets.only(left: 8, right: 8), + padding: const EdgeInsets.only( + left: 8, right: 8), decoration: BoxDecoration( color: MyColors.yellowColor, - borderRadius: BorderRadius.circular(10), + borderRadius: + BorderRadius.circular(10), ), - child: LocaleKeys.newString.tr().toText10(isBold: true)), + child: LocaleKeys.newString + .tr() + .toText10(isBold: true)), ], ), ], ), ), - LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() { - Navigator.pushNamed(context, AppRoutes.offersAndDiscounts); + LocaleKeys.viewAllOffers + .tr() + .toText12(isUnderLine: true) + .onPress(() { + Navigator.pushNamed( + context, AppRoutes.offersAndDiscounts); }) ], ).paddingOnly(left: 21, right: 21), @@ -295,38 +385,56 @@ class _DashboardScreenState extends State { child: ListView.separated( shrinkWrap: true, physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(left: 21, right: 21, top: 13), + padding: const EdgeInsets.only( + left: 21, right: 21, top: 13), scrollDirection: Axis.horizontal, itemBuilder: (cxt, index) { return model.isOffersLoading ? const OffersShimmerWidget() : InkWell( onTap: () { - navigateToDetails(data.getOffersList[index]); + navigateToDetails( + data.getOffersList[index]); }, child: SizedBox( width: 73, child: Column( - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, children: [ Container( width: 73, height: 73, decoration: BoxDecoration( - borderRadius: const BorderRadius.all( + borderRadius: + const BorderRadius + .all( Radius.circular(100), ), - border: Border.all(color: MyColors.lightGreyE3Color, width: 1), + border: Border.all( + color: MyColors + .lightGreyE3Color, + width: 1), ), child: ClipRRect( - borderRadius: const BorderRadius.all( + borderRadius: + const BorderRadius + .all( Radius.circular(50), ), child: Hero( - tag: "ItemImage" + data.getOffersList[index].rowID!, - transitionOnUserGestures: true, + tag: "ItemImage" + + data + .getOffersList[ + index] + .rowID!, + transitionOnUserGestures: + true, child: Image.network( - data.getOffersList[index].bannerImage!, + data + .getOffersList[ + index] + .bannerImage!, fit: BoxFit.contain, ), ), @@ -334,9 +442,22 @@ class _DashboardScreenState extends State { ), 4.height, Expanded( - child: AppState().isArabic(context) - ? data.getOffersList[index].titleAR!.toText12(isCenter: true, maxLine: 1) - : data.getOffersList[index].title!.toText12(isCenter: true, maxLine: 1), + child: AppState() + .isArabic(context) + ? data + .getOffersList[ + index] + .titleAR! + .toText12( + isCenter: true, + maxLine: 1) + : data + .getOffersList[ + index] + .title! + .toText12( + isCenter: true, + maxLine: 1), ), ], ), @@ -367,14 +488,18 @@ class _DashboardScreenState extends State { BottomNavigationBarItem( icon: SvgPicture.asset( "assets/icons/home.svg", - color: currentIndex == 0 ? MyColors.grey3AColor : MyColors.grey98Color, + color: currentIndex == 0 + ? MyColors.grey3AColor + : MyColors.grey98Color, ).paddingAll(4), label: LocaleKeys.home.tr(), ), BottomNavigationBarItem( icon: SvgPicture.asset( "assets/icons/create_req.svg", - color: currentIndex == 1 ? MyColors.grey3AColor : MyColors.grey98Color, + color: currentIndex == 1 + ? MyColors.grey3AColor + : MyColors.grey98Color, ).paddingAll(4), label: LocaleKeys.createRequest.tr(), ), @@ -384,7 +509,9 @@ class _DashboardScreenState extends State { children: [ SvgPicture.asset( "assets/icons/work_list.svg", - color: currentIndex == 2 ? MyColors.grey3AColor : MyColors.grey98Color, + color: currentIndex == 2 + ? MyColors.grey3AColor + : MyColors.grey98Color, ).paddingAll(4), Consumer( builder: (cxt, data, child) { @@ -397,8 +524,12 @@ class _DashboardScreenState extends State { child: Container( padding: const EdgeInsets.only(left: 4, right: 4), alignment: Alignment.center, - decoration: BoxDecoration(color: MyColors.redColor, borderRadius: BorderRadius.circular(17)), - child: data.workListCounter.toString().toText10(color: Colors.white), + decoration: BoxDecoration( + color: MyColors.redColor, + borderRadius: BorderRadius.circular(17)), + child: data.workListCounter + .toString() + .toText10(color: Colors.white), ), ); }, @@ -410,19 +541,38 @@ class _DashboardScreenState extends State { BottomNavigationBarItem( icon: SvgPicture.asset( "assets/icons/item_for_sale.svg", - color: currentIndex == 3 ? MyColors.grey3AColor : MyColors.grey98Color, + color: currentIndex == 3 + ? MyColors.grey3AColor + : MyColors.grey98Color, ).paddingAll(4), label: LocaleKeys.itemsForSale.tr(), ), + BottomNavigationBarItem( + icon: SvgPicture.asset( + "assets/icons/chat/chat.svg", + color: currentIndex == 4 + ? MyColors.grey3AColor + : MyColors.grey98Color, + ).paddingAll(4), + label: LocaleKeys.chat.tr(), + ), ], currentIndex: currentIndex, - selectedLabelStyle: const TextStyle(fontSize: 10, color: MyColors.grey3AColor, fontWeight: FontWeight.w600), - unselectedLabelStyle: const TextStyle(fontSize: 10, color: MyColors.grey98Color, fontWeight: FontWeight.w600), + selectedLabelStyle: const TextStyle( + fontSize: 10, + color: MyColors.grey3AColor, + fontWeight: FontWeight.w600), + unselectedLabelStyle: const TextStyle( + fontSize: 10, + color: MyColors.grey98Color, + fontWeight: FontWeight.w600), type: BottomNavigationBarType.fixed, selectedItemColor: MyColors.grey3AColor, backgroundColor: MyColors.backgroundColor, - selectedIconTheme: const IconThemeData(color: MyColors.grey3AColor, size: 28), - unselectedIconTheme: const IconThemeData(color: MyColors.grey98Color, size: 28), + selectedIconTheme: + const IconThemeData(color: MyColors.grey3AColor, size: 28), + unselectedIconTheme: + const IconThemeData(color: MyColors.grey98Color, size: 28), onTap: (int index) { if (index == 1) { Navigator.pushNamed(context, AppRoutes.mowadhafhi); @@ -430,6 +580,8 @@ class _DashboardScreenState extends State { Navigator.pushNamed(context, AppRoutes.workList); } else if (index == 3) { Navigator.pushNamed(context, AppRoutes.itemsForSale); + } else if (index == 4) { + Navigator.pushNamed(context, AppRoutes.chat); } }, ), @@ -453,6 +605,7 @@ class _DashboardScreenState extends State { } }); - Navigator.pushNamed(context, AppRoutes.offersAndDiscountsDetails, arguments: getOffersDetailList); + Navigator.pushNamed(context, AppRoutes.offersAndDiscountsDetails, + arguments: getOffersDetailList); } } diff --git a/lib/widgets/app_bar_widget.dart b/lib/widgets/app_bar_widget.dart index 51bee9c..745dedf 100644 --- a/lib/widgets/app_bar_widget.dart +++ b/lib/widgets/app_bar_widget.dart @@ -1,11 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; -AppBar AppBarWidget(BuildContext context, {required String title, bool showHomeButton = true, bool showNotificationButton = false, bool showMemberButton = false}) { +AppBar AppBarWidget(BuildContext context, + {required String title, + bool showHomeButton = true, + bool showNotificationButton = false, + bool showMemberButton = false, + String? image}) { return AppBar( leadingWidth: 0, // leading: GestureDetector( @@ -18,10 +24,18 @@ AppBar AppBarWidget(BuildContext context, {required String title, bool showHomeB children: [ GestureDetector( behavior: HitTestBehavior.opaque, - onTap: Feedback.wrapForTap(() => Navigator.maybePop(context), context), - child: const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor), + onTap: + Feedback.wrapForTap(() => Navigator.maybePop(context), context), + child: + const Icon(Icons.arrow_back_ios, color: MyColors.darkIconColor), ), 4.width, + if (image != null) SvgPicture.asset( + image, + height: 40, + width: 40, + ), + if (image != null) 14.width, title.toText24(color: MyColors.darkTextColor, isBold: true).expanded, ], ), @@ -32,7 +46,8 @@ AppBar AppBarWidget(BuildContext context, {required String title, bool showHomeB if (showHomeButton) IconButton( onPressed: () { - Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)); + Navigator.popUntil( + context, ModalRoute.withName(AppRoutes.dashboard)); }, icon: const Icon(Icons.home, color: MyColors.darkIconColor), ), diff --git a/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart b/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart index 9aabbb7..e807a7b 100644 --- a/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart +++ b/lib/widgets/bottom_sheets/search_employee_bottom_sheet.dart @@ -22,10 +22,16 @@ class SearchEmployeeBottomSheet extends StatefulWidget { List? actionHistoryList; Function(ReplacementList) onSelectEmployee; - SearchEmployeeBottomSheet({required this.title, required this.apiMode, this.notificationID, this.actionHistoryList, required this.onSelectEmployee}); + SearchEmployeeBottomSheet( + {required this.title, + required this.apiMode, + this.notificationID, + this.actionHistoryList, + required this.onSelectEmployee}); @override - State createState() => _SearchEmployeeBottomSheetState(); + State createState() => + _SearchEmployeeBottomSheetState(); } class _SearchEmployeeBottomSheetState extends State { @@ -53,8 +59,12 @@ class _SearchEmployeeBottomSheetState extends State { userId: _selectedSearchIndex == 1 ? searchText : "", email: _selectedSearchIndex == 2 ? searchText : "", ); - favouriteUserList = replacementList?.where((element) => element.isFavorite ?? false).toList(); - nonFavouriteUserList = replacementList?.where((element) => !(element.isFavorite ?? false)).toList(); + favouriteUserList = replacementList + ?.where((element) => element.isFavorite ?? false) + .toList(); + nonFavouriteUserList = replacementList + ?.where((element) => !(element.isFavorite ?? false)) + .toList(); Utils.hideLoading(context); setState(() {}); } catch (e) { @@ -104,7 +114,8 @@ class _SearchEmployeeBottomSheetState extends State { IconButton( constraints: const BoxConstraints(), onPressed: () async { - await SystemChannels.textInput.invokeMethod('TextInput.hide'); + await SystemChannels.textInput + .invokeMethod('TextInput.hide'); fetchUserByInput(); }, icon: Icon(Icons.search)) @@ -123,7 +134,8 @@ class _SearchEmployeeBottomSheetState extends State { ListView.separated( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, - itemBuilder: (cxt, index) => employeeItemView(favouriteUserList![index]), + itemBuilder: (cxt, index) => + employeeItemView(favouriteUserList![index]), separatorBuilder: (cxt, index) => Container( height: 1, color: MyColors.borderE3Color, @@ -137,7 +149,8 @@ class _SearchEmployeeBottomSheetState extends State { ListView.separated( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, - itemBuilder: (cxt, index) => employeeItemView(nonFavouriteUserList![index]), + itemBuilder: (cxt, index) => employeeItemView( + nonFavouriteUserList![index]), separatorBuilder: (cxt, index) => Container( height: 1, color: MyColors.borderE3Color, @@ -148,7 +161,10 @@ class _SearchEmployeeBottomSheetState extends State { ).expanded ], ).paddingOnly(left: 21, right: 21, bottom: 0, top: 21).expanded, - Container(width: double.infinity, height: 1, color: MyColors.lightGreyEFColor), + Container( + width: double.infinity, + height: 1, + color: MyColors.lightGreyEFColor), DefaultButton( LocaleKeys.cancel.tr(), () { @@ -185,7 +201,11 @@ class _SearchEmployeeBottomSheetState extends State { Expanded( child: (replacement.employeeDisplayName ?? "").toText12(), ), - Icon(Icons.star, size: 16, color: replacement.isFavorite! ? MyColors.yellowFavColor : MyColors.borderCEColor), + Icon(Icons.star, + size: 16, + color: replacement.isFavorite! + ? MyColors.yellowFavColor + : MyColors.borderCEColor), ], ), ), @@ -208,7 +228,9 @@ class _SearchEmployeeBottomSheetState extends State { width: double.infinity, height: double.infinity, decoration: BoxDecoration( - color: value == groupValue ? MyColors.grey3AColor : Colors.transparent, + color: value == groupValue + ? MyColors.grey3AColor + : Colors.transparent, borderRadius: BorderRadius.all(const Radius.circular(100)), ), ), diff --git a/lib/widgets/shimmer/dashboard_shimmer_widget.dart b/lib/widgets/shimmer/dashboard_shimmer_widget.dart index cd5d88f..369b808 100644 --- a/lib/widgets/shimmer/dashboard_shimmer_widget.dart +++ b/lib/widgets/shimmer/dashboard_shimmer_widget.dart @@ -187,3 +187,70 @@ class ServicesMenuShimmer extends StatelessWidget { ); } } + +class ChatHomeShimmer extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey.shade100, + child: ListView.builder( + itemBuilder: (_, __) => Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 48.0, + height: 48.0, + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(40))), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + height: 8.0, + color: Colors.white, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 2.0), + ), + Container( + width: double.infinity, + height: 8.0, + color: Colors.white, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 2.0), + ), + Container( + width: 40.0, + height: 8.0, + color: Colors.white, + ), + ], + ), + ) + ], + ), + ), + itemCount: 6, + ), + ), + ), + ], + )); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 14c034a..afdb5fb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,12 @@ dependencies: share: 2.0.4 flutter_rating_bar: ^4.0.1 + #Chat + signalr_netcore: ^1.3.3 + logging: ^1.0.1 + + + dev_dependencies: flutter_test: sdk: flutter @@ -108,6 +114,7 @@ flutter: - assets/icons/ - assets/images/ - assets/images/login/ + - assets/icons/chat/ - assets/images/logos/ - assets/images/drawer/ - assets/icons/nfc/ic_nfc.png