staff_policy_upload_pdf
This commit is contained in:
parent
bfe7055dc1
commit
872b4e4649
File diff suppressed because one or more lines are too long
@ -1,4 +1,6 @@
|
|||||||
// import 'dart:io' as html;
|
// import 'dart:io' as html;
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@ -26,6 +28,7 @@ import '../../../../layouts/main_layout.dart';
|
|||||||
import '../../../../layouts/responsive_layout.dart';
|
import '../../../../layouts/responsive_layout.dart';
|
||||||
import '../../../../providers/manager_provider.dart';
|
import '../../../../providers/manager_provider.dart';
|
||||||
import '../../../../providers/quotation_staff_proivder.dart';
|
import '../../../../providers/quotation_staff_proivder.dart';
|
||||||
|
import '../../../../themes/indicators/custom_loader.dart';
|
||||||
import '../../../../themes/indicators/customizd_file_upload.dart';
|
import '../../../../themes/indicators/customizd_file_upload.dart';
|
||||||
import '../../../../themes/indicators/date_field_theme.dart';
|
import '../../../../themes/indicators/date_field_theme.dart';
|
||||||
import '../../../../themes/indicators/text_field_theme.dart';
|
import '../../../../themes/indicators/text_field_theme.dart';
|
||||||
@ -122,15 +125,20 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
"manager_id": managerId,
|
"manager_id": managerId,
|
||||||
// "policy_pdf_file_name": "policy_doc.pdf",
|
// "policy_pdf_file_name": "policy_doc.pdf",
|
||||||
// "policy_payment_receipt_file_name": "receipt_doc.pdf",
|
// "policy_payment_receipt_file_name": "receipt_doc.pdf",
|
||||||
"created_by": userId,
|
// "created_by": userId,
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> dataForPolicyPDFDetails() {
|
||||||
|
final data = {"quotation_id": acceptedQuotationId, "manager_id": managerId};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
List<Map<String, dynamic>> quotationData = [];
|
List<Map<String, dynamic>> quotationData = [];
|
||||||
List<Map<String, dynamic>> originalData = [];
|
List<Map<String, dynamic>> originalData = [];
|
||||||
List<Map<String, dynamic>> filteredData = [];
|
List<Map<String, dynamic>> filteredData = [];
|
||||||
// bool isLoading = false;
|
bool isLoader = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -336,6 +344,50 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatDate(String rawDate) {
|
||||||
|
try {
|
||||||
|
final dateTime = DateTime.parse(rawDate);
|
||||||
|
return DateFormat('dd-MM-yyyy').format(dateTime); // 24-hour format
|
||||||
|
} catch (e) {
|
||||||
|
return rawDate; // fallback if parsing fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateUploadPDFPolicyData(Map<String, dynamic> policyData) {
|
||||||
|
print('updateUploadPDFPolicyData - $policyData');
|
||||||
|
if (!mounted || policyData.isEmpty) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
hasPolicyData = true;
|
||||||
|
choosedPolcyId = policyData['id']?.toString() ?? '';
|
||||||
|
|
||||||
|
controllers["policyNumber"]?.text =
|
||||||
|
policyData['policy_number']?.toString() ?? '';
|
||||||
|
controllers["policyPaymentMode"]?.text =
|
||||||
|
policyData['payment_mode']?.toString() ?? '';
|
||||||
|
controllers["issueDate"]?.text =
|
||||||
|
_formatDate(policyData['issued_date']!.toString()) ?? '';
|
||||||
|
controllers["startDate"]?.text =
|
||||||
|
_formatDate(policyData['start_date']!.toString()) ?? '';
|
||||||
|
controllers["endDate"]?.text =
|
||||||
|
_formatDate(policyData['end_date']!.toString()) ?? '';
|
||||||
|
|
||||||
|
// RC FILE
|
||||||
|
String? rcPath = policyData["policy_pdf_file_name"];
|
||||||
|
if (rcPath != null && rcPath.isNotEmpty) {
|
||||||
|
selectedPDFFileNames = rcPath.split('/').last; // UI filename
|
||||||
|
docUploadedPDFFileUrlFromApi = rcPath; // API download URL
|
||||||
|
docPDFUploadedFile = null;
|
||||||
|
// docUploadedRCFile =
|
||||||
|
// rcFileUrlFromApi as PlatformFile?; // user has not re-uploaded yet
|
||||||
|
} else {
|
||||||
|
selectedPDFFileNames = null; // no UI file shown
|
||||||
|
docUploadedPDFFileUrlFromApi = null;
|
||||||
|
docPDFUploadedFile = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void handleSave() {
|
void handleSave() {
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -344,6 +396,25 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
final dataSet = dataDetails();
|
final dataSet = dataDetails();
|
||||||
print("dataSetPolicy - $dataSet");
|
print("dataSetPolicy - $dataSet");
|
||||||
|
|
||||||
|
// File validations
|
||||||
|
// if ((docPDFUploadedFile == null || docPDFUploadedFile?.bytes == null) &&
|
||||||
|
// (docUploadedPDFFileUrlFromApi == null ||
|
||||||
|
// docUploadedPDFFileUrlFromApi!.isEmpty)) {
|
||||||
|
// ToastHelper.showErrorToast(context, "Policy PDF is required");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
createUserData(dataSet);
|
||||||
|
} else {
|
||||||
|
// isDi sable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUploadPolicy() {
|
||||||
|
setState(() {
|
||||||
|
dataForPolicyPDFDetails();
|
||||||
|
final dataSet = dataForPolicyPDFDetails();
|
||||||
|
print("dataSetPolicy - $dataSet");
|
||||||
// File validations
|
// File validations
|
||||||
if ((docPDFUploadedFile == null || docPDFUploadedFile?.bytes == null) &&
|
if ((docPDFUploadedFile == null || docPDFUploadedFile?.bytes == null) &&
|
||||||
(docUploadedPDFFileUrlFromApi == null ||
|
(docUploadedPDFFileUrlFromApi == null ||
|
||||||
@ -351,19 +422,7 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
ToastHelper.showErrorToast(context, "Policy PDF is required");
|
ToastHelper.showErrorToast(context, "Policy PDF is required");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
createPolicyPDFData(dataSet);
|
||||||
// if ((docUploadedFile == null || docUploadedFile?.bytes == null) &&
|
|
||||||
// (docUploadedFileUrlFromApi == null ||
|
|
||||||
// docUploadedFileUrlFromApi!.isEmpty)) {
|
|
||||||
// ToastHelper.showErrorToast(context, "Payment Receipt is required");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// print("managerId - $managerId ,userId - $userId ");
|
|
||||||
createUserData(dataSet);
|
|
||||||
} else {
|
|
||||||
// isDi sable = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,13 +477,16 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
Future<void> createUserData(Map<String, dynamic> userData) async {
|
Future<void> createUserData(Map<String, dynamic> userData) async {
|
||||||
final bool isUpdating = choosedPolcyId != null;
|
final bool isUpdating = choosedPolcyId != null;
|
||||||
final id = choosedPolcyId;
|
final id = choosedPolcyId;
|
||||||
|
// final id = '1';
|
||||||
// print('UPDId- $id');
|
// print('UPDId- $id');
|
||||||
|
|
||||||
// final bool isUpdating = false;
|
// final bool isUpdating = false;
|
||||||
final uri = Uri.parse(
|
final uri = Uri.parse(
|
||||||
isUpdating
|
'${Env.apiUrl}policy/updatePolicy',
|
||||||
? '${Env.apiUrl}policy/updatePolicy'
|
// isUpdating
|
||||||
: '${Env.apiUrl}policy/createPolicy',
|
// ?
|
||||||
|
// :
|
||||||
|
// '${Env.apiUrl}policy/createPolicy',
|
||||||
);
|
);
|
||||||
if (_token == null) {
|
if (_token == null) {
|
||||||
throw Exception('Token not found. Please log in.');
|
throw Exception('Token not found. Please log in.');
|
||||||
@ -517,14 +579,122 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> createPolicyPDFData(Map<String, dynamic> userData) async {
|
||||||
|
final bool isUpdating = choosedPolcyId != null;
|
||||||
|
final id = choosedPolcyId;
|
||||||
|
// final id = '1';
|
||||||
|
// print('UPDId- $id');
|
||||||
|
|
||||||
|
final uri = Uri.parse('${Env.apiUrl}policy/uploadPolicyFile');
|
||||||
|
if (_token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use MultipartRequest (POST only)
|
||||||
|
final request = http.MultipartRequest('POST', uri);
|
||||||
|
request.headers['Authorization'] = 'Bearer $_token';
|
||||||
|
request.headers['app-signature'] = Env.App_Signature;
|
||||||
|
|
||||||
|
// If updating, spoof the method Laravel-style
|
||||||
|
if (isUpdating) {
|
||||||
|
request.fields['id'] = id!;
|
||||||
|
request.fields['updated_by'] = userId!.toString();
|
||||||
|
} else {
|
||||||
|
request.fields['created_by'] = userId!.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
print("USerDAta - $userData");
|
||||||
|
|
||||||
|
await attachFiles(request);
|
||||||
|
userData.forEach((key, value) {
|
||||||
|
if (key != 'policy_pdf_file_name') {
|
||||||
|
request.fields[key] = value.toString();
|
||||||
|
print("✅ Encoded $key: ${request.fields[key]}");
|
||||||
|
} else {
|
||||||
|
print('Something Missing..');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
print(" Sending request with fields: ${request.fields}");
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (_) => const CustomLoader(
|
||||||
|
message: 'Loading data...',
|
||||||
|
loaderColor: Colors.deepPurple,
|
||||||
|
useBlurBackground: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final streamedResponse = await request.send();
|
||||||
|
final response = await http.Response.fromStream(streamedResponse);
|
||||||
|
print("Response status: ${response.statusCode}");
|
||||||
|
print("Response body: ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
// Close the loader before showing any message
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
// dispose();
|
||||||
|
print("✅ PDF submitted successfully!");
|
||||||
|
ToastHelper.showSuccessToast(context, 'PDF Uploaded');
|
||||||
|
print("Response: ${response.body}");
|
||||||
|
|
||||||
|
final Map<String, dynamic> jsonResponse = jsonDecode(response.body);
|
||||||
|
final policy =
|
||||||
|
jsonResponse['data']?['file_read_data']?['value']?['policy'];
|
||||||
|
if (policy != null) {
|
||||||
|
final extractedData = {
|
||||||
|
'id': jsonResponse['data']?['policy_id']?.toString(),
|
||||||
|
'policy_number': policy['policy_number'],
|
||||||
|
'issued_date': policy['issue_date'],
|
||||||
|
'start_date': policy['period']?['start'],
|
||||||
|
'end_date': policy['period']?['end'],
|
||||||
|
'payment_mode': policy['payment_mode'] ?? 'online',
|
||||||
|
// optional if available
|
||||||
|
'policy_pdf_file_name':
|
||||||
|
jsonResponse['data']?['policy_pdf_file_name'],
|
||||||
|
// optional if your API returns it
|
||||||
|
};
|
||||||
|
|
||||||
|
print('extractedData - $extractedData');
|
||||||
|
updateUploadPDFPolicyData(extractedData);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
await apiService.clearLocalStorageAndRedirect();
|
||||||
|
} else {
|
||||||
|
print("❌ Submission failed. Status: ${response.statusCode}");
|
||||||
|
print("Body: ${response.body}");
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(" Upload Policy Failed"),
|
||||||
|
content: Text(
|
||||||
|
"There was a problem in uploading pdf. Please try again.",
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text("OK"),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("🔥 Error submitting user: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
// height: MediaQuery.of(context).size.height,
|
|
||||||
// width: MediaQuery.of(context).size.width,
|
|
||||||
// padding: const EdgeInsets.all(20),
|
|
||||||
// child: SingleChildScrollView(
|
|
||||||
// scrollDirection: Axis.vertical,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -540,17 +710,12 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
// SingleChildScrollView(child: buildFormFields(context)),
|
|
||||||
buildFormFields(context),
|
buildFormFields(context),
|
||||||
|
if (roleId != 'manager') ...[
|
||||||
// const SizedBox(height: 20),
|
|
||||||
|
|
||||||
//confirm button
|
|
||||||
if (!hasPolicyData && roleId != 'manager') ...[
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
handleSave();
|
handleSave();
|
||||||
},
|
},
|
||||||
@ -565,7 +730,7 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
color: Color(0xFF425B5B),
|
color: Color(0xFF425B5B),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Submit',
|
!hasPolicyData ? 'Submit' : 'Update',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@ -581,46 +746,6 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Container(
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// color: const Color(0xFFEDF6F5),
|
|
||||||
// borderRadius: BorderRadius.circular(10),
|
|
||||||
// ),
|
|
||||||
// padding: const EdgeInsets.all(20),
|
|
||||||
// child: Column(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
// children: [
|
|
||||||
// Text(
|
|
||||||
// "Enquiry Details",
|
|
||||||
// style: GoogleFonts.inter(
|
|
||||||
// fontSize: 16,
|
|
||||||
// fontWeight: FontWeight.w700,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// SizedBox(height: 15),
|
|
||||||
// Row(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
// children: [
|
|
||||||
// Expanded(flex: 2, child: registernumber(context)),
|
|
||||||
// const SizedBox(width: 20),
|
|
||||||
// Expanded(flex: 2, child: insurer(context)),
|
|
||||||
// const SizedBox(width: 20),
|
|
||||||
// Expanded(flex: 2, child: buildIdv(context)),
|
|
||||||
// const SizedBox(width: 20),
|
|
||||||
// Expanded(flex: 2, child: buildPremiumAmnt(context)),
|
|
||||||
// const SizedBox(width: 20),
|
|
||||||
// Expanded(flex: 2, child: buildPlanType(context)),
|
|
||||||
// const SizedBox(width: 20),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
//
|
|
||||||
// const SizedBox(height: 20),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// ),
|
// ),
|
||||||
@ -995,7 +1120,6 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
ThemedFormField(
|
ThemedFormField(
|
||||||
controller: controllers['premium_amount']!,
|
controller: controllers['premium_amount']!,
|
||||||
readOnly: true,
|
|
||||||
// validator: (value) =>
|
// validator: (value) =>
|
||||||
// Validators.requiredField(value, "policyPremiumAmount"),
|
// Validators.requiredField(value, "policyPremiumAmount"),
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
txtwidth: ResponsiveLayout.isMobile(context)
|
||||||
@ -1215,17 +1339,32 @@ class PolicyStaffTabState extends ConsumerState<PolicyStaffTab> {
|
|||||||
children: [
|
children: [
|
||||||
ThemedUploadField(
|
ThemedUploadField(
|
||||||
hintText: selectedPDFFileNames ?? "Upload Document",
|
hintText: selectedPDFFileNames ?? "Upload Document",
|
||||||
|
allowedExtensions: ['pdf'],
|
||||||
txtwidth: ResponsiveLayout.isMobile(context)
|
txtwidth: ResponsiveLayout.isMobile(context)
|
||||||
? null
|
? null
|
||||||
: (docUploadedPDFFileUrlFromApi != null)
|
: (docUploadedPDFFileUrlFromApi != null)
|
||||||
? MediaQuery.of(context).size.width * 0.15
|
? MediaQuery.of(context).size.width * 0.15
|
||||||
: MediaQuery.of(context).size.width * 0.175,
|
: MediaQuery.of(context).size.width * 0.175,
|
||||||
// backgroundColor: Color(0xFFEDF6F5),
|
// backgroundColor: Color(0xFFEDF6F5),
|
||||||
onFileSelected: (fileName, file) {
|
onFileSelected: (fileName, file) async {
|
||||||
|
print("Picked file: $fileName (${file.size} bytes)");
|
||||||
|
|
||||||
|
// if (!fileName.toLowerCase().endsWith('.pdf')) {
|
||||||
|
// ToastHelper.showErrorToast(context, Only PDF files are allowed.)
|
||||||
|
// ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
// const SnackBar(
|
||||||
|
// content: Text('Only PDF files are allowed.'),
|
||||||
|
// backgroundColor: Colors.red,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// return; // Stop here
|
||||||
|
// }
|
||||||
|
|
||||||
print("Picked file: $fileName (${file.size} bytes)");
|
print("Picked file: $fileName (${file.size} bytes)");
|
||||||
setState(() {
|
setState(() {
|
||||||
docPDFUploadedFile = file;
|
docPDFUploadedFile = file;
|
||||||
});
|
});
|
||||||
|
handleUploadPolicy();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
62
lib/presentation/themes/indicators/custom_loader.dart
Normal file
62
lib/presentation/themes/indicators/custom_loader.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomLoader extends StatelessWidget {
|
||||||
|
final String? message;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final double size;
|
||||||
|
final Color loaderColor;
|
||||||
|
final bool useBlurBackground;
|
||||||
|
|
||||||
|
const CustomLoader({
|
||||||
|
super.key,
|
||||||
|
this.message,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.size = 50,
|
||||||
|
this.loaderColor = Colors.blue,
|
||||||
|
this.useBlurBackground = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
if (useBlurBackground) Container(color: Colors.black.withOpacity(0.4)),
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: backgroundColor ?? Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: loaderColor,
|
||||||
|
strokeWidth: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (message != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
message!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -108,6 +108,7 @@ class ThemedUploadField extends StatefulWidget {
|
|||||||
final Color? errorBorderColor;
|
final Color? errorBorderColor;
|
||||||
final Function(String fileName, PlatformFile file)? onFileSelected;
|
final Function(String fileName, PlatformFile file)? onFileSelected;
|
||||||
FormFieldValidator<String>? validator;
|
FormFieldValidator<String>? validator;
|
||||||
|
final List<String>? allowedExtensions;
|
||||||
|
|
||||||
ThemedUploadField({
|
ThemedUploadField({
|
||||||
super.key,
|
super.key,
|
||||||
@ -119,6 +120,7 @@ class ThemedUploadField extends StatefulWidget {
|
|||||||
this.errorBorderColor,
|
this.errorBorderColor,
|
||||||
this.onFileSelected,
|
this.onFileSelected,
|
||||||
this.validator,
|
this.validator,
|
||||||
|
this.allowedExtensions,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -131,7 +133,10 @@ class _ThemedUploadFieldState extends State<ThemedUploadField> {
|
|||||||
String? errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
Future<void> _pickFile() async {
|
Future<void> _pickFile() async {
|
||||||
final error = await fileService.pickSingleFile(maxFileSizeInMB: 3);
|
final error = await fileService.pickSingleFile(
|
||||||
|
maxFileSizeInMB: 3,
|
||||||
|
allowedExtensions: widget.allowedExtensions,
|
||||||
|
);
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
setState(() => errorMessage = error);
|
setState(() => errorMessage = error);
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
@ -162,6 +167,7 @@ class _ThemedUploadFieldState extends State<ThemedUploadField> {
|
|||||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
|
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: _pickFile,
|
onTap: _pickFile,
|
||||||
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
|
|||||||
@ -40,7 +40,8 @@ class _ThemedDateFieldState extends State<ThemedDateField> {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
FormFieldState<String> field,
|
FormFieldState<String> field,
|
||||||
) async {
|
) async {
|
||||||
DateTime initialDate = selectedDate ??
|
DateTime initialDate =
|
||||||
|
selectedDate ??
|
||||||
(widget.controller?.text.isNotEmpty == true
|
(widget.controller?.text.isNotEmpty == true
|
||||||
? DateFormat('dd-MM-yyyy').parse(widget.controller!.text)
|
? DateFormat('dd-MM-yyyy').parse(widget.controller!.text)
|
||||||
: DateTime.now());
|
: DateTime.now());
|
||||||
@ -78,6 +79,7 @@ class _ThemedDateFieldState extends State<ThemedDateField> {
|
|||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => pickDate(context, field),
|
onTap: () => pickDate(context, field),
|
||||||
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: widget.txtwidth ?? MediaQuery.of(context).size.width,
|
width: widget.txtwidth ?? MediaQuery.of(context).size.width,
|
||||||
@ -120,6 +122,7 @@ class _ThemedDateFieldState extends State<ThemedDateField> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
if (hasError)
|
if (hasError)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 5, left: 5),
|
padding: const EdgeInsets.only(top: 5, left: 5),
|
||||||
|
|||||||
@ -7,14 +7,20 @@ class FileUploadService {
|
|||||||
|
|
||||||
PlatformFile? singleFile;
|
PlatformFile? singleFile;
|
||||||
|
|
||||||
static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg'];
|
/// Default allowed extensions
|
||||||
|
static const defaultAllowedExtensions = ['pdf', 'png', 'jpg', 'jpeg'];
|
||||||
|
// static const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg'];
|
||||||
|
|
||||||
Future<String?> pickSingleFile({int maxFileSizeInMB = 5}) async {
|
Future<String?> pickSingleFile({
|
||||||
|
int maxFileSizeInMB = 5,
|
||||||
|
List<String>? allowedExtensions,
|
||||||
|
}) async {
|
||||||
|
final extensions = allowedExtensions ?? defaultAllowedExtensions;
|
||||||
final result = await FilePicker.platform.pickFiles(
|
final result = await FilePicker.platform.pickFiles(
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
withData: true,
|
withData: true,
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: allowedExtensions,
|
allowedExtensions: extensions,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != null && result.files.isNotEmpty) {
|
if (result != null && result.files.isNotEmpty) {
|
||||||
@ -22,7 +28,7 @@ class FileUploadService {
|
|||||||
final ext = file.extension?.toLowerCase() ?? '';
|
final ext = file.extension?.toLowerCase() ?? '';
|
||||||
final sizeInMB = file.size / (1024 * 1024);
|
final sizeInMB = file.size / (1024 * 1024);
|
||||||
|
|
||||||
if (!allowedExtensions.contains(ext)) {
|
if (!extensions.contains(ext)) {
|
||||||
return "Unsupported format: ${file.name}";
|
return "Unsupported format: ${file.name}";
|
||||||
}
|
}
|
||||||
if (sizeInMB > maxFileSizeInMB) {
|
if (sizeInMB > maxFileSizeInMB) {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class ThemedFormField extends HookWidget {
|
|||||||
this.keyboardType, // ✅ new
|
this.keyboardType, // ✅ new
|
||||||
this.maxLength, // ✅ new
|
this.maxLength, // ✅ new
|
||||||
this.inputFormatters,
|
this.inputFormatters,
|
||||||
|
this.isShowCursor,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
@ -38,6 +39,7 @@ class ThemedFormField extends HookWidget {
|
|||||||
final Color? errorTextColor;
|
final Color? errorTextColor;
|
||||||
final double? txtwidth;
|
final double? txtwidth;
|
||||||
final bool readOnly;
|
final bool readOnly;
|
||||||
|
final bool? isShowCursor;
|
||||||
final TextInputType? keyboardType;
|
final TextInputType? keyboardType;
|
||||||
final int? maxLength;
|
final int? maxLength;
|
||||||
final double? txtheight;
|
final double? txtheight;
|
||||||
@ -59,6 +61,7 @@ class ThemedFormField extends HookWidget {
|
|||||||
);
|
);
|
||||||
final obscureBtn = IconButton(
|
final obscureBtn = IconButton(
|
||||||
onPressed: () => isObscured.value = !isObscured.value,
|
onPressed: () => isObscured.value = !isObscured.value,
|
||||||
|
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
isObscured.value
|
isObscured.value
|
||||||
? Icons.visibility_off_outlined
|
? Icons.visibility_off_outlined
|
||||||
@ -143,10 +146,11 @@ class ThemedFormField extends HookWidget {
|
|||||||
readOnly: readOnly, // ✅ now supported
|
readOnly: readOnly, // ✅ now supported
|
||||||
keyboardType: keyboardType, // ✅ e.g. TextInputType.number
|
keyboardType: keyboardType, // ✅ e.g. TextInputType.number
|
||||||
maxLength: maxLength, // ✅ max length
|
maxLength: maxLength, // ✅ max length
|
||||||
|
showCursor: isShowCursor ?? true,
|
||||||
|
enableInteractiveSelection: isShowCursor ?? true,
|
||||||
inputFormatters:
|
inputFormatters:
|
||||||
inputFormatters ??
|
inputFormatters ??
|
||||||
[FilteringTextInputFormatter.allow(RegExp(r'[a-z A-Z]'))],
|
[FilteringTextInputFormatter.allow(RegExp(r'[a-z A-Z]'))],
|
||||||
|
|
||||||
// ✅ allow multiline if user sets maxLines / minLines
|
// ✅ allow multiline if user sets maxLines / minLines
|
||||||
minLines: (keyboardType == TextInputType.multiline) ? 3 : 1,
|
minLines: (keyboardType == TextInputType.multiline) ? 3 : 1,
|
||||||
maxLines: (keyboardType == TextInputType.multiline) ? null : 1,
|
maxLines: (keyboardType == TextInputType.multiline) ? null : 1,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user