778 lines
27 KiB
Dart
778 lines
27 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:intl/intl.dart';
|
|
import 'package:nhancepolicy/logger.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
|
|
import '../config/environment.dart';
|
|
import '../customAppBar/toastHelper.dart';
|
|
import '../service/api_service.dart';
|
|
import '../service/token_storage_service.dart';
|
|
|
|
class NonEBClaimsCreate extends StatefulWidget {
|
|
final BuildContext parentContext;
|
|
final VoidCallback onSuccess;
|
|
|
|
const NonEBClaimsCreate({
|
|
Key? key,
|
|
required this.parentContext,
|
|
required this.onSuccess,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<NonEBClaimsCreate> createState() => _NonEBClaimsCreateState();
|
|
}
|
|
|
|
class _NonEBClaimsCreateState extends State<NonEBClaimsCreate> {
|
|
bool isLoading = false;
|
|
dynamic empClientId;
|
|
dynamic empClientBranchId;
|
|
final tokenService = TokenStorageService();
|
|
String? _postPreToken = '';
|
|
List<Map<String, dynamic>> policyNumberList = [];
|
|
int? policyNumberId;
|
|
bool isSubmitting = false;
|
|
Map<String, dynamic> getClaimPoliciesApi = {};
|
|
int? selectedClientPolicyId;
|
|
bool isPolicyValid = true;
|
|
bool isNatureOfLossValid = true;
|
|
bool isLossLocationValid = true;
|
|
bool isLossDateValid = true;
|
|
bool isLossDescriptionValid = true;
|
|
|
|
final TextEditingController natureOfLossController = TextEditingController();
|
|
final TextEditingController lossLocationController = TextEditingController();
|
|
final TextEditingController lossDescriptionController =
|
|
TextEditingController();
|
|
DateTime? lossDate;
|
|
PlatformFile? selectedAssetFile;
|
|
late ApiService apiService;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
apiService = ApiService(context);
|
|
_loadToken();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
natureOfLossController.dispose();
|
|
lossLocationController.dispose();
|
|
lossDescriptionController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadToken() async {
|
|
_postPreToken = tokenService.getCurrentToken();
|
|
empClientId = await tokenService.readValue('empClientId');
|
|
empClientBranchId = await tokenService.readValue('empClientBranchId');
|
|
getClaimsPoliciesDetails();
|
|
}
|
|
|
|
Future<void> getClaimsPoliciesDetails() async {
|
|
logDebug('9');
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
try {
|
|
logDebug('10');
|
|
|
|
final request = {
|
|
"client_id": empClientId,
|
|
"client_branch_id": empClientBranchId
|
|
};
|
|
|
|
final response =
|
|
await apiService.getNonEBClaimPoliciesToApi(_postPreToken!, request);
|
|
if (response['status'] == 'success' || response['status'] == true) {
|
|
setState(() {
|
|
isLoading = false;
|
|
final data = response['data'];
|
|
final rawList = data is List ? data : <dynamic>[];
|
|
|
|
policyNumberList = rawList.map<Map<String, dynamic>>((policy) {
|
|
final map = Map<String, dynamic>.from(policy as Map);
|
|
return {
|
|
// Send this id as client_policy_id in create API payload.
|
|
'id': int.tryParse(map['id'].toString()) ?? 0,
|
|
// Show only policy number in dropdown.
|
|
'label': (map['policy_no'] ?? '').toString(),
|
|
};
|
|
}).where((p) => p['id'] != 0).toList();
|
|
});
|
|
} else {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
|
|
// ToastHelper.showWarningToast(
|
|
// context, 'Request failed with status: ${response.statusCode}');
|
|
logDebug('Request failed with status: ${response['code']}');
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
// isLoading = false;
|
|
});
|
|
logDebug('Exception occurred: $e');
|
|
} finally {
|
|
setState(() {
|
|
// _isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Future<void> sendFormDataToApi() async {
|
|
setState(() {
|
|
isPolicyValid = selectedClientPolicyId != null;
|
|
isNatureOfLossValid = natureOfLossController.text.trim().isNotEmpty;
|
|
isLossLocationValid = lossLocationController.text.trim().isNotEmpty;
|
|
isLossDateValid = lossDate != null;
|
|
isLossDescriptionValid = lossDescriptionController.text.trim().isNotEmpty;
|
|
});
|
|
|
|
if (!isPolicyValid ||
|
|
!isNatureOfLossValid ||
|
|
!isLossLocationValid ||
|
|
!isLossDateValid ||
|
|
!isLossDescriptionValid) {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Please Fill Required Fields',
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (selectedAssetFile == null || selectedAssetFile!.bytes == null) {
|
|
ToastHelper.showErrorToast(
|
|
context,
|
|
'Please upload one document',
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => isSubmitting = true); // 🔥 start loader
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
try {
|
|
logDebug('Check One');
|
|
final fields = <String, dynamic>{
|
|
'client_policy_id': selectedClientPolicyId,
|
|
'nature_of_loss': natureOfLossController.text.trim(),
|
|
'loss_location': lossLocationController.text.trim(),
|
|
'loss_date': DateFormat('dd-MM-yyyy').format(lossDate!),
|
|
'loss_description': lossDescriptionController.text.trim(),
|
|
};
|
|
|
|
final request = http.MultipartRequest(
|
|
'POST', Uri.parse('${Environment.apiUrlPost}api/v1/non-eb-claim/create'));
|
|
request.headers['Authorization'] = 'Bearer $_postPreToken';
|
|
request.headers['APP-SIGNATURE'] =
|
|
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y';
|
|
final stringFields =
|
|
fields.map((key, value) => MapEntry(key, value?.toString() ?? ''));
|
|
logDebug("📁 stringFields: ${stringFields}");
|
|
request.fields.addAll(stringFields);
|
|
|
|
final pf = selectedAssetFile!;
|
|
final ext = pf.extension?.toLowerCase() ?? '';
|
|
Uint8List fileBytes = pf.bytes!;
|
|
var fileName = pf.name;
|
|
|
|
if (['jpg', 'jpeg', 'png', 'heic'].contains(ext)) {
|
|
final pdf = pw.Document();
|
|
final image = pw.MemoryImage(fileBytes);
|
|
pdf.addPage(
|
|
pw.Page(
|
|
build: (pw.Context context) => pw.Center(
|
|
child: pw.Image(image, fit: pw.BoxFit.contain),
|
|
),
|
|
),
|
|
);
|
|
fileBytes = await pdf.save();
|
|
fileName = pf.name.replaceAll(RegExp(r'\.\w+$'), '.pdf');
|
|
}
|
|
|
|
request.files.add(http.MultipartFile.fromBytes(
|
|
'asset_file',
|
|
fileBytes,
|
|
filename: fileName,
|
|
));
|
|
|
|
logDebug("Payload being sent:");
|
|
logDebug("File: ${pf.name}");
|
|
|
|
final response = await request.send();
|
|
final responseBody = await response.stream.bytesToString();
|
|
|
|
final decoded = jsonDecode(responseBody);
|
|
if (decoded['status'] == true) {
|
|
resetFormOnServiceChange();
|
|
Navigator.pop(context);
|
|
widget.onSuccess();
|
|
|
|
ToastHelper.showSuccessToast(context, decoded['message']);
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
logDebug('Form data submitted successfully');
|
|
} else {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
Navigator.pop(context);
|
|
ToastHelper.showErrorToast(context, "Failed: ${decoded['message']}");
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
logDebug('Error submitting form data: $e');
|
|
} finally {
|
|
setState(() => isSubmitting = false); // 🔥 stop loader
|
|
}
|
|
}
|
|
|
|
void resetFormOnServiceChange() {
|
|
selectedClientPolicyId = null;
|
|
policyNumberId = null;
|
|
natureOfLossController.clear();
|
|
lossLocationController.clear();
|
|
lossDescriptionController.clear();
|
|
lossDate = null;
|
|
selectedAssetFile = null;
|
|
|
|
isPolicyValid = true;
|
|
isNatureOfLossValid = true;
|
|
isLossLocationValid = true;
|
|
isLossDateValid = true;
|
|
isLossDescriptionValid = true;
|
|
|
|
}
|
|
|
|
Future<void> pickSingleAssetFile() async {
|
|
final result = await FilePicker.platform.pickFiles(
|
|
withData: true,
|
|
allowMultiple: false,
|
|
);
|
|
|
|
if (result != null && result.files.isNotEmpty) {
|
|
setState(() {
|
|
selectedAssetFile = result.files.first;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return WillPopScope(
|
|
onWillPop: () async {
|
|
resetFormOnServiceChange();
|
|
return true;
|
|
},
|
|
child: Dialog(
|
|
backgroundColor: Colors.white, // ✅ PURE WHITE popup
|
|
insetPadding: const EdgeInsets.all(20),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: SizedBox(
|
|
width: MediaQuery.of(context).size.width *
|
|
0.75, // Desktop popup width
|
|
// height: MediaQuery.of(context).size.height * 0.85,
|
|
child: Stack(
|
|
children: [
|
|
/// MAIN CONTENT (YOUR EXISTING UI)
|
|
SingleChildScrollView(
|
|
child: Container(
|
|
padding: EdgeInsets.all(20),
|
|
color: Colors.white,
|
|
child: Column(
|
|
children: [
|
|
/// 🔹 HEADER ROW (Title + Close)
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"Raise Insurance Claim",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => {
|
|
setState(() {
|
|
resetFormOnServiceChange();
|
|
}),
|
|
Navigator.pop(context)
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
Column(
|
|
children: [
|
|
_row([
|
|
buildDropdownField(
|
|
'Select Policy',
|
|
(value) {
|
|
setState(() {
|
|
policyNumberId = value;
|
|
selectedClientPolicyId = value;
|
|
isPolicyValid = true;
|
|
});
|
|
},
|
|
policyNumberList,
|
|
'label',
|
|
policyNumberId,
|
|
required: true,
|
|
isValid: isPolicyValid,
|
|
),
|
|
buildTextField(
|
|
'Nature Of Loss',
|
|
natureOfLossController,
|
|
required: true,
|
|
isValid: isNatureOfLossValid,
|
|
),
|
|
]),
|
|
_row([
|
|
buildTextField(
|
|
'Loss Location',
|
|
lossLocationController,
|
|
required: true,
|
|
isValid: isLossLocationValid,
|
|
),
|
|
buildDatePickerField(
|
|
label: 'Loss Date',
|
|
selectedDate: lossDate,
|
|
allowFuture: false,
|
|
onDateSelected: (d) =>
|
|
setState(() => lossDate = d),
|
|
required: true,
|
|
isValid: isLossDateValid,
|
|
),
|
|
]),
|
|
_row([
|
|
buildTextAreaField(
|
|
'Loss Description',
|
|
lossDescriptionController,
|
|
required: true,
|
|
isValid: isLossDescriptionValid,
|
|
),
|
|
]),
|
|
_row([
|
|
Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
fieldLabel(
|
|
'Upload Document',
|
|
required: true,
|
|
),
|
|
Container(
|
|
height: 42,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF1F1F1),
|
|
borderRadius:
|
|
BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
selectedAssetFile?.name ??
|
|
'Choose one file',
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: pickSingleAssetFile,
|
|
child: const Text('Browse'),
|
|
),
|
|
if (selectedAssetFile != null)
|
|
IconButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
selectedAssetFile = null;
|
|
});
|
|
},
|
|
icon: const Icon(
|
|
Icons.close,
|
|
size: 18,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (selectedAssetFile == null)
|
|
const SizedBox(
|
|
height: 16,
|
|
child: Text(
|
|
"Required",
|
|
style: TextStyle(
|
|
color: Colors.red,
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
)
|
|
else
|
|
const SizedBox(height: 16),
|
|
],
|
|
),
|
|
]),
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: SizedBox(
|
|
width: 120,
|
|
height: 42,
|
|
child: ElevatedButton(
|
|
onPressed: isSubmitting
|
|
? null
|
|
: sendFormDataToApi,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
const Color(0xFFE26728),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(10),
|
|
),
|
|
),
|
|
child: isSubmitting
|
|
? const SizedBox(
|
|
height: 18,
|
|
width: 18,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: Colors.white,
|
|
),
|
|
)
|
|
: const Text(
|
|
'Send',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
/// 🔥 LOADER OVERLAY
|
|
if (isLoading)
|
|
Positioned.fill(
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.6),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Center(
|
|
child: Image.asset(
|
|
'assets/nhance-loader.gif',
|
|
height: 60,
|
|
width: 60,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)));
|
|
}
|
|
|
|
/// ---------- HELPERS ----------
|
|
|
|
Widget _row(List<Widget> children) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 14),
|
|
child: Row(
|
|
children: children
|
|
.map((e) => Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(right: 12),
|
|
child: e,
|
|
)))
|
|
.toList(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget fieldLabel(String text, {bool required = false}) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 6),
|
|
child: RichText(
|
|
text: TextSpan(
|
|
text: text,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black,
|
|
),
|
|
children: required
|
|
? const [
|
|
TextSpan(
|
|
text: ' *',
|
|
style: TextStyle(color: Colors.red),
|
|
)
|
|
]
|
|
: [],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget errorText(bool isValid) {
|
|
return SizedBox(
|
|
height: 16, // fixed height for alignment
|
|
child: isValid
|
|
? null
|
|
: const Text(
|
|
"Required",
|
|
style: TextStyle(
|
|
color: Colors.red,
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildTextField(
|
|
String label,
|
|
TextEditingController controller, {
|
|
bool required = false,
|
|
bool isValid = true,
|
|
TextInputType keyboardType = TextInputType.text,
|
|
List<TextInputFormatter>? inputFormatters,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
fieldLabel(label, required: required),
|
|
formBox(
|
|
child: TextField(
|
|
controller: controller,
|
|
keyboardType: keyboardType,
|
|
inputFormatters: inputFormatters,
|
|
decoration: const InputDecoration(
|
|
isDense: true,
|
|
border: InputBorder.none,
|
|
),
|
|
),
|
|
),
|
|
errorText(isValid),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildTextAreaField(
|
|
String label,
|
|
TextEditingController controller, {
|
|
bool required = false,
|
|
bool isValid = true,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
fieldLabel(label, required: required),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF1F1F1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: TextField(
|
|
controller: controller,
|
|
maxLines: 3,
|
|
decoration: const InputDecoration(
|
|
isDense: true,
|
|
border: InputBorder.none,
|
|
),
|
|
),
|
|
),
|
|
errorText(isValid),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildDropdownField(
|
|
String label,
|
|
void Function(int?) onChanged,
|
|
List<Map<String, dynamic>> itemsList,
|
|
String displayField,
|
|
int? selectedValue, {
|
|
bool required = false,
|
|
bool isValid = true,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
fieldLabel(label, required: required),
|
|
formBox(
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<int>(
|
|
isExpanded: true,
|
|
value: selectedValue,
|
|
hint: const Text('Select'),
|
|
icon: const Icon(Icons.keyboard_arrow_down),
|
|
|
|
/// ✅ This controls selected value (closed state)
|
|
selectedItemBuilder: (context) {
|
|
return itemsList.map<Widget>((item) {
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
item[displayField] ?? '',
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
softWrap: false,
|
|
),
|
|
);
|
|
}).toList();
|
|
},
|
|
|
|
/// ✅ This controls dropdown list (open state)
|
|
items: itemsList.map<DropdownMenuItem<int>>((item) {
|
|
return DropdownMenuItem<int>(
|
|
value: item['id'],
|
|
child: Text(
|
|
item[displayField] ?? '',
|
|
style: const TextStyle(fontSize: 13),
|
|
), // FULL TEXT here
|
|
);
|
|
}).toList(),
|
|
|
|
onChanged: onChanged,
|
|
),
|
|
),
|
|
),
|
|
if (required) errorText(isValid),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildDatePickerField({
|
|
required String label,
|
|
required DateTime? selectedDate,
|
|
required bool allowFuture,
|
|
required ValueChanged<DateTime?> onDateSelected,
|
|
DateTime? minDate,
|
|
DateTime? maxDate,
|
|
bool required = false,
|
|
bool isValid = true,
|
|
}) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
fieldLabel(label, required: required),
|
|
Container(
|
|
height: 42,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF1F1F1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
// border: required && !isValid
|
|
// ? Border.all(color: Colors.red)
|
|
// : null,
|
|
),
|
|
child: InkWell(
|
|
onTap: () async {
|
|
final DateTime now = DateTime.now();
|
|
final DateTime first = minDate ?? DateTime(1980);
|
|
final DateTime last =
|
|
allowFuture ? (maxDate ?? DateTime(2100)) : now;
|
|
|
|
final DateTime initialDate =
|
|
selectedDate ?? (first.isAfter(now) ? first : now);
|
|
|
|
final picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: initialDate,
|
|
firstDate: first,
|
|
lastDate: last,
|
|
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
|
);
|
|
|
|
if (picked != null) {
|
|
onDateSelected(picked);
|
|
}
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
selectedDate != null
|
|
? DateFormat('dd-MM-yyyy').format(selectedDate)
|
|
: 'Select',
|
|
style: const TextStyle(color: Colors.black),
|
|
),
|
|
// ✅ Show clear button only if date selected
|
|
if (selectedDate != null)
|
|
GestureDetector(
|
|
onTap: () {
|
|
onDateSelected(null); // 🔥 Clear date
|
|
},
|
|
child: const Icon(
|
|
Icons.close,
|
|
size: 18,
|
|
color: Colors.grey,
|
|
),
|
|
)
|
|
else
|
|
const Icon(Icons.calendar_today, size: 18),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
errorText(isValid),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget formBox({required Widget child}) {
|
|
return Container(
|
|
height: 42,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF1F1F1), // 👈 light grey
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Center(child: child),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// ---------- STYLES ----------
|
|
final _labelStyle = GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
);
|