421 lines
13 KiB
Dart
421 lines
13 KiB
Dart
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());
|
|
}
|
|
}
|
|
}
|