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 createState() => _ServiceRequestScreenState(); } class _ServiceRequestScreenState extends ConsumerState { final List _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 _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 _pickFile() async { final choice = await showDialog( 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 _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 _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( mobile: 90, tablet: 110, desktop: 130, ); final serviceIconContainerH = r.getValue( mobile: 70, tablet: 84, desktop: 100, ); final serviceIconSize = r.getValue( mobile: 36, tablet: 42, desktop: 50, ); final messageBoxHeight = r.getValue( mobile: 100, tablet: 117, desktop: 140, ); final fileCardWidth = r.getValue( mobile: 90, tablet: 110, desktop: 130, ); final fileCardHeight = r.getValue( mobile: 100, tablet: 120, desktop: 140, ); final fileIconSize = r.getValue( 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), ), ), ), ); }