fist commit ftc staff app clone
This commit is contained in:
503
lib/view/screens/chat/controller/chat_screen_controller.dart
Normal file
503
lib/view/screens/chat/controller/chat_screen_controller.dart
Normal file
@@ -0,0 +1,503 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:ftc_mobile_app/controllers/home/inbox_screen_controller.dart';
|
||||
import 'package:ftc_mobile_app/models/chat/all_group_messages_model.dart';
|
||||
import 'package:ftc_mobile_app/models/chat/single_chat.dart';
|
||||
import 'package:ftc_mobile_app/models/profileData/user_data.dart';
|
||||
import 'package:ftc_mobile_app/utilities/extensions/custom_extensions.dart';
|
||||
import 'package:ftc_mobile_app/utilities/frequent_functions.dart';
|
||||
import 'package:ftc_mobile_app/utilities/image_picker_popup.dart';
|
||||
import 'package:ftc_mobile_app/utilities/local_storage_manager/export_local_storage.dart';
|
||||
import 'package:ftc_mobile_app/view/custom_widgets/home/custom_message_dialog.dart';
|
||||
import 'package:ftc_mobile_app/view/screens/chat/arguments/chat_screen_args.dart';
|
||||
import 'package:ftc_mobile_app/web_services/chat_services.dart';
|
||||
import 'package:ftc_mobile_app/web_services/web_url.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:socket_io_client/socket_io_client.dart';
|
||||
import '../../../../models/chat/ChatModel.dart';
|
||||
|
||||
class ChatScreenController extends GetxController {
|
||||
final GlobalKey<ScaffoldState> screenKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
final messageTEC = TextEditingController();
|
||||
final messageFN = FocusNode();
|
||||
|
||||
final messages = RxList<ChatModel>();
|
||||
final isSocketConnected = false.obs;
|
||||
|
||||
late final ChatScreenArgs args;
|
||||
|
||||
String myId = "";
|
||||
|
||||
ChatScreenController(ChatScreenArgs data) {
|
||||
args = data;
|
||||
}
|
||||
|
||||
late Socket _socketIO;
|
||||
String listenId = "";
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
//Getting my ID
|
||||
// String userJson = LocalStorageManager.getSessionToken(
|
||||
// tokenKey: LocalStorageKeys.kUserModelKey,
|
||||
// );
|
||||
// UserModel userModel = UserModel.fromJson(json.decode(userJson));
|
||||
myId = LocalStorageManager.userId;
|
||||
|
||||
if (_canChat) {
|
||||
initializeSocket();
|
||||
}
|
||||
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
if (isGroup) {
|
||||
fetchGroupMessagesFromService(args.groupData!.groupId);
|
||||
} else {
|
||||
fetchSingleMessagesFromService();
|
||||
}
|
||||
|
||||
if (_canChat.not) {
|
||||
showCantMessageDialog();
|
||||
}
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
bool get isGroup => args.groupData != null;
|
||||
|
||||
/// This method checks the current date if it lies within schedule time or not.
|
||||
/// If it does, it means chat is enabled else chat will be disabled.
|
||||
/// If schedule times not available, it returns true;
|
||||
bool get _canChat {
|
||||
final startMills = args.groupData?.scheduleTime.startTime ?? 0;
|
||||
final endMills = args.groupData?.scheduleTime.endTime ?? 0;
|
||||
|
||||
if (startMills > 0 && endMills > 0) {
|
||||
// Schedule times are available
|
||||
|
||||
final scheduleTime = _getScheduleTime(startMills, endMills);
|
||||
final currentTime = TimeOfDay.now();
|
||||
|
||||
// Current time within start, end schedule time
|
||||
return (currentTime.isAfter(scheduleTime.start) &&
|
||||
currentTime.isBefore(scheduleTime.end));
|
||||
} else {
|
||||
// Schedule times not available
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Make sure to pass correct millis values
|
||||
({TimeOfDay start, TimeOfDay end}) _getScheduleTime(
|
||||
int startMills, int endMills) {
|
||||
final sd = DateTime.fromMillisecondsSinceEpoch(startMills);
|
||||
final ed = DateTime.fromMillisecondsSinceEpoch(endMills);
|
||||
|
||||
final startTime = TimeOfDay(hour: sd.hour, minute: sd.minute);
|
||||
final endTime = TimeOfDay(hour: ed.hour, minute: ed.minute);
|
||||
|
||||
return (start: startTime, end: endTime);
|
||||
}
|
||||
|
||||
initializeSocket() {
|
||||
debugPrint('Socket address: ${WebUrls.socketUrl}');
|
||||
|
||||
_socketIO = io(
|
||||
WebUrls.socketUrl,
|
||||
OptionBuilder()
|
||||
.setTransports(['websocket']) // for Flutter or Dart VM
|
||||
.enableForceNewConnection()
|
||||
.enableAutoConnect()
|
||||
.setExtraHeaders({'foo': 'bar'}) // optional
|
||||
.build());
|
||||
|
||||
isSocketConnected.value = true;
|
||||
_socketIO.onConnect((_) {
|
||||
isSocketConnected.value = true;
|
||||
debugPrint('Socket Connected');
|
||||
});
|
||||
_socketIO.onDisconnect((_) {
|
||||
debugPrint('Socket Disconnected');
|
||||
// isSocketConnected.value = false;
|
||||
});
|
||||
_socketIO.onConnectError((e) {
|
||||
debugPrint('Socket Connection Error: ${e.toString()}');
|
||||
// isSocketConnected.value = false;
|
||||
});
|
||||
_socketIO.onConnectTimeout((e) {
|
||||
debugPrint('Socket timeout Error: ${e.toString()}');
|
||||
// isSocketConnected.value = false;
|
||||
});
|
||||
|
||||
//listenIdReceive
|
||||
listenId = (isGroup) ? args.groupData!.groupId : (myId + args.otherUserId);
|
||||
debugPrint('listen on: $listenId');
|
||||
_socketIO.on(listenId, (data) {
|
||||
debugPrint('listen on listenId: $data');
|
||||
_handleIncomingMessages(data);
|
||||
});
|
||||
}
|
||||
|
||||
_handleIncomingMessages(Map<String, dynamic> chatJson) {
|
||||
log("chat listen: $chatJson");
|
||||
// _total += 1;
|
||||
// _skip += 1;
|
||||
final chatModel = ChatModel.fromJson(chatJson);
|
||||
|
||||
if (chatJson.containsKey('createdAt')) {
|
||||
chatModel.date =
|
||||
DateTime.tryParse(chatJson["createdAt"])?.millisecondsSinceEpoch ?? 0;
|
||||
} else {
|
||||
chatModel.date = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
if (isGroup) {
|
||||
if (chatJson.containsKey('userId') && chatJson["userId"] is Map) {
|
||||
chatModel.from = UserData.fromJson(chatJson["userId"]);
|
||||
}
|
||||
_onFirstMessageSent();
|
||||
} else {
|
||||
chatModel.from = UserData(
|
||||
id: args.otherUserId,
|
||||
name: args.name,
|
||||
profilePictureUrl: args.profilePicPath);
|
||||
chatModel.to = UserData(id: chatJson['to']);
|
||||
}
|
||||
messages.insert(0, chatModel);
|
||||
args.onLastMessageUpdate?.call(chatModel);
|
||||
}
|
||||
|
||||
Future fetchSingleMessagesFromService() async {
|
||||
// _skip = 0;
|
||||
final response = await ChatService()
|
||||
.allSingleUsersChatMessagesServerAdmin(
|
||||
from: myId,
|
||||
to: args.otherUserId,
|
||||
)
|
||||
.showLoader();
|
||||
|
||||
if (response is List<ChatModel>) {
|
||||
if (response.isNotEmpty) {
|
||||
//Note: Converting this response to List of ChatModel objects
|
||||
// List<ChatModel> chats = [];
|
||||
// await Future.forEach(response.reversed, (e) {
|
||||
// chats.add(ChatModel(
|
||||
// id: e.id,
|
||||
// from: e.from,
|
||||
// to: e.to,
|
||||
// message: e.message,
|
||||
// date:
|
||||
// DateTime.tryParse(e.createdAt)?.millisecondsSinceEpoch ?? 0));
|
||||
// });
|
||||
messages.value = response.reversed.toList();
|
||||
}
|
||||
} else if (response is String) {
|
||||
FrequentFunctions.showToast(message: response);
|
||||
}
|
||||
}
|
||||
|
||||
void fetchGroupMessagesFromService(String idOfGroup) async {
|
||||
dynamic response = await ChatService().allGroupMessages(
|
||||
sortOrder: -1,
|
||||
offset: 0,
|
||||
limit: 100000,
|
||||
groupId: idOfGroup,
|
||||
isDeleted: false).showLoader();
|
||||
|
||||
if (response is List<AllGroupMessages>) {
|
||||
if (response.isNotEmpty) {
|
||||
//Note: Converting this response to List of ChatModel objects
|
||||
List<ChatModel> chats = [];
|
||||
await Future.forEach(response, (e) {
|
||||
chats.add(ChatModel(
|
||||
id: e.id,
|
||||
from: e.userId,
|
||||
message: e.message,
|
||||
messageType: e.messageType,
|
||||
filePath: e.filePath,
|
||||
date:
|
||||
DateTime.tryParse(e.createdAt)?.millisecondsSinceEpoch ?? 0));
|
||||
});
|
||||
messages.value = chats;
|
||||
}
|
||||
} else if (response is String) {
|
||||
FrequentFunctions.showToast(message: response);
|
||||
}
|
||||
}
|
||||
|
||||
void sendMessageButtonPressed() async {
|
||||
if (messageTEC.text.trim().isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sendMessage(
|
||||
message: messageTEC.text.trim(),
|
||||
);
|
||||
messageTEC.clear();
|
||||
}
|
||||
|
||||
//if sending first message, then updating chat list screen by calling it's listing api
|
||||
_onFirstMessageSent() {
|
||||
if (messages.length == 1) {
|
||||
try {
|
||||
final iController = Get.find<InboxScreenController>();
|
||||
iController.onFirstMessageSend.value = true;
|
||||
print("Got controller");
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_sendMessage({
|
||||
String? message,
|
||||
File? file,
|
||||
}) {
|
||||
if (isGroup) {
|
||||
_sendGroupMessage(
|
||||
message: message,
|
||||
file: file,
|
||||
);
|
||||
} else {
|
||||
_sendPrivateMessage(
|
||||
message: message,
|
||||
file: file,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///Note: no need to update ui for sent message here because
|
||||
///it's gonna handled by socket event
|
||||
_sendGroupMessage({
|
||||
String? message,
|
||||
File? file,
|
||||
}) async {
|
||||
await ChatService().addGroupMessageService(
|
||||
message: message ?? "",
|
||||
messageType: (file != null) ? MessageType.file : MessageType.message,
|
||||
file: file,
|
||||
userId: myId,
|
||||
groupId: args.groupData!.groupId,
|
||||
);
|
||||
}
|
||||
|
||||
///Note: handling messages list update also here
|
||||
_sendPrivateMessage({
|
||||
String? message,
|
||||
File? file,
|
||||
}) async {
|
||||
final id = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
final model = ChatModel(
|
||||
id: id.toString(),
|
||||
from: UserData(id: LocalStorageManager.userId),
|
||||
to: UserData(
|
||||
id: args.otherUserId,
|
||||
name: args.name,
|
||||
profilePictureUrl: args.profilePicPath,
|
||||
),
|
||||
message: message,
|
||||
messageType:
|
||||
(file != null) ? MessageType.file.name : MessageType.message.name,
|
||||
fileType: (file != null) ? ChatModel.fileTypeLocalPath : null,
|
||||
date: id,
|
||||
state: ChatModel.stateLoading,
|
||||
);
|
||||
|
||||
messages.insert(0, model);
|
||||
args.onLastMessageUpdate?.call(model);
|
||||
|
||||
dynamic response = await ChatService().addSingleMessage(
|
||||
message: message ?? "",
|
||||
messageType: (file != null) ? MessageType.file : MessageType.message,
|
||||
file: file,
|
||||
senderId: LocalStorageManager.userId,
|
||||
receiverId: args.otherUserId,
|
||||
);
|
||||
|
||||
final msg = messages.firstWhereOrNull((e) {
|
||||
return e.id == id.toString();
|
||||
});
|
||||
|
||||
if (msg != null) {
|
||||
final index = messages.indexOf(msg);
|
||||
|
||||
if (response is SingleChatModelClass) {
|
||||
//message sent successfully
|
||||
msg.id = response.id;
|
||||
msg.fileType = null;
|
||||
msg.state = ChatModel.stateSuccess;
|
||||
|
||||
messages
|
||||
..removeAt(index)
|
||||
..insert(index, msg);
|
||||
|
||||
_onFirstMessageSent();
|
||||
} else {
|
||||
msg.state = ChatModel.stateError;
|
||||
messages.removeAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pickAndSendFile() async {
|
||||
Get.focusScope?.unfocus();
|
||||
Get.bottomSheet(CupertinoActionSheet(
|
||||
actions: [
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
ImagePickerPopup.getImageFromSource(
|
||||
fromCamera: true,
|
||||
onFetchImage: (f) {
|
||||
_sendMessage(file: f);
|
||||
});
|
||||
},
|
||||
leading: const Icon(CupertinoIcons.camera),
|
||||
title: const Text("Camera"),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 18.r,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles(type: FileType.media);
|
||||
|
||||
if (result != null && result.files.single.path != null) {
|
||||
if (result.files.single.path!.isImageFileName) {
|
||||
_sendMessage(file: File(result.files.single.path!));
|
||||
} else {
|
||||
FrequentFunctions.showToast(message: "File doesn't supported ");
|
||||
}
|
||||
}
|
||||
},
|
||||
leading: const Icon(CupertinoIcons.photo_on_rectangle),
|
||||
title: const Text("Gallery"),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 18.r,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
final FilePickerResult? result = await FilePicker.platform
|
||||
.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["pdf", "doc", "docx", "xlsx", "xls"]);
|
||||
|
||||
if (result != null) {
|
||||
_sendMessage(file: File(result.files.single.path!));
|
||||
}
|
||||
},
|
||||
leading: const Icon(Icons.attach_file),
|
||||
title: const Text("File"),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 18.r,
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
// _sendImage(File file) async {
|
||||
// debugPrint("file: ${file.path}");
|
||||
// //
|
||||
// // ChatModel model = ChatModel(
|
||||
// // sentBy: UserData(id: myId),
|
||||
// // sentTo: otherUser,
|
||||
// // date: DateTime.now().toUtc().millisecondsSinceEpoch,
|
||||
// // files: [file.path],
|
||||
// // fileType: fileTypeLocalPath,
|
||||
// // state: ChatModel.stateLoading);
|
||||
// //
|
||||
// // final modelHash = model.hashCode.toString();
|
||||
// // model.localId = modelHash;
|
||||
// //
|
||||
// // debugPrint("message modelHash: $modelHash");
|
||||
// // _handleChat(model.toJson());
|
||||
// //
|
||||
// // // return;
|
||||
// // var res = await repository.sendChatAttachmentApi(
|
||||
// // req: ChatMessageRequest(
|
||||
// // sentBy: myId,
|
||||
// // sentTo: otherUser.id!,
|
||||
// // files: [file],
|
||||
// // fileType: "image"),
|
||||
// // );
|
||||
// //
|
||||
// // final i = messages.indexWhere((e) => e.localId == modelHash);
|
||||
// //
|
||||
// // if (res.success == true) {
|
||||
// // if (i != -1) {
|
||||
// // messages[i].state = ChatModel.stateSuccess;
|
||||
// // messages.refresh();
|
||||
// // }
|
||||
// // } else {
|
||||
// // if (i != -1) {
|
||||
// // messages[i].state = ChatModel.stateError;
|
||||
// // messages.refresh();
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
|
||||
//Always call this method if _canChat is false
|
||||
void showCantMessageDialog() {
|
||||
final startMills = args.groupData!.scheduleTime.startTime;
|
||||
final endMills = args.groupData!.scheduleTime.endTime;
|
||||
|
||||
final scheduleTime = _getScheduleTime(startMills, endMills);
|
||||
final sd = DateTime(
|
||||
2024, 1, 1, scheduleTime.start.hour, scheduleTime.start.minute, 0);
|
||||
final ed =
|
||||
DateTime(2024, 1, 1, scheduleTime.end.hour, scheduleTime.end.minute, 0);
|
||||
|
||||
showDialog(
|
||||
context: screenKey.currentState!.context,
|
||||
builder: (BuildContext context) {
|
||||
return CustomMessageDialog(
|
||||
dialogButtonText: "Close",
|
||||
dialogMessageText:
|
||||
"It is currently outside the working hours. You can only send message during the working hours",
|
||||
dialogMessageTextBold:
|
||||
"Working hours: ${DateFormat("hh:mm aa").format(sd)} - ${DateFormat("hh:mm aa").format(ed)}",
|
||||
headingText: "You Can't message right now",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// new
|
||||
void removeFocus() {
|
||||
FocusScope.of(screenKey.currentContext!).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
_socketIO.clearListeners();
|
||||
_socketIO.disconnect();
|
||||
_socketIO.destroy();
|
||||
_socketIO.dispose();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
messageTEC.dispose();
|
||||
messageFN.dispose();
|
||||
// scrollController.dispose();
|
||||
Get.delete<ChatScreenController>();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user