896 lines
30 KiB
Dart
896 lines
30 KiB
Dart
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:taxglide/consts/app_style.dart';
|
|
import 'package:taxglide/consts/comman_button.dart';
|
|
import 'package:taxglide/consts/responsive_helper.dart';
|
|
import 'package:taxglide/controller/api_contoller.dart';
|
|
import 'package:taxglide/controller/api_repository.dart';
|
|
|
|
import 'package:taxglide/view/Main_controller/main_controller.dart';
|
|
|
|
class ServiceRequestScreen extends ConsumerStatefulWidget {
|
|
final int id;
|
|
final String service;
|
|
final String icon;
|
|
final int? returnTabIndex;
|
|
|
|
const ServiceRequestScreen({
|
|
super.key,
|
|
required this.id,
|
|
required this.service,
|
|
required this.icon,
|
|
this.returnTabIndex,
|
|
});
|
|
|
|
@override
|
|
ConsumerState<ServiceRequestScreen> createState() =>
|
|
_ServiceRequestScreenState();
|
|
}
|
|
|
|
class _ServiceRequestScreenState extends ConsumerState<ServiceRequestScreen> {
|
|
final List<File> _selectedFiles = [];
|
|
final TextEditingController _messageController = TextEditingController();
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
bool _isFileError = false;
|
|
bool _isTermsAccepted = false;
|
|
bool _isCheckboxError = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_messageController.dispose();
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _showSnackBar(String msg, {bool isError = false}) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
duration: const Duration(seconds: 2),
|
|
backgroundColor: isError ? Colors.red : Colors.green,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
elevation: 6,
|
|
margin: const EdgeInsets.symmetric(horizontal: 50, vertical: 25),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
|
content: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
isError ? Icons.error_outline : Icons.check_circle_outline,
|
|
color: Colors.white,
|
|
size: 18,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
msg,
|
|
style: AppTextStyles.regular.copyWith(color: Colors.white, fontSize: 12),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 2,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
bool _validateForm() {
|
|
setState(() {
|
|
_isFileError = false;
|
|
_isCheckboxError = false;
|
|
});
|
|
|
|
bool isValid = true;
|
|
// if (_selectedFiles.isEmpty) {
|
|
// setState(() => _isFileError = true);
|
|
// _showSnackBar("Please upload at least one file", isError: true);
|
|
// isValid = false;
|
|
// }
|
|
if (!_isTermsAccepted) {
|
|
setState(() => _isCheckboxError = true);
|
|
_showSnackBar("Please accept terms and conditions", isError: true);
|
|
isValid = false;
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
Future<void> _submitRequest() async {
|
|
if (_validateForm()) {
|
|
try {
|
|
_showSnackBar("Submitting request...");
|
|
await ApiRepository().updateServiceRequest(
|
|
serviceId: widget.id,
|
|
message: _messageController.text.trim(),
|
|
documents: _selectedFiles,
|
|
);
|
|
_showSnackBar("Request submitted successfully!");
|
|
ref.invalidate(serviceHistoryNotifierProvider);
|
|
Get.offAll(() => const MainController());
|
|
} catch (e) {
|
|
debugPrint("❌ Submit error: $e");
|
|
_showSnackBar("Something went wrong. Please try again.", isError: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _pickFile() async {
|
|
final choice = await showDialog<String>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
title: const Text('Select File Type'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ListTile(
|
|
leading: const Icon(Icons.camera_alt, color: Colors.blue),
|
|
title: const Text('Camera'),
|
|
onTap: () => Navigator.pop(ctx, 'camera'),
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.photo, color: Colors.blue),
|
|
title: const Text('Gallery'),
|
|
onTap: () => Navigator.pop(ctx, 'gallery'),
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.insert_drive_file, color: Colors.blue),
|
|
title: const Text('Files'),
|
|
onTap: () => Navigator.pop(ctx, 'any'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
if (choice == null) return;
|
|
|
|
try {
|
|
if (choice == 'camera') {
|
|
final hasPermission = await _requestPermission(ImageSource.camera);
|
|
if (!hasPermission) return;
|
|
final picked = await ImagePicker().pickImage(
|
|
source: ImageSource.camera,
|
|
);
|
|
if (picked != null) {
|
|
setState(() {
|
|
_selectedFiles.add(File(picked.path));
|
|
_isFileError = false;
|
|
});
|
|
}
|
|
} else if (choice == 'gallery') {
|
|
final hasPermission = await _requestPermission(ImageSource.gallery);
|
|
if (!hasPermission) return;
|
|
final picked = await ImagePicker().pickMultiImage();
|
|
if (picked.isNotEmpty) {
|
|
setState(() {
|
|
for (var image in picked) {
|
|
_selectedFiles.add(File(image.path));
|
|
}
|
|
_isFileError = false;
|
|
});
|
|
}
|
|
} else if (choice == 'any') {
|
|
final result = await FilePicker.platform.pickFiles(
|
|
allowMultiple: true,
|
|
type: FileType.any,
|
|
);
|
|
if (result != null && result.files.isNotEmpty) {
|
|
for (var file in result.files) {
|
|
if (file.path != null) {
|
|
setState(() {
|
|
_selectedFiles.add(File(file.path!));
|
|
_isFileError = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint("❌ File picking error: $e");
|
|
_showSnackBar("Failed to pick file: $e", isError: true);
|
|
}
|
|
}
|
|
|
|
Future<void> _showSettingsDialog(String permission) async {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: Text('$permission Permission Required'),
|
|
content: Text(
|
|
'Allow $permission access to capture and upload your documents easily. You can enable it in the app settings.',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: const Text('Cancel'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
openAppSettings();
|
|
},
|
|
child: const Text('Go to Settings'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<bool> _requestPermission(ImageSource source) async {
|
|
try {
|
|
Permission permission;
|
|
String name;
|
|
|
|
if (source == ImageSource.camera) {
|
|
permission = Permission.camera;
|
|
name = 'Camera';
|
|
} else {
|
|
if (Platform.isAndroid) {
|
|
final info = await DeviceInfoPlugin().androidInfo;
|
|
if (info.version.sdkInt >= 33) {
|
|
permission = Permission.photos;
|
|
name = 'Photos';
|
|
} else {
|
|
permission = Permission.storage;
|
|
name = 'Storage';
|
|
}
|
|
} else {
|
|
permission = Permission.photos;
|
|
name = 'Photos';
|
|
}
|
|
}
|
|
|
|
var status = await permission.status;
|
|
|
|
print("status $status");
|
|
status = await permission.request();
|
|
if (status.isGranted || status.isLimited) return true;
|
|
if (status.isPermanentlyDenied || (Platform.isIOS && status.isDenied)) {
|
|
await _showSettingsDialog(name);
|
|
return false;
|
|
}
|
|
|
|
await _showSettingsDialog(name);
|
|
return false;
|
|
} catch (e) {
|
|
debugPrint("Permission error: $e");
|
|
return Platform.isIOS;
|
|
}
|
|
}
|
|
|
|
bool _isImage(String pathStr) {
|
|
final ext = path.extension(pathStr).toLowerCase();
|
|
return ['.jpg', '.jpeg', '.png', '.gif', '.webp'].contains(ext);
|
|
}
|
|
|
|
bool _isPdf(String pathStr) =>
|
|
path.extension(pathStr).toLowerCase() == '.pdf';
|
|
|
|
bool _isZip(String pathStr) =>
|
|
['.zip', '.rar', '.7z'].contains(path.extension(pathStr).toLowerCase());
|
|
|
|
bool _isExcel(String pathStr) =>
|
|
['.xls', '.xlsx'].contains(path.extension(pathStr).toLowerCase());
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final r = ResponsiveUtils(context);
|
|
|
|
// Responsive values
|
|
final hPadding = r.spacing(mobile: 10, tablet: 16, desktop: 24);
|
|
final hPaddingInner = r.spacing(mobile: 20, tablet: 24, desktop: 32);
|
|
final headerFontSize = r.fontSize(mobile: 20, tablet: 24, desktop: 28);
|
|
final labelFontSize = r.fontSize(mobile: 14, tablet: 16, desktop: 18);
|
|
final readOnlyFontSize = r.fontSize(mobile: 13, tablet: 14, desktop: 15);
|
|
final uploadFontSize = r.fontSize(mobile: 11.5, tablet: 12.78, desktop: 14);
|
|
final uploadSubFontSize = r.fontSize(mobile: 9, tablet: 10, desktop: 11);
|
|
final checkboxFontSize = r.fontSize(
|
|
mobile: 11.5,
|
|
tablet: 12.78,
|
|
desktop: 14,
|
|
);
|
|
final messageFontSize = r.fontSize(
|
|
mobile: 10.5,
|
|
tablet: 11.31,
|
|
desktop: 12.5,
|
|
);
|
|
final serviceNameFontSize = r.fontSize(mobile: 13, tablet: 14, desktop: 15);
|
|
final uploadedCountFontSize = r.fontSize(
|
|
mobile: 13,
|
|
tablet: 14,
|
|
desktop: 15,
|
|
);
|
|
|
|
final serviceIconContainerW = r.getValue<double>(
|
|
mobile: 90,
|
|
tablet: 110,
|
|
desktop: 130,
|
|
);
|
|
final serviceIconContainerH = r.getValue<double>(
|
|
mobile: 70,
|
|
tablet: 84,
|
|
desktop: 100,
|
|
);
|
|
final serviceIconSize = r.getValue<double>(
|
|
mobile: 36,
|
|
tablet: 42,
|
|
desktop: 50,
|
|
);
|
|
|
|
final messageBoxHeight = r.getValue<double>(
|
|
mobile: 100,
|
|
tablet: 117,
|
|
desktop: 140,
|
|
);
|
|
final fileCardWidth = r.getValue<double>(
|
|
mobile: 90,
|
|
tablet: 110,
|
|
desktop: 130,
|
|
);
|
|
final fileCardHeight = r.getValue<double>(
|
|
mobile: 100,
|
|
tablet: 120,
|
|
desktop: 140,
|
|
);
|
|
final fileIconSize = r.getValue<double>(
|
|
mobile: 34,
|
|
tablet: 40,
|
|
desktop: 46,
|
|
);
|
|
|
|
final spacingSM = r.spacing(mobile: 10, tablet: 10, desktop: 12);
|
|
final spacingMD = r.spacing(mobile: 16, tablet: 20, desktop: 24);
|
|
final spacingLG = r.spacing(mobile: 20, tablet: 20, desktop: 24);
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color.fromARGB(255, 230, 229, 229),
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: hPadding,
|
|
vertical: 21,
|
|
),
|
|
child: SizedBox(
|
|
height: 50,
|
|
width: double.infinity,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Text(
|
|
"New Service Request",
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: headerFontSize,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 0,
|
|
child: GestureDetector(
|
|
onTap: () => Get.offAll(() => const MainController()),
|
|
child: const Icon(Icons.arrow_back_ios_rounded),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
SizedBox(height: spacingLG),
|
|
|
|
// Service icon and name
|
|
Center(
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: serviceIconContainerW,
|
|
height: serviceIconContainerH,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: const Color(0xFFE1E1E1)),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x66757575),
|
|
offset: Offset(0, 1),
|
|
blurRadius: 7.3,
|
|
),
|
|
],
|
|
),
|
|
padding: const EdgeInsets.all(8),
|
|
child: Image.network(
|
|
widget.icon,
|
|
width: serviceIconSize,
|
|
height: serviceIconSize,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Icon(
|
|
Icons.error_outline,
|
|
color: Colors.red.shade300,
|
|
size: serviceIconSize * 0.8,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
SizedBox(height: spacingMD),
|
|
Text(
|
|
widget.service,
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: serviceNameFontSize,
|
|
color: const Color(0xFF3F3F3F),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
SizedBox(height: spacingLG),
|
|
|
|
// Request Type
|
|
_buildLabel("Request Type*", hPaddingInner, labelFontSize),
|
|
_buildReadOnlyBox(
|
|
widget.service,
|
|
hPaddingInner,
|
|
readOnlyFontSize,
|
|
),
|
|
|
|
SizedBox(height: spacingSM),
|
|
|
|
// File Upload Header
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: hPaddingInner),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"File Upload",
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: labelFontSize,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
GestureDetector(
|
|
onTap: _pickFile,
|
|
child: Container(
|
|
width: 20,
|
|
height: 20,
|
|
decoration: const BoxDecoration(
|
|
color: Colors.black,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
Icons.add,
|
|
size: 18,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: spacingLG),
|
|
|
|
// Upload Box
|
|
_buildUploadBox(hPaddingInner, uploadFontSize, uploadSubFontSize),
|
|
|
|
// Selected Files
|
|
if (_selectedFiles.isNotEmpty)
|
|
_buildEnhancedSelectedFiles(
|
|
hPaddingInner,
|
|
fileCardWidth,
|
|
fileCardHeight,
|
|
fileIconSize,
|
|
uploadedCountFontSize,
|
|
),
|
|
SizedBox(height: spacingLG),
|
|
|
|
// Message
|
|
_buildLabel("Message", hPaddingInner, labelFontSize),
|
|
_buildMessageBox(
|
|
hPaddingInner,
|
|
messageBoxHeight,
|
|
messageFontSize,
|
|
),
|
|
|
|
// Terms Checkbox
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: hPadding),
|
|
child: Row(
|
|
children: [
|
|
Checkbox(
|
|
value: _isTermsAccepted,
|
|
onChanged: (v) {
|
|
setState(() {
|
|
_isTermsAccepted = v ?? false;
|
|
if (v == true) _isCheckboxError = false;
|
|
});
|
|
},
|
|
activeColor: Colors.green,
|
|
side: BorderSide(
|
|
color: _isCheckboxError ? Colors.red : Colors.grey,
|
|
width: _isCheckboxError ? 2 : 1,
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
"I accept the terms and conditions *",
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontStyle: FontStyle.normal,
|
|
fontSize: checkboxFontSize,
|
|
height: 25.56 / 12.78,
|
|
letterSpacing: 0.8,
|
|
color: _isCheckboxError
|
|
? Colors.red
|
|
: const Color(0xFF4F4C4C),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Submit Button
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: r.spacing(mobile: 30, tablet: 41, desktop: 60),
|
|
vertical: spacingLG,
|
|
),
|
|
child: CommanButton(
|
|
text: 'Submit Request',
|
|
onPressed: _submitRequest,
|
|
),
|
|
),
|
|
|
|
SizedBox(height: r.spacing(mobile: 40, tablet: 60, desktop: 80)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLabel(String title, double hPadding, double fontSize) => Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: hPadding),
|
|
child: Text(
|
|
title,
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: fontSize,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
);
|
|
|
|
Widget _buildReadOnlyBox(String text, double hPadding, double fontSize) =>
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: hPadding, vertical: 15),
|
|
child: Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(6),
|
|
border: Border.all(color: const Color(0xFFDFDFDF)),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x66BDBDBD),
|
|
offset: Offset(0, 1),
|
|
blurRadius: 7,
|
|
),
|
|
],
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 21),
|
|
child: Text(
|
|
text,
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: fontSize,
|
|
color: const Color(0xFF3F3F3F),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Widget _buildUploadBox(
|
|
double hPadding,
|
|
double fontSize,
|
|
double subFontSize,
|
|
) => Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: hPadding),
|
|
child: Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(6),
|
|
border: Border.all(
|
|
color: const Color(0xFFDFDFDF),
|
|
width: 1,
|
|
),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 21),
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
"Upload Documents or Images",
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: fontSize,
|
|
height: 25.56 / 12.78,
|
|
letterSpacing: 0.8,
|
|
color: const Color(0xFF4F4C4C),
|
|
),
|
|
),
|
|
Text(
|
|
"(40 MB only allowed File)",
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: subFontSize,
|
|
height: 25.56 / 12.78,
|
|
letterSpacing: 0.8,
|
|
color: const Color(0xFF4F4C4C),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
Widget _buildEnhancedSelectedFiles(
|
|
double hPadding,
|
|
double cardWidth,
|
|
double cardHeight,
|
|
double iconSize,
|
|
double countFontSize,
|
|
) => Container(
|
|
margin: EdgeInsets.symmetric(horizontal: hPadding, vertical: 10),
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: const Color(0xFFE1E1E1)),
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
color: Color(0x1A000000),
|
|
offset: Offset(0, 2),
|
|
blurRadius: 8,
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(6),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
Icons.check_circle,
|
|
color: Colors.green.shade600,
|
|
size: 18,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"Uploaded Files (${_selectedFiles.length})",
|
|
style: AppTextStyles.semiBold.copyWith(
|
|
fontSize: countFontSize,
|
|
color: const Color(0xFF111827),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (_selectedFiles.length > 2)
|
|
Row(
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () {
|
|
_scrollController.animateTo(
|
|
_scrollController.offset - 120,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade200,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
Icons.arrow_back_ios_new,
|
|
size: 14,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
GestureDetector(
|
|
onTap: () {
|
|
_scrollController.animateTo(
|
|
_scrollController.offset + 120,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade200,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
Icons.arrow_forward_ios,
|
|
size: 14,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
SizedBox(
|
|
height: cardHeight,
|
|
child: ListView.builder(
|
|
controller: _scrollController,
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: _selectedFiles.length,
|
|
itemBuilder: (context, index) {
|
|
final file = _selectedFiles[index];
|
|
final pathStr = file.path;
|
|
|
|
Widget preview;
|
|
Color bgColor;
|
|
|
|
if (_isImage(pathStr)) {
|
|
preview = ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Image.file(
|
|
file,
|
|
fit: BoxFit.cover,
|
|
width: cardWidth,
|
|
height: cardHeight,
|
|
),
|
|
);
|
|
bgColor = Colors.transparent;
|
|
} else if (_isPdf(pathStr)) {
|
|
bgColor = Colors.red.shade50;
|
|
preview = Icon(
|
|
Icons.picture_as_pdf,
|
|
size: iconSize,
|
|
color: Colors.red.shade600,
|
|
);
|
|
} else if (_isZip(pathStr)) {
|
|
bgColor = Colors.orange.shade50;
|
|
preview = Icon(
|
|
Icons.folder_zip,
|
|
size: iconSize,
|
|
color: Colors.orange.shade600,
|
|
);
|
|
} else if (_isExcel(pathStr)) {
|
|
bgColor = Colors.green.shade50;
|
|
preview = Icon(
|
|
Icons.table_chart,
|
|
size: iconSize,
|
|
color: Colors.green.shade600,
|
|
);
|
|
} else {
|
|
bgColor = Colors.blue.shade50;
|
|
preview = Icon(
|
|
Icons.insert_drive_file,
|
|
size: iconSize,
|
|
color: Colors.blue.shade600,
|
|
);
|
|
}
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 12),
|
|
child: Stack(
|
|
children: [
|
|
Container(
|
|
width: cardWidth,
|
|
decoration: BoxDecoration(
|
|
color: bgColor,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(
|
|
color: Colors.grey.shade300,
|
|
width: 1.5,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
offset: const Offset(0, 2),
|
|
blurRadius: 4,
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [Expanded(child: Center(child: preview))],
|
|
),
|
|
),
|
|
Positioned(
|
|
right: -2,
|
|
top: -2,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_selectedFiles.removeAt(index);
|
|
if (_selectedFiles.isEmpty) _isFileError = true;
|
|
});
|
|
},
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.shade600,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.2),
|
|
offset: const Offset(0, 2),
|
|
blurRadius: 4,
|
|
),
|
|
],
|
|
),
|
|
padding: const EdgeInsets.all(4),
|
|
child: const Icon(
|
|
Icons.close,
|
|
size: 14,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
Widget _buildMessageBox(double hPadding, double boxHeight, double fontSize) =>
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: hPadding, vertical: 15),
|
|
child: Container(
|
|
width: double.infinity,
|
|
height: boxHeight,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: const Color(0xFFCFCFCF)),
|
|
),
|
|
child: TextFormField(
|
|
controller: _messageController,
|
|
maxLines: null,
|
|
expands: true,
|
|
style: AppTextStyles.regular.copyWith(
|
|
fontSize: fontSize,
|
|
color: const Color(0xFF6C7278),
|
|
),
|
|
decoration: const InputDecoration(
|
|
hintText: "Enter your message",
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|