Initial commit

This commit is contained in:
2025-12-08 23:25:00 +05:00
commit ee5cb4ac1a
851 changed files with 115172 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
import 'package:driver/models/conversation_model.dart';
class ChatVideoContainer {
Url videoUrl;
String thumbnailUrl;
ChatVideoContainer({required this.videoUrl, required this.thumbnailUrl});
}

View File

@@ -0,0 +1,358 @@
import 'dart:async';
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:driver/app/chat_screens/ChatVideoContainer.dart';
import 'package:driver/app/chat_screens/full_screen_image_viewer.dart';
import 'package:driver/app/chat_screens/full_screen_video_viewer.dart';
import 'package:driver/constant/constant.dart';
import 'package:driver/controllers/chat_controller.dart';
import 'package:driver/models/conversation_model.dart';
import 'package:driver/themes/app_them_data.dart';
import 'package:driver/themes/theme_controller.dart';
import 'package:driver/utils/fire_store_utils.dart';
import 'package:driver/utils/network_image_widget.dart';
import 'package:driver/widget/firebase_pagination/src/firestore_pagination.dart';
import 'package:driver/widget/firebase_pagination/src/models/view_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
class ChatScreen extends StatelessWidget {
const ChatScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return GetX(
init: ChatController(),
builder: (controller) {
return Scaffold(
appBar: AppBar(
backgroundColor: isDark ? AppThemeData.surfaceDark : AppThemeData.surface,
centerTitle: false,
titleSpacing: 0,
title: Text(
controller.customerName.value,
textAlign: TextAlign.start,
style: TextStyle(
fontFamily: AppThemeData.medium,
fontSize: 16,
color: isDark ? AppThemeData.grey50 : AppThemeData.grey900,
),
),
),
body: Column(
children: [
Expanded(
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: FirestorePagination(
controller: controller.scrollController,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, documentSnapshots, index) {
ConversationModel inboxModel = ConversationModel.fromJson(documentSnapshots[index].data() as Map<String, dynamic>);
return chatItemView(isDark, inboxModel.senderId == FireStoreUtils.getCurrentUid(), inboxModel);
},
onEmpty: Constant.showEmptyView(message: "No Conversion found".tr, isDark: isDark),
// orderBy is compulsory to enable pagination
query: FirebaseFirestore.instance
.collection(controller.chatType.value == "Driver" ? 'chat_driver' : 'chat_restaurant')
.doc(controller.orderId.value)
.collection("thread")
.orderBy('createdAt', descending: false),
isLive: true,
viewType: ViewType.list,
),
),
),
Container(
color: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkWell(
onTap: () {
onCameraClick(context, controller);
},
child: SvgPicture.asset("assets/icons/ic_picture_one.svg")),
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 10),
child: TextField(
textInputAction: TextInputAction.send,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
controller: controller.messageController.value,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(top: 3, left: 10),
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
hintText: 'Type message here....'.tr,
),
onSubmitted: (value) async {
if (controller.messageController.value.text.isNotEmpty) {
controller.sendMessage(controller.messageController.value.text, null, '', 'text');
Timer(const Duration(milliseconds: 500), () => controller.scrollController.jumpTo(controller.scrollController.position.maxScrollExtent));
controller.messageController.value.clear();
}
},
),
)),
InkWell(
onTap: () {
if (controller.messageController.value.text.isNotEmpty) {
controller.sendMessage(controller.messageController.value.text, null, '', 'text');
Timer(const Duration(milliseconds: 500), () => controller.scrollController.jumpTo(controller.scrollController.position.maxScrollExtent));
controller.messageController.value.clear();
}
},
child: Container(
margin: const EdgeInsets.only(left: 10),
decoration: BoxDecoration(
color: isDark ? AppThemeData.grey700 : AppThemeData.grey200,
borderRadius: BorderRadius.circular(30),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: SvgPicture.asset("assets/icons/ic_send.svg"),
),
),
),
],
),
const SizedBox(
height: 20,
),
],
),
),
),
],
),
);
});
}
Widget chatItemView(isDark, bool isMe, ConversationModel data) {
return Container(
padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10),
child: isMe
? Align(
alignment: Alignment.topRight,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
data.messageType == "text"
? Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12), bottomLeft: Radius.circular(12)),
color: AppThemeData.primary300,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Text(
data.message.toString(),
style: const TextStyle(
fontFamily: AppThemeData.medium,
fontSize: 16,
color: AppThemeData.grey50,
),
),
)
// Container(
// decoration: BoxDecoration(
// borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12), bottomLeft: Radius.circular(12)),
// color: AppThemeData.primary300,
// ),
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
// child: Text(
// data.message.toString(),
// style: const TextStyle(
// fontFamily: AppThemeData.medium,
// fontSize: 16,
// color: AppThemeData.grey50,
// ),
// ),
// )
: data.messageType == "image"
? ClipRRect(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12), bottomLeft: Radius.circular(12)),
child: Stack(alignment: Alignment.center, children: [
GestureDetector(
onTap: () {
Get.to(FullScreenImageViewer(imageUrl: data.url!.url));
},
child: Hero(
tag: data.url!.url,
child: NetworkImageWidget(
imageUrl: data.url!.url,
height: 100,
width: 100,
fit: BoxFit.cover,
),
),
),
]),
)
: FloatingActionButton(
mini: true,
heroTag: data.id,
backgroundColor: AppThemeData.primary300,
onPressed: () {
Get.to(FullScreenVideoViewer(heroTag: data.id.toString(), videoUrl: data.url!.url));
},
child: const Icon(
Icons.play_arrow,
color: Colors.white,
),
),
const SizedBox(height: 5),
Text(DateFormat('MMM d, yyyy hh:mm aa').format(DateTime.fromMillisecondsSinceEpoch(data.createdAt!.millisecondsSinceEpoch)),
style: const TextStyle(color: Colors.grey, fontSize: 12)),
],
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
data.messageType == "text"
? Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12), bottomRight: Radius.circular(12)),
color: isDark ? AppThemeData.grey700 : AppThemeData.grey200,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Text(
data.message.toString(),
style: TextStyle(
fontFamily: AppThemeData.medium,
fontSize: 16,
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
),
),
)
: data.messageType == "image"
? ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 50,
maxWidth: 200,
),
child: ClipRRect(
borderRadius: const BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12), bottomRight: Radius.circular(12)),
child: Stack(alignment: Alignment.center, children: [
GestureDetector(
onTap: () {
Get.to(FullScreenImageViewer(imageUrl: data.url!.url));
},
child: Hero(
tag: data.url!.url,
child: NetworkImageWidget(
imageUrl: data.url!.url,
),
),
),
]),
))
: FloatingActionButton(
mini: true,
heroTag: data.id,
backgroundColor: AppThemeData.primary300,
onPressed: () {
Get.to(FullScreenVideoViewer(heroTag: data.id.toString(), videoUrl: data.url!.url));
},
child: const Icon(
Icons.play_arrow,
color: Colors.white,
),
),
const SizedBox(height: 5),
Text(DateFormat('MMM d, yyyy hh:mm aa').format(DateTime.fromMillisecondsSinceEpoch(data.createdAt!.millisecondsSinceEpoch)),
style: const TextStyle(color: Colors.grey, fontSize: 12)),
],
),
);
}
void onCameraClick(BuildContext context, ChatController controller) {
final action = CupertinoActionSheet(
message: Text(
'Send Media'.tr,
style: TextStyle(fontSize: 15.0),
),
actions: <Widget>[
CupertinoActionSheetAction(
isDefaultAction: false,
onPressed: () async {
Get.back();
XFile? image = await controller.imagePicker.pickImage(source: ImageSource.gallery);
if (image != null) {
Url url = await FireStoreUtils.uploadChatImageToFireStorage(File(image.path), context);
controller.sendMessage('', url, '', 'image');
}
},
child: Text("Choose image from gallery".tr),
),
CupertinoActionSheetAction(
isDefaultAction: false,
onPressed: () async {
Get.back();
XFile? galleryVideo = await controller.imagePicker.pickVideo(source: ImageSource.gallery);
if (galleryVideo != null) {
ChatVideoContainer? videoContainer = await FireStoreUtils.uploadChatVideoToFireStorage(context, File(galleryVideo.path));
if (videoContainer != null) {
controller.sendMessage('', videoContainer.videoUrl, videoContainer.thumbnailUrl, 'video');
}
}
},
child: Text("Choose video from gallery".tr),
),
CupertinoActionSheetAction(
isDestructiveAction: false,
onPressed: () async {
Get.back();
XFile? image = await controller.imagePicker.pickImage(source: ImageSource.camera);
if (image != null) {
Url url = await FireStoreUtils.uploadChatImageToFireStorage(File(image.path), context);
controller.sendMessage('', url, '', 'image');
}
},
child: Text("Take a picture".tr),
),
CupertinoActionSheetAction(
isDestructiveAction: false,
onPressed: () async {
Get.back();
XFile? recordedVideo = await controller.imagePicker.pickVideo(source: ImageSource.camera);
if (recordedVideo != null) {
ChatVideoContainer? videoContainer = await FireStoreUtils.uploadChatVideoToFireStorage(context, File(recordedVideo.path));
if (videoContainer != null) {
controller.sendMessage('', videoContainer.videoUrl, videoContainer.thumbnailUrl, 'video');
}
}
},
child: Text("Record video".tr),
)
],
cancelButton: CupertinoActionSheetAction(
child: Text(
'Cancel'.tr,
),
onPressed: () {
Get.back();
},
),
);
showCupertinoModalPopup(context: context, builder: (context) => action);
}
}

