fist commit ftc staff app clone
This commit is contained in:
89
lib/view/screens/chat/widgets/chat_screen_footer_widget.dart
Normal file
89
lib/view/screens/chat/widgets/chat_screen_footer_widget.dart
Normal 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,
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
420
lib/view/screens/chat/widgets/message_bubble.dart
Normal file
420
lib/view/screens/chat/widgets/message_bubble.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user