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 _launchUrl(String url) async { try { await launchUrl(Uri.parse(url)); } catch (e) { debugPrint(e.toString()); } } }