View File

@@ -0,0 +1,136 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:driver/app/chat_screens/chat_screen.dart';
import 'package:driver/constant/constant.dart';
import 'package:driver/constant/show_toast_dialog.dart';
import 'package:driver/models/inbox_model.dart';
import 'package:driver/models/user_model.dart';
import 'package:driver/themes/app_them_data.dart';
import 'package:driver/themes/responsive.dart';
import 'package:driver/themes/theme_controller.dart';
import 'package:driver/utils/fire_store_utils.dart';
import 'package:driver/utils/network_image_widget.dart';
import 'package:driver/widget/firebase_pagination/src/firestore_pagination.dart';
import 'package:driver/widget/firebase_pagination/src/models/view_type.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class DriverInboxScreen extends StatelessWidget {
const DriverInboxScreen({super.key});
@override
Widget build(BuildContext context) {
final themeController = Get.find<ThemeController>();
final isDark = themeController.isDark.value;
return Scaffold(
body: FirestorePagination(
//item builder type is compulsory.
physics: const BouncingScrollPhysics(),
itemBuilder: (context, documentSnapshots, index) {
final data = documentSnapshots[index].data() as Map<String, dynamic>?;
InboxModel inboxModel = InboxModel.fromJson(data!);
return InkWell(
onTap: () async {
ShowToastDialog.showLoader("Please wait".tr);
UserModel? customer = await FireStoreUtils.getUserProfile(inboxModel.customerId.toString());
UserModel? restaurantUser = await FireStoreUtils.getUserProfile(inboxModel.restaurantId.toString());
ShowToastDialog.closeLoader();
Get.to(const ChatScreen(), arguments: {
"customerName": customer!.fullName(),
"restaurantName": restaurantUser!.fullName(),
"orderId": inboxModel.orderId,
"restaurantId": restaurantUser.id,
"customerId": customer.id,
"customerProfileImage": customer.profilePictureURL ?? "",
"restaurantProfileImage": restaurantUser.profilePictureURL ?? "",
"token": customer.fcmToken,
"chatType": inboxModel.chatType,
});
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5),
child: Container(
decoration: ShapeDecoration(
color: isDark ? AppThemeData.grey900 : AppThemeData.grey50,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(10)),
child: NetworkImageWidget(
imageUrl: inboxModel.customerProfileImage.toString(),
fit: BoxFit.cover,
height: Responsive.height(6, context),
width: Responsive.width(12, context),
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
"${inboxModel.customerName}",
textAlign: TextAlign.start,
style: TextStyle(
fontFamily: AppThemeData.semiBold,
fontSize: 16,
color: isDark ? AppThemeData.grey100 : AppThemeData.grey800,
),
),
),
Text(
Constant.timestampToDate(inboxModel.createdAt!),
textAlign: TextAlign.start,
style: TextStyle(
fontFamily: AppThemeData.regular,
fontSize: 16,
color: isDark ? AppThemeData.grey400 : AppThemeData.grey500,
),
),
],
),
const SizedBox(
height: 5,
),
Text(
"${inboxModel.lastMessage}",
textAlign: TextAlign.start,
style: TextStyle(
fontFamily: AppThemeData.medium,
fontSize: 14,
color: isDark ? AppThemeData.grey200 : AppThemeData.grey700,
),
)
],
),
),
],
),
),
),
),
);
},
shrinkWrap: true,
onEmpty: Constant.showEmptyView(message: "No Conversion found".tr,isDark: isDark),
// orderBy is compulsory to enable pagination
query: FirebaseFirestore.instance.collection('chat_driver').where("restaurantId", isEqualTo: FireStoreUtils.getCurrentUid()).orderBy('createdAt', descending: true),
//Change types customerId
viewType: ViewType.list,
initialLoader: Constant.loader(),
// to fetch real-time data
isLive: true,
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:photo_view/photo_view.dart';
class FullScreenImageViewer extends StatelessWidget {
final String imageUrl;
final File? imageFile;
const FullScreenImageViewer({super.key, required this.imageUrl, this.imageFile});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.black,
iconTheme: const IconThemeData(color: Colors.white),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
body: Container(
color: Colors.black,
child: Hero(
tag: imageUrl,
child: PhotoView(
imageProvider: imageFile == null ? NetworkImage(imageUrl) : Image.file(imageFile!).image,
),
),
));
}
}

View File

@@ -0,0 +1,75 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';
class FullScreenVideoViewer extends StatefulWidget {
final String videoUrl;
final String heroTag;
final File? videoFile;
const FullScreenVideoViewer({super.key, required this.videoUrl, required this.heroTag, this.videoFile});
@override
_FullScreenVideoViewerState createState() => _FullScreenVideoViewerState();
}
class _FullScreenVideoViewerState extends State<FullScreenVideoViewer> {
late VideoPlayerController _controller;
@override
void initState() {
super.initState();
_controller = widget.videoFile == null ? VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)) : VideoPlayerController.file(widget.videoFile!)
..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
_controller.setLooping(true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.black,
iconTheme: const IconThemeData(color: Colors.white),
systemOverlayStyle: SystemUiOverlayStyle.light,
),
body: Container(
color: Colors.black,
child: Hero(
tag: widget.videoUrl,
child: Center(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
),
)),
floatingActionButton: FloatingActionButton(
heroTag: widget.heroTag,
onPressed: () {
setState(() {
_controller.value.isPlaying ? _controller.pause() : _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? CupertinoIcons.pause : CupertinoIcons.play_arrow_solid,
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
}