Houshan-Basa/lib/ui/screens/ticket/ticket_page.dart

861 lines
47 KiB
Dart

// ignore_for_file: use_build_context_synchronously, deprecated_member_use_from_same_package
import 'package:cross_file/cross_file.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hoshan/core/gen/assets.gen.dart';
import 'package:hoshan/core/services/file_manager/pick_file_services.dart';
import 'package:hoshan/core/utils/date_time.dart';
import 'package:hoshan/core/utils/file.dart';
import 'package:hoshan/core/utils/strings.dart';
import 'package:hoshan/data/model/empty_states_enum.dart';
import 'package:hoshan/data/model/ticket_model.dart';
import 'package:hoshan/ui/screens/ticket/bloc/send_ticket_bloc.dart';
import 'package:hoshan/ui/theme/colors.dart';
import 'package:hoshan/ui/theme/responsive.dart';
import 'package:hoshan/ui/theme/text.dart';
import 'package:hoshan/ui/widgets/components/audio/player.dart';
import 'package:hoshan/ui/widgets/components/audio/recorder.dart';
import 'package:hoshan/ui/widgets/components/button/circle_icon_btn.dart';
import 'package:hoshan/ui/widgets/components/dialog/bottom_sheets.dart';
import 'package:hoshan/ui/widgets/components/dialog/dialog_handler.dart';
import 'package:hoshan/ui/widgets/components/dropdown/more_popup_menu.dart';
import 'package:hoshan/ui/widgets/components/image/custome_image.dart';
import 'package:hoshan/ui/widgets/components/image/network_image.dart';
import 'package:hoshan/ui/widgets/components/snackbar/snackbar_manager.dart';
import 'package:hoshan/ui/widgets/sections/empty/empty_states.dart';
import 'package:hoshan/ui/widgets/sections/header/reversible_appbar.dart';
import 'package:hoshan/ui/widgets/sections/loading/random_container.dart';
class TicketPage extends StatefulWidget {
const TicketPage({super.key});
@override
State<TicketPage> createState() => _TicketPageState();
}
class _TicketPageState extends State<TicketPage> {
ValueNotifier<bool> visibleAttach = ValueNotifier(false);
ValueNotifier<bool> visibleRecorder = ValueNotifier(false);
ValueNotifier<XFile?> selectedFile = ValueNotifier(null);
final TextEditingController message = TextEditingController();
final GlobalKey containerKey = GlobalKey();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ReversibleAppbar(
context,
titleText: 'ارسال تیکت به پشتیبانی',
),
body: Responsive(context).maxWidthInDesktop(
child: (contxet, maxWidth) =>
BlocBuilder<SendTicketBloc, SendTicketState>(
builder: (context, state) {
if (state is SendTicketInitial) {
return ListView.builder(
shrinkWrap: true,
itemCount: 20,
reverse: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 24, bottom: 90),
itemBuilder: (context, index) {
return RandomContainer(isUser: index % 2 == 0);
},
);
}
return state.tickets.isEmpty
? EmptyStates.getEmptyState(status: EmptyStatesEnum.inbox)
: SingleChildScrollView(
reverse: true,
physics: const BouncingScrollPhysics(),
child: ListView.builder(
shrinkWrap: true,
itemCount: state.tickets.length,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 24, bottom: 90),
itemBuilder: (context, index) {
final ticketList = state.tickets[index].tickets;
return Column(
children: [
Row(
children: [
const Expanded(child: Divider()),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0),
child: Text(
state.tickets[index].date ?? '',
style: AppTextStyles.body4.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface),
textDirection: TextDirection.rtl,
),
),
const Expanded(child: Divider()),
],
),
const SizedBox(
height: 8,
),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
itemCount: ticketList.length,
shrinkWrap: true,
itemBuilder: (context, tIndex) {
Ticket ticket = ticketList[tIndex];
return chatBubble(ticket, maxWidth,
state.tickets[index].date ?? '');
},
),
],
);
},
),
);
},
),
),
bottomSheet: chatBar(),
);
}
Container chatBar() {
return Container(
key: containerKey,
color: Theme.of(context).colorScheme.surface,
child: ValueListenableBuilder(
valueListenable: selectedFile,
builder: (context, file, child) {
return ValueListenableBuilder(
valueListenable: visibleRecorder,
builder: (context, inRrecording, child) {
return inRrecording
? Container(
margin: const EdgeInsets.symmetric(
horizontal: 18.0, vertical: 12),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.surface),
child: Recorder(
play: true,
onDelete: () {
visibleRecorder.value = false;
selectedFile.value = null;
visibleAttach.value = false;
},
onRecordFinish: (fileRecorded) {
visibleRecorder.value = false;
visibleAttach.value = false;
selectedFile.value = fileRecorded;
},
onError: (p0) {
visibleRecorder.value = false;
selectedFile.value = null;
visibleAttach.value = false;
},
),
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
ValueListenableBuilder(
valueListenable: visibleAttach,
builder: (context, show, child) {
return show
? Container(
margin: const EdgeInsets.fromLTRB(
32, 24, 16, 24)
.copyWith(bottom: 0),
child: Row(
children: [
CircleIconBtn(
icon: Assets
.icon.outline.galleryAdd,
color: Theme.of(context)
.colorScheme
.primary,
iconColor: Colors.white,
onTap: () async {
await BottomSheetHandler(
context)
.showPickImage(
onSelect: (file) {
selectedFile.value = file;
visibleAttach.value = false;
},
);
}),
const SizedBox(
width: 8,
),
CircleIconBtn(
icon: Assets.icon.outline.musicnote,
color: Theme.of(context)
.colorScheme
.primary,
iconColor: Colors.white,
onTap: () async {
final file =
await PickFileService(context)
.getFile(
fileType:
FileType.audio);
if (file != null) {
selectedFile.value =
file.single;
visibleAttach.value = false;
}
},
),
const SizedBox(
width: 8,
),
CircleIconBtn(
icon: Assets.icon.outline.cardAdd,
color: Theme.of(context)
.colorScheme
.primary,
iconColor: Colors.white,
onTap: () async {
final file =
await PickFileService(
context)
.getFile(
fileType:
FileType.custom,
allowedExtensions: [
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'xlsm',
'xlsb',
'xlt',
'xltx',
'xltm'
]);
if (file != null) {
selectedFile.value =
file.single;
visibleAttach.value = false;
}
})
],
),
)
: const SizedBox.shrink();
},
),
if (file != null)
Container(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 18),
margin:
const EdgeInsets.all(16).copyWith(bottom: 0),
decoration: BoxDecoration(
color: AppColors.primaryColor.defaultShade,
borderRadius: BorderRadius.circular(16)
.copyWith(topRight: Radius.zero)),
child: file.isAudio()
? Player(
fileUrl: file.path,
inMessages: true,
)
: Row(
children: [
const SizedBox(
width: 8,
),
Expanded(
child: Text(
file.name,
style: AppTextStyles.body4.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface),
)),
const SizedBox(
width: 8,
),
SizedBox(
width: 46,
child: AspectRatio(
aspectRatio: 3 / 4,
child: ClipRRect(
borderRadius:
BorderRadius.circular(10),
child: file.isImage()
? GestureDetector(
onTap: () =>
DialogHandler(
context:
context)
.showImageHero(
image: file
.path),
child: SizedBox(
child: CustomeImage(
src: file.path,
fit: BoxFit.cover,
)),
)
: Container(
color: Colors.white,
child: const Icon(
CupertinoIcons.doc)),
),
),
),
],
),
),
Directionality(
textDirection: TextDirection.rtl,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding:
const EdgeInsets.only(bottom: 8.0),
child:
// ValueListenableBuilder(
// valueListenable: message,
// builder: (context, val, _) {
// if (val.text.isEmpty) {
// return CircleIconBtn(
// icon: Assets
// .icon.outline.microphoneChat,
// color: Colors.white,
// onTap: () {
// visibleRecorder.value = true;
// },
// );
// }
// return
ValueListenableBuilder(
valueListenable: message,
builder:
(context, controller, _) {
return controller
.text.isEmpty &&
file == null
? CircleIconBtn(
icon: Assets
.icon
.outline
.microphoneChat,
color:
Theme.of(context)
.colorScheme
.primary,
iconColor:
Colors.white,
onTap: () async {
visibleRecorder
.value = true;
})
: CircleIconBtn(
icon: Assets
.icon.bold.send,
color: Colors.white,
onTap: () async {
//SEND MESSAGE
context
.read<
SendTicketBloc>()
.add(SendTicket(
text: message
.text,
file: selectedFile
.value));
message.clear();
selectedFile.value =
null;
},
);
})
// ;
// }),
),
const SizedBox(
width: 4,
),
Expanded(
child: ValueListenableBuilder(
valueListenable: message,
builder: (context, val, _) {
return Directionality(
textDirection:
val.text.startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
child: TextField(
controller: message,
onChanged: (value) {},
// enabled: ,
minLines: 1,
style: AppTextStyles.body4
.copyWith(
color:
Theme.of(context)
.colorScheme
.onSurface),
maxLines: 6, // Set this
keyboardType:
TextInputType.multiline,
decoration: InputDecoration(
filled: true,
hintText:
'بنویسید یا پیام صوتی بگذارید...',
hintStyle:
AppTextStyles.body4,
fillColor: Theme.of(context)
.scaffoldBackgroundColor,
contentPadding:
const EdgeInsets
.fromLTRB(
18, 12, 18, 12),
border: OutlineInputBorder(
borderSide:
BorderSide.none,
borderRadius:
BorderRadius.circular(
24),
),
),
),
);
})),
const SizedBox(
width: 4,
),
Padding(
padding:
const EdgeInsets.only(bottom: 8.0),
child: CircleIconBtn(
icon: file != null
? Assets.icon.outline.trash
: Assets.icon.outline.elementPlus,
color: Theme.of(context)
.colorScheme
.primary,
iconColor: Colors.white,
onTap: () {
if (file != null) {
selectedFile.value = null;
return;
}
visibleAttach.value =
!visibleAttach.value;
},
),
),
/*
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
ValueListenableBuilder(
valueListenable: visibleAttach,
builder: (context, isVisible, _) {
return AnimatedVisibility(
isVisible: isVisible,
duration: const Duration(
milliseconds: 200),
child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 4.0),
child: Row(children: [
CircleIconBtn(
icon: Assets.icon.outline
.galleryAdd,
color: Colors.white,
onTap: () async {
await BottomSheetHandler(
context)
.showPickImage(
onSelect: (file) {
selectedFile.value =
file;
},
);
}),
Padding(
padding:
const EdgeInsets.only(
right: 4.0),
child: CircleIconBtn(
icon: Assets.icon.outline
.musicnote,
color: Colors.white,
onTap: () async {
final file =
await PickFileService
.getFile(
fileType:
FileType
.audio);
if (file != null) {
selectedFile.value =
file.single;
}
},
),
),
Padding(
padding:
const EdgeInsets.only(
right: 4.0),
child: CircleIconBtn(
icon: Assets.icon
.outline.cardAdd,
color: Colors.white,
onTap: () async {
final file =
await PickFileService.getFile(
fileType:
FileType
.custom,
allowedExtensions: [
'pdf'
]);
if (file != null) {
selectedFile.value =
file.single;
}
}),
),
]),
));
}),
CircleIconBtn(
icon: Assets.icon.outline.elementPlus,
color: Colors.white,
onTap: () => visibleAttach.value =
!visibleAttach.value,
),
],
),
)*/
],
)),
)
],
);
},
);
}),
);
}
Widget chatBubble(Ticket ticket, double maxWidthDesktop, String date) {
final GlobalKey containerKey = GlobalKey();
final isUser = ticket.role == 'user';
XFile? localFile = ticket.localFile;
String? fileUrl = ticket.file;
return GestureDetector(
onLongPress: () {
MorePopupMenuHandler(context: context).showMorePopupMenu(
containerKey: containerKey,
color: isUser
? AppColors.primaryColor.defaultShade
: Theme.of(context).colorScheme.surface,
items: [
if (isUser)
PopUpMenuItemModel(
popupMenuItem: PopupMenuItem(
value: 0,
child: MorePopupMenuHandler.morePopUpItem(
color: isUser
? Colors.white
: Theme.of(context).colorScheme.primary,
icon: Assets.icon.outline.trash,
title: 'حذف')),
click: () {
try {
context
.read<SendTicketBloc>()
.add(DeleteTicket(id: ticket.id!, date: date));
} catch (e) {
if (kDebugMode) {
print("Error when delete message: $e");
}
}
},
),
if (ticket.text != null && ticket.text!.isNotEmpty)
PopUpMenuItemModel(
popupMenuItem: PopupMenuItem<int>(
value: 1,
child: MorePopupMenuHandler.morePopUpItem(
color: isUser
? Colors.white
: Theme.of(context).colorScheme.primary,
icon: Assets.icon.outline.copy,
title: 'کپی',
),
),
click: () async {
await Clipboard.setData(
ClipboardData(text: ticket.text ?? ''));
Future.delayed(
Duration.zero,
() => SnackBarManager(context, id: 'Copy').show(
status: SnackBarStatus.info,
message: 'متن کپی شد 😃',
isTop: false));
},
),
// PopUpMenuItemModel(
// popupMenuItem: PopupMenuItem<int>(
// value: 1,
// child: MorePopupMenuHandler.morePopUpItem(
// icon: Assets.icon.outline.trash,
// title: 'حذف',
// ),
// ),
// click: () async {
// await DialogHandler(context: context).showDeleteItem(
// title: 'پیام مورد نظر پاک شود؟',
// description: '.با این کار اطلاعات شما از بین خواهد رفت',
// onConfirm: () async {},
// );
// },
// )
]);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
key: containerKey,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: ticket.id == -2
? AppColors.red[50]
: isUser
? AppColors.primaryColor.defaultShade
: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16).copyWith(
bottomLeft: isUser
? const Radius.circular(16)
: const Radius.circular(0),
bottomRight: isUser
? const Radius.circular(0)
: const Radius.circular(16)),
),
constraints: BoxConstraints(
maxWidth: MediaQuery.sizeOf(context).width * 0.8),
child: Directionality(
textDirection: (ticket.text ?? '').startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
child: Column(
children: [
localFile != null
? Container(
margin: const EdgeInsets.only(bottom: 8),
child: localFile.name.isImage()
? GestureDetector(
onTap: () => DialogHandler(context: context)
.showImageHero(image: localFile.path),
child: Container(
constraints: BoxConstraints(
maxWidth:
Responsive(context).isMobile()
? MediaQuery.sizeOf(context)
.width *
0.5
: maxWidthDesktop * 0.3,
maxHeight:
Responsive(context).isMobile()
? MediaQuery.sizeOf(context)
.width *
0.6
: maxWidthDesktop * 0.4),
child: ClipRRect(
borderRadius:
BorderRadius.circular(8),
child: CustomeImage(
src: localFile.path,
fit: BoxFit.cover,
)),
),
)
: localFile.name.isAudio()
? Player(
fileUrl: localFile.path,
inMessages: true,
)
: Container(
decoration: BoxDecoration(
color: AppColors.gray.defaultShade,
borderRadius:
BorderRadius.circular(10)),
padding: const EdgeInsets.all(8),
constraints:
const BoxConstraints(minHeight: 64),
child: Row(
children: [
SizedBox(
child: localFile.name.isDocument()
? const Icon(
CupertinoIcons.doc)
: const SizedBox.shrink(),
),
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 12.0),
child: Text(
localFile.name,
textDirection: localFile.name
.startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
style: const TextStyle(
fontSize: 16),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
)),
],
),
),
)
: fileUrl != null
? Container(
margin: const EdgeInsets.only(bottom: 8),
child: fileUrl.isImage()
? GestureDetector(
onTap: () =>
DialogHandler(context: context)
.showImageHero(
image: fileUrl,
isUrl: true),
child: Container(
constraints: BoxConstraints(
maxWidth:
Responsive(context).isMobile()
? maxWidthDesktop * 0.5
: maxWidthDesktop * 0.3,
maxHeight:
Responsive(context).isMobile()
? maxWidthDesktop * 0.6
: maxWidthDesktop * 0.4),
child: ClipRRect(
borderRadius:
BorderRadius.circular(8),
child: ImageNetwork(
url: fileUrl,
fit: BoxFit.cover,
)),
),
)
: fileUrl.isAudio()
? Player(
fileUrl: fileUrl,
inMessages: true,
)
: Container(
decoration: BoxDecoration(
color:
AppColors.gray.defaultShade,
borderRadius:
BorderRadius.circular(10)),
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(
minHeight: 64),
child: Row(
children: [
SizedBox(
child: fileUrl.isDocument()
? const Icon(
CupertinoIcons.doc)
: const SizedBox.shrink(),
),
Expanded(
child: Padding(
padding: const EdgeInsets
.symmetric(
horizontal: 12.0),
child: Text(
fileUrl.split('/').last,
textDirection: fileUrl
.split('/')
.last
.startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
style: const TextStyle(
fontSize: 16),
overflow:
TextOverflow.ellipsis,
maxLines: 2,
),
)),
],
),
),
)
: const SizedBox.shrink(),
Text(
(ticket.text ?? ''),
style: AppTextStyles.body4.copyWith(
color: isUser
? Colors.white
: Theme.of(context).colorScheme.onSurface),
),
],
),
),
),
if (ticket.createdAt != null)
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 0),
child: ticket.id == -1
? const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator())
: Text(
DateTimeUtils.convertToSentTime(ticket.createdAt!),
style: AppTextStyles.body5
.copyWith(color: AppColors.gray[700]),
),
)
],
),
),
);
}
}