fist commit ftc staff app clone

This commit is contained in:
2024-08-01 13:46:46 +05:30
commit bf9064a9c9
515 changed files with 42796 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:ftc_mobile_app/models/chat/ChatModel.dart';
import 'group_data_args.dart';
class ChatScreenArgs {
final String name;
final String profilePicPath;
final String otherUserId;
final GroupDataArgs? groupData;
final ValueChanged<ChatModel>? onLastMessageUpdate;
ChatScreenArgs({
required this.name,
required this.profilePicPath,
required this.otherUserId,
this.groupData,
this.onLastMessageUpdate,
});
}

View File

@@ -0,0 +1,13 @@
import 'package:ftc_mobile_app/models/chat/combined_last_messages_model_class.dart';
class GroupDataArgs {
final String groupId;
final List groupMembersIds;
final GroupWorkingScheduleTime scheduleTime;
GroupDataArgs({
required this.groupId,
required this.groupMembersIds,
required this.scheduleTime,
});
}

View File

@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:ftc_mobile_app/ftc_mobile_app.dart';
import 'package:ftc_mobile_app/models/chat/ChatModel.dart';
import 'package:ftc_mobile_app/utilities/extensions/custom_extensions.dart';
import 'package:ftc_mobile_app/view/custom_widgets/my_circle_image.dart';
import 'package:get/get.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:intl/intl.dart';
import 'arguments/chat_screen_args.dart';
import 'widgets/chat_screen_footer_widget.dart';
import 'widgets/message_bubble.dart';
class ChatScreen extends StatefulWidget {
final ChatScreenArgs args;
const ChatScreen({Key? key, required this.args}) : super(key: key);
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
late final ChatScreenController controller;
final sepFormatter = DateFormat("dd MMM yyyy");
@override
void initState() {
controller = Get.put(ChatScreenController(widget.args));
super.initState();
}
@override
Widget build(BuildContext context) {
return CustomScaffold(
backgroundColor: Colors.white,
screenKey: controller.screenKey,
onScreenTap: controller.removeFocus,
showAppBar: true,
appBar: _appBar,
body: SafeArea(
child: Column(
children: [
Expanded(child: messagesList()),
Obx(() {
return controller.isSocketConnected()
? ChatScreenFooterWidget(
controller: controller,
enabled: controller.isSocketConnected(),
)
: FrequentFunctions.noWidget;
})
],
),
),
);
}
AppBar get _appBar {
return AppBar(
toolbarHeight: 56.r,
leading: IconButton(
icon: CustomImageWidget(
imagePath: AssetsManager.kBackIcon,
height: 11.53.h,
width: 8.66.w,
),
onPressed: () {
Navigator.pop(context);
},
),
centerTitle: false,
titleSpacing: 0,
leadingWidth: 50.r,
surfaceTintColor: Colors.white,
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
MyCircleImage(
imageSize: 32.r,
url: "${WebUrls.baseUrl}${widget.args.profilePicPath}",
errorWidget: CustomImageWidget(
imagePath: AssetsManager.kPersonMainIcon,
imageColor: CustomAppColors.kDarkBlueTextColor,
height: 32.r,
width: 32.r,
),
),
10.horizontalSpace,
CustomTextWidget(
text: widget.args.name,
isExpanded: false,
fontSize: 16.sp,
fontWeight: FontWeight.w700,
fontColor: CustomAppColors.kDarkBlueTextColor,
),
],
),
);
}
Widget messagesList() {
return Obx(() {
final messages = controller.messages;
return (messages.isEmpty)
? FrequentFunctions.noWidget
: _messagesList(messages());
});
}
Widget _messagesList(List<ChatModel> list) {
final now = DateTime.now();
return GroupedListView<ChatModel, String>(
reverse: true,
elements: list,
padding: REdgeInsets.symmetric(horizontal: 18),
order: GroupedListOrder.DESC,
groupBy: (message) {
final messageDate =
DateTime.fromMillisecondsSinceEpoch(message.date ?? 0);
return DateFormatter.dateFormatter.format(messageDate);
},
groupSeparatorBuilder: (String date) {
final isToday = (date == DateFormatter.dateFormatter.format(now));
return _buildGroupSeparatorWidget(isToday
? "Today"
: sepFormatter.format(DateFormatter.dateFormatter.parse(date)));
},
itemBuilder: (_, e) => _buildItemWidget(list.indexOf(e), e),
sort: false,
separator: 8.verticalSpace,
);
}
Widget _buildGroupSeparatorWidget(String susTag) {
return Padding(
padding: REdgeInsets.symmetric(vertical: 12.0),
child: Row(
children: [
Expanded(
child: Divider(
height: 1,
color: Colors.grey.withOpacity(0.3),
)),
Text(
susTag,
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500),
).addPaddingHorizontal(10),
Expanded(
child: Divider(
height: 1,
color: Colors.grey.withOpacity(0.3),
)),
],
),
);
}
Widget _buildItemWidget(int index, ChatModel message) {
final isMyMessage = (message.from?.id == controller.myId);
final hasFile = message.filePath.isNotNullOrEmpty();
print("filePath: ${message.filePath}");
return MessageBubble(
senderName: message.from?.name ?? "",
profilePic: !isMyMessage ? (message.from?.profilePictureUrl ?? "") : "",
content: hasFile ? (message.filePath ?? '') : (message.message ?? ''),
type: isMyMessage ? MessageType.sent : MessageType.received,
contentType: hasFile
? (message.fileType == ChatModel.fileTypeLocalPath)
? MessageContentType.file
: MessageContentType.url
: MessageContentType.text,
sentMessageColor: CustomAppColors.kSmokeColor,
receivedMessageColor: CustomAppColors.kSecondaryColor,
messageTime: (message.date == null)
? ""
: DateTime.fromMillisecondsSinceEpoch(message.date!)
.toIso8601String(),
state: MessageState.stateNone,
status: MessageSeenStatus.seen,
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}

View 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();
}
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter_svg/svg.dart';
import 'package:ftc_mobile_app/ftc_mobile_app.dart';
import 'package:flutter/material.dart';
import 'package:ftc_mobile_app/utilities/extensions/custom_extensions.dart';
import 'package:get/get.dart';
class ChatScreenFooterWidget extends StatelessWidget {
const ChatScreenFooterWidget({
super.key,
required this.controller,
required this.enabled,
});
final ChatScreenController controller;
final bool enabled;
@override
Widget build(BuildContext context) {
return Padding(
padding: REdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Align(
alignment: Alignment.bottomCenter,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
onPressed: controller.pickAndSendFile,
icon: SvgPicture.asset(
AssetsManager.svgIcAdd,
width: 32.r,
height: 32.r,
),
),
Expanded(
child: TextField(
minLines: 1,
maxLines: 5,
textAlign: TextAlign.left,
controller: controller.messageTEC,
keyboardType: TextInputType.multiline,
focusNode: controller.messageFN,
enabled: enabled,
decoration: InputDecoration(
hintText: 'Type Message...',
// border: InputBorder.none,
enabledBorder: OutlineInputBorder(
borderRadius: 4.toRadius(),
borderSide: BorderSide(
width: 1,
color: Colors.grey.withOpacity(0.3),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: 4.toRadius(),
borderSide: BorderSide(
width: 1,
color: Get.theme.primaryColor,
),
),
contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
),
),
),
5.horizontalSpace,
SizedBox(
width: 80.w,
height: 40.r,
child: IgnorePointer(
ignoring: enabled.not,
child: CustomAppButton(
buttonText: "Send",
buttonColor:
enabled ? Get.theme.primaryColor : Colors.grey,
borderColor:
enabled ? Get.theme.primaryColor : Colors.grey,
onTap: controller.sendMessageButtonPressed,
),
)),
],
),
8.verticalSpace,
],
),
));
}
}

