feat: Implement certificate upload form with metadata fields, expiry date selection, and file upload functionality
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../domain/usecases/upload_certificate_usecase.dart';
|
||||
import '../blocs/certificate_upload/certificate_upload_cubit.dart';
|
||||
import '../blocs/certificate_upload/certificate_upload_state.dart';
|
||||
import '../../domain/usecases/upload_certificate_usecase.dart';
|
||||
import '../widgets/certificate_upload_page/index.dart';
|
||||
|
||||
/// Page for uploading a certificate with metadata (expiry, issuer, etc).
|
||||
class CertificateUploadPage extends StatefulWidget {
|
||||
@@ -179,62 +179,16 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_PdfFileTypesBanner(
|
||||
PdfFileTypesBanner(
|
||||
message: t.staff_documents.upload.pdf_banner,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Name Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.name_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
enabled: _isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.name_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Issuer Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.issuer_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _issuerController,
|
||||
enabled: _isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.issuer_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Certificate Number Field
|
||||
Text(
|
||||
'Certificate Number',
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _numberController,
|
||||
enabled: _isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter number if applicable',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
CertificateMetadataFields(
|
||||
nameController: _nameController,
|
||||
issuerController: _issuerController,
|
||||
numberController: _numberController,
|
||||
isNewCertificate: _isNewCertificate,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
@@ -242,44 +196,9 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Expiry Date Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.expiry_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
InkWell(
|
||||
ExpiryDateField(
|
||||
selectedDate: _selectedExpiryDate,
|
||||
onTap: _selectDate,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.calendar,
|
||||
size: 20,
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Text(
|
||||
_selectedExpiryDate != null
|
||||
? DateFormat(
|
||||
'MMM dd, yyyy',
|
||||
).format(_selectedExpiryDate!)
|
||||
: t.staff_certificates.upload_modal.select_date,
|
||||
style: _selectedExpiryDate != null
|
||||
? UiTypography.body1m.textPrimary
|
||||
: UiTypography.body1m.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
@@ -289,7 +208,7 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
_FileSelector(
|
||||
FileSelector(
|
||||
selectedFilePath: _selectedFilePath,
|
||||
onTap: _pickFile,
|
||||
),
|
||||
@@ -299,110 +218,40 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: UiConstants.space4,
|
||||
children: <Widget>[
|
||||
// Attestation
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: state.isAttested,
|
||||
onChanged: (bool? val) =>
|
||||
BlocProvider.of<CertificateUploadCubit>(
|
||||
context,
|
||||
).setAttested(val ?? false),
|
||||
activeColor: UiColors.primary,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
t.staff_documents.upload.attestation,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
(_selectedFilePath != null &&
|
||||
state.isAttested &&
|
||||
_nameController.text.isNotEmpty)
|
||||
? () {
|
||||
final String? err = _validatePdfFile(
|
||||
context,
|
||||
_selectedFilePath!,
|
||||
);
|
||||
if (err != null) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: err,
|
||||
type: UiSnackbarType.error,
|
||||
margin: const EdgeInsets.all(
|
||||
UiConstants.space4,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
BlocProvider.of<CertificateUploadCubit>(
|
||||
context,
|
||||
).uploadCertificate(
|
||||
UploadCertificateParams(
|
||||
certificationType: _selectedType!,
|
||||
name: _nameController.text,
|
||||
filePath: _selectedFilePath!,
|
||||
expiryDate: _selectedExpiryDate,
|
||||
issuer: _issuerController.text,
|
||||
certificateNumber: _numberController.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.primary,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
child: state.status == CertificateUploadStatus.uploading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
)
|
||||
: Text(
|
||||
t.staff_certificates.upload_modal.save,
|
||||
style: UiTypography.body1m.white,
|
||||
),
|
||||
child: CertificateUploadActions(
|
||||
isAttested: state.isAttested,
|
||||
isFormValid: _selectedFilePath != null &&
|
||||
state.isAttested &&
|
||||
_nameController.text.isNotEmpty,
|
||||
isUploading: state.status == CertificateUploadStatus.uploading,
|
||||
hasExistingCertificate: widget.certificate != null,
|
||||
onUploadPressed: () {
|
||||
final String? err = _validatePdfFile(
|
||||
context,
|
||||
_selectedFilePath!,
|
||||
);
|
||||
if (err != null) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: err,
|
||||
type: UiSnackbarType.error,
|
||||
margin: const EdgeInsets.all(UiConstants.space4),
|
||||
);
|
||||
return;
|
||||
}
|
||||
BlocProvider.of<CertificateUploadCubit>(context)
|
||||
.uploadCertificate(
|
||||
UploadCertificateParams(
|
||||
certificationType: _selectedType!,
|
||||
name: _nameController.text,
|
||||
filePath: _selectedFilePath!,
|
||||
expiryDate: _selectedExpiryDate,
|
||||
issuer: _issuerController.text,
|
||||
certificateNumber: _numberController.text,
|
||||
),
|
||||
),
|
||||
|
||||
// Remove Button (only if existing)
|
||||
if (widget.certificate != null) ...<Widget>[
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _showRemoveConfirmation(context),
|
||||
icon: const Icon(UiIcons.delete, size: 20),
|
||||
label: Text(t.staff_certificates.card.remove),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(
|
||||
color: UiColors.destructive,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
onRemovePressed: () => _showRemoveConfirmation(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -412,104 +261,3 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Banner displaying accepted file types and size limit for PDF upload.
|
||||
class _PdfFileTypesBanner extends StatelessWidget {
|
||||
const _PdfFileTypesBanner({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primaryForeground,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: UiColors.primary.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.info, size: 20, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Text(message, style: UiTypography.body2r.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FileSelector extends StatelessWidget {
|
||||
const _FileSelector({this.selectedFilePath, required this.onTap});
|
||||
|
||||
final String? selectedFilePath;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (selectedFilePath != null) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.primary),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.certificate, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Text(
|
||||
selectedFilePath!.split('/').last,
|
||||
style: UiTypography.body1m.primary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
t.staff_documents.upload.replace,
|
||||
style: UiTypography.body3m.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 120,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, style: BorderStyle.solid),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
color: UiColors.background,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.uploadCloud, size: 32, color: UiColors.primary),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.drag_drop,
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.supported_formats,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// Widget for certificate metadata input fields (name, issuer, number).
|
||||
class CertificateMetadataFields extends StatelessWidget {
|
||||
const CertificateMetadataFields({
|
||||
required this.nameController,
|
||||
required this.issuerController,
|
||||
required this.numberController,
|
||||
required this.isNewCertificate,
|
||||
});
|
||||
|
||||
final TextEditingController nameController;
|
||||
final TextEditingController issuerController;
|
||||
final TextEditingController numberController;
|
||||
final bool isNewCertificate;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// Name Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.name_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: nameController,
|
||||
enabled: isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.name_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Issuer Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.issuer_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: issuerController,
|
||||
enabled: isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.issuer_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Certificate Number Field
|
||||
Text(
|
||||
'Certificate Number',
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: numberController,
|
||||
enabled: isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter number if applicable',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
import '../../blocs/certificate_upload/certificate_upload_cubit.dart';
|
||||
|
||||
/// Widget for attestation checkbox and action buttons in certificate upload form.
|
||||
class CertificateUploadActions extends StatelessWidget {
|
||||
const CertificateUploadActions({
|
||||
required this.isAttested,
|
||||
required this.isFormValid,
|
||||
required this.isUploading,
|
||||
required this.hasExistingCertificate,
|
||||
required this.onUploadPressed,
|
||||
required this.onRemovePressed,
|
||||
});
|
||||
|
||||
final bool isAttested;
|
||||
final bool isFormValid;
|
||||
final bool isUploading;
|
||||
final bool hasExistingCertificate;
|
||||
final VoidCallback onUploadPressed;
|
||||
final VoidCallback onRemovePressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: UiConstants.space4,
|
||||
children: <Widget>[
|
||||
// Attestation
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: isAttested,
|
||||
onChanged: (bool? val) =>
|
||||
BlocProvider.of<CertificateUploadCubit>(context).setAttested(
|
||||
val ?? false,
|
||||
),
|
||||
activeColor: UiColors.primary,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
t.staff_documents.upload.attestation,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: isFormValid ? onUploadPressed : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.primary,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
child: isUploading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
)
|
||||
: Text(
|
||||
t.staff_certificates.upload_modal.save,
|
||||
style: UiTypography.body1m.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Remove Button (only if existing)
|
||||
if (hasExistingCertificate) ...<Widget>[
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
onPressed: onRemovePressed,
|
||||
icon: const Icon(UiIcons.delete, size: 20),
|
||||
label: Text(t.staff_certificates.card.remove),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(
|
||||
color: UiColors.destructive,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// Widget for selecting certificate expiry date.
|
||||
class ExpiryDateField extends StatelessWidget {
|
||||
const ExpiryDateField({
|
||||
required this.selectedDate,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final DateTime? selectedDate;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.expiry_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.calendar,
|
||||
size: 20,
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Text(
|
||||
selectedDate != null
|
||||
? DateFormat('MMM dd, yyyy').format(selectedDate!)
|
||||
: t.staff_certificates.upload_modal.select_date,
|
||||
style: selectedDate != null
|
||||
? UiTypography.body1m.textPrimary
|
||||
: UiTypography.body1m.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
/// Widget for selecting certificate file.
|
||||
class FileSelector extends StatelessWidget {
|
||||
const FileSelector({
|
||||
required this.selectedFilePath,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String? selectedFilePath;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (selectedFilePath != null) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.primary),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.certificate, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Text(
|
||||
selectedFilePath!.split('/').last,
|
||||
style: UiTypography.body1m.primary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
t.staff_documents.upload.replace,
|
||||
style: UiTypography.body3m.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 120,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: UiColors.border, style: BorderStyle.solid),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
color: UiColors.background,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.uploadCloud, size: 32, color: UiColors.primary),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.drag_drop,
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.supported_formats,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export 'certificate_metadata_fields.dart';
|
||||
export 'certificate_upload_actions.dart';
|
||||
export 'expiry_date_field.dart';
|
||||
export 'file_selector.dart';
|
||||
export 'pdf_file_types_banner.dart';
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Banner displaying accepted file types and size limit for PDF upload.
|
||||
class PdfFileTypesBanner extends StatelessWidget {
|
||||
const PdfFileTypesBanner({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primaryForeground,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: UiColors.primary.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.info, size: 20, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Text(message, style: UiTypography.body2r.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user