View File

@@ -0,0 +1,420 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:ftc_mobile_app/utilities/extensions/custom_extensions.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:ftc_mobile_app/ftc_mobile_app.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../custom_widgets/my_circle_image.dart';
import 'package:path/path.dart' as path;
enum MessageType { sent, received }
enum MessageSeenStatus { delivered, seen }
enum MessageContentType { text, file, url }
enum MessageState {
stateNone(0),
stateError(-1),
stateSending(1),
stateSuccess(2);
final int intValue;
static MessageState stateFromIntValue(int value) {
switch (value) {
case -1:
return MessageState.stateError;
case 1:
return MessageState.stateSending;
case 2:
return MessageState.stateSuccess;
default:
return MessageState.stateNone;
}
}
const MessageState(this.intValue);
}
class MessageBubble extends StatelessWidget {
final String senderName;
final String content;
final MessageState state;
final MessageContentType contentType;
final String profilePic;
final MessageType type;
final MessageSeenStatus status;
final String messageTime;
final Color? sentMessageColor;
final Color? receivedMessageColor;
final Color? sentMessageTextColor;
final Color? receivedMessageTextColor;
final bool showReportButton;
const MessageBubble({
Key? key,
required this.senderName,
required this.content,
required this.contentType,
required this.state,
required this.profilePic,
required this.type,
required this.status,
required this.messageTime,
this.sentMessageColor,
this.receivedMessageColor,
this.sentMessageTextColor,
this.receivedMessageTextColor,
this.showReportButton = true,
}) : super(key: key);
Color get _backgroundColor => (type == MessageType.sent)
? (sentMessageColor ?? Get.theme.primaryColor)
: (receivedMessageColor ?? const Color(0xffC1C1C5));
Color get messageColor => (type == MessageType.sent)
? (sentMessageTextColor ?? Colors.black)
: (receivedMessageTextColor ?? Colors.white);
// double get _paddingLeft => (type == MessageType.sent) ? 0.15.sw : 0;
final radius = 16.0;
@override
Widget build(BuildContext context) {
final loader = Visibility(
visible: (state == MessageState.stateSending),
child: Align(
alignment: Alignment.topCenter,
child: SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(
color: Get.theme.primaryColor,
strokeWidth: 2,
)),
),
);
final messageBox = SizedBox(
width: 0.7.sw,
child: Padding(
padding: REdgeInsets.symmetric(vertical: 5),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(type == MessageType.received)
? MyCircleImage(
imageSize: 20.r,
url: WebUrls.baseUrl + profilePic,
errorWidget: const DecoratedBox(
decoration: BoxDecoration(color: Color(0xffC1C1C5)),
child: Center(
child: Icon(
Icons.image_not_supported_outlined,
color: Colors.black,
size: 12,
),
),
),
).paddingOnly(right: 8.r)
: FrequentFunctions.noWidget,
Expanded(
child: (contentType == MessageContentType.text)
? _textMessage()
: (content.isImageFileName)
? _imageWidget()
: _documentFileWidget(),
),
],
),
4.verticalSpace,
Row(
children: [
(type == MessageType.received)
? SizedBox(width: 28.r)
: FrequentFunctions.noWidget,
(state == MessageState.stateSending)
? loader
: (state == MessageState.stateError)
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.warning_amber_rounded,
color: Colors.red,
size: 14.r,
),
4.horizontalSpace,
Text(
'Message not sent',
style: const TextStyle().copyWith(
color: Colors.red,
),
)
],
)
: FrequentFunctions.noWidget
],
)
],
),
),
);
return SizedBox(
width: double.maxFinite,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: (type == MessageType.sent)
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [messageBox],
),
);
}
Widget _textMessage() => Container(
padding: REdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration:
BoxDecoration(color: _backgroundColor, borderRadius: 10.toRadius()),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
(type == MessageType.received)
? CustomTextWidget(
text: senderName,
fontColor: messageColor,
textAlign: TextAlign.left,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
).paddingOnly(bottom: 10.r)
: FrequentFunctions.noWidget,
CustomTextWidget(
text: content,
fontColor: messageColor,
textAlign: TextAlign.left,
fontSize: 12.sp,
fontWeight: FontWeight.w400,
),
4.verticalSpace,
Align(
alignment: Alignment.centerRight,
child: _messageTimeWidget(),
)
],
),
);
Widget _imageWidget() {
return InkWell(
onTap: _openPreviewDialog,
child: Container(
width: 200.r,
height: 250.r,
clipBehavior: Clip.antiAlias,
padding: REdgeInsets.all(4),
decoration: BoxDecoration(
color: _backgroundColor,
borderRadius: radius.toRadius()),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
//Sender Name
(type == MessageType.received)
? CustomTextWidget(
text: senderName,
fontColor: messageColor,
textAlign: TextAlign.left,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
).paddingOnly(bottom: 10.r)
: FrequentFunctions.noWidget,
//image
Expanded(
child: ClipRRect(
clipBehavior: Clip.antiAlias,
borderRadius: (radius - 4).toRadius(),
child: (contentType == MessageContentType.file)
? Image.file(File(content), fit: BoxFit.cover)
: CachedNetworkImage(
imageUrl: (WebUrls.baseUrl + content),
fit: BoxFit.cover,
),
),
),
8.verticalSpace,
//Time
Align(
alignment: Alignment.centerRight,
child: _messageTimeWidget(),
).addPaddingHorizontal(12),
8.verticalSpace,
],
),
),
);
}
Widget _documentFileWidget() {
return Container(
decoration: BoxDecoration(
color: _backgroundColor,
borderRadius: radius.toRadius(),
),
clipBehavior: Clip.antiAlias,
padding: REdgeInsets.all(4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
//Sender Name
(type == MessageType.received)
? CustomTextWidget(
text: senderName,
fontColor: messageColor,
textAlign: TextAlign.left,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
).paddingOnly(bottom: 10.r)
: FrequentFunctions.noWidget,
//File
InkWell(
onTap: () {
if (contentType == MessageContentType.url) {
_launchUrl(WebUrls.baseUrl + content);
} else {
_launchUrl(content);
}
},
child: Card(
elevation: 0,
surfaceTintColor: Colors.white,
child: Padding(
padding: REdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
children: [
CircleAvatar(
radius: 24.r,
backgroundColor:
CustomAppColors.kSecondaryColor.withOpacity(0.1),
child: const Icon(
Icons.file_copy_outlined,
color: CustomAppColors.kSecondaryColor,
),
),
12.horizontalSpace,
Expanded(
child: CustomTextWidget(
text: path.basename(content),
fontColor: messageColor,
textAlign: TextAlign.left,
fontSize: 12.sp,
fontWeight: FontWeight.w400,
),
)
],
),
),
),
),
4.verticalSpace,
//Time
Align(
alignment: Alignment.centerRight,
child: _messageTimeWidget(),
).addPaddingHorizontal(12),
],
),
);
}
Widget _messageTimeWidget() {
return (messageTime.isNotEmpty)
? Text(
DateFormat("hh:mm aa")
.format(DateTime.parse(messageTime).toLocal()),
style: TextStyle(
color: (type == MessageType.sent) ? Colors.grey : Colors.white,
fontSize: 10.sp,
fontWeight: FontWeight.w400,
),
)
: FrequentFunctions.noWidget;
}
void _openPreviewDialog() {
final img = (contentType == MessageContentType.file)
? FileImage(File(content))
: CachedNetworkImageProvider(WebUrls.baseUrl + content);
Get.dialog(
Material(
type: MaterialType.transparency,
child: SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: Stack(
fit: StackFit.expand,
children: [
Positioned.fill(
child: PhotoViewGestureDetectorScope(
axis: Axis.vertical,
child: PhotoView(
tightMode: true,
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 1.1,
initialScale: PhotoViewComputedScale.contained,
imageProvider: img as ImageProvider,
heroAttributes:
const PhotoViewHeroAttributes(tag: "someTag"),
),
),
),
Positioned(
right: 12,
top: 12,
child: InkWell(
onTap: Get.back,
child: Card(
color: Colors.white,
shape: 24.toRoundedRectRadius(),
elevation: 4,
child: RSizedBox.square(
dimension: 40,
child: Icon(
Icons.close,
color: Colors.black,
size: 24.r,
),
),
),
),
)
],
),
),
),
);
}
Future<void> _launchUrl(String url) async {
try {
await launchUrl(Uri.parse(url));
} catch (e) {
debugPrint(e.toString());
}
}
}