Merge dev into feature branch
This commit is contained in:
@@ -40,27 +40,42 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
||||
Future<StaffCertificate> uploadCertificate({
|
||||
required String certificateType,
|
||||
required String name,
|
||||
required String filePath,
|
||||
String? filePath,
|
||||
String? existingFileUri,
|
||||
DateTime? expiryDate,
|
||||
String? issuer,
|
||||
String? certificateNumber,
|
||||
}) async {
|
||||
// 1. Upload the file to cloud storage
|
||||
final FileUploadResponse uploadRes = await _uploadService.uploadFile(
|
||||
filePath: filePath,
|
||||
fileName:
|
||||
'staff_cert_${certificateType}_${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
visibility: FileVisibility.private,
|
||||
);
|
||||
String fileUri;
|
||||
String? signedUrl;
|
||||
|
||||
// 2. Generate a signed URL
|
||||
final SignedUrlResponse signedUrlRes =
|
||||
await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
|
||||
if (filePath != null) {
|
||||
// NEW FILE: Full upload pipeline
|
||||
// 1. Upload the file to cloud storage
|
||||
final FileUploadResponse uploadRes = await _uploadService.uploadFile(
|
||||
filePath: filePath,
|
||||
fileName:
|
||||
'staff_cert_${certificateType}_${DateTime.now().millisecondsSinceEpoch}.pdf',
|
||||
visibility: FileVisibility.private,
|
||||
);
|
||||
|
||||
// 3. Initiate verification
|
||||
// 2. Generate a signed URL
|
||||
final SignedUrlResponse signedUrlRes =
|
||||
await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
|
||||
|
||||
fileUri = uploadRes.fileUri;
|
||||
signedUrl = signedUrlRes.signedUrl;
|
||||
} else if (existingFileUri != null) {
|
||||
// EXISTING FILE: Metadata-only update — skip upload steps
|
||||
fileUri = existingFileUri;
|
||||
} else {
|
||||
throw ArgumentError('Either filePath or existingFileUri must be provided');
|
||||
}
|
||||
|
||||
// 3. Create verification (works for both new and existing files)
|
||||
final VerificationResponse verificationRes =
|
||||
await _verificationService.createVerification(
|
||||
fileUri: uploadRes.fileUri,
|
||||
fileUri: fileUri,
|
||||
type: 'certification',
|
||||
subjectType: 'worker',
|
||||
subjectId: certificateType,
|
||||
@@ -71,21 +86,21 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
||||
},
|
||||
);
|
||||
|
||||
// 4. Save certificate via V2 API
|
||||
// 4. Save/update certificate via V2 API (upserts on certificate_type)
|
||||
await _api.post(
|
||||
StaffEndpoints.certificates,
|
||||
data: <String, dynamic>{
|
||||
'certificateType': certificateType,
|
||||
'name': name,
|
||||
'fileUri': signedUrlRes.signedUrl,
|
||||
'expiresAt': expiryDate?.toIso8601String(),
|
||||
if (signedUrl != null) 'fileUri': signedUrl,
|
||||
'expiresAt': expiryDate?.toUtc().toIso8601String(),
|
||||
'issuer': issuer,
|
||||
'certificateNumber': certificateNumber,
|
||||
'verificationId': verificationRes.verificationId,
|
||||
},
|
||||
);
|
||||
|
||||
// 5. Return updated list
|
||||
// 5. Return updated certificate
|
||||
final List<StaffCertificate> certificates = await getCertificates();
|
||||
return certificates.firstWhere(
|
||||
(StaffCertificate c) => c.certificateType == certificateType,
|
||||
|
||||
@@ -9,10 +9,15 @@ abstract interface class CertificatesRepository {
|
||||
Future<List<StaffCertificate>> getCertificates();
|
||||
|
||||
/// Uploads a certificate file and saves the record.
|
||||
///
|
||||
/// When [filePath] is provided, a new file is uploaded to cloud storage.
|
||||
/// When only [existingFileUri] is provided, the existing stored file is
|
||||
/// reused and only metadata (e.g. expiry date) is updated.
|
||||
Future<StaffCertificate> uploadCertificate({
|
||||
required String certificateType,
|
||||
required String name,
|
||||
required String filePath,
|
||||
String? filePath,
|
||||
String? existingFileUri,
|
||||
DateTime? expiryDate,
|
||||
String? issuer,
|
||||
String? certificateNumber,
|
||||
|
||||
@@ -15,6 +15,7 @@ class UploadCertificateUseCase
|
||||
certificateType: params.certificateType,
|
||||
name: params.name,
|
||||
filePath: params.filePath,
|
||||
existingFileUri: params.existingFileUri,
|
||||
expiryDate: params.expiryDate,
|
||||
issuer: params.issuer,
|
||||
certificateNumber: params.certificateNumber,
|
||||
@@ -25,14 +26,21 @@ class UploadCertificateUseCase
|
||||
/// Parameters for [UploadCertificateUseCase].
|
||||
class UploadCertificateParams {
|
||||
/// Creates [UploadCertificateParams].
|
||||
///
|
||||
/// Either [filePath] (for a new file upload) or [existingFileUri] (for a
|
||||
/// metadata-only update using an already-stored file) must be provided.
|
||||
UploadCertificateParams({
|
||||
required this.certificateType,
|
||||
required this.name,
|
||||
required this.filePath,
|
||||
this.filePath,
|
||||
this.existingFileUri,
|
||||
this.expiryDate,
|
||||
this.issuer,
|
||||
this.certificateNumber,
|
||||
});
|
||||
}) : assert(
|
||||
filePath != null || existingFileUri != null,
|
||||
'Either filePath or existingFileUri must be provided',
|
||||
);
|
||||
|
||||
/// The type of certification (e.g. "FOOD_HYGIENE", "SIA_BADGE").
|
||||
final String certificateType;
|
||||
@@ -40,8 +48,12 @@ class UploadCertificateParams {
|
||||
/// The name of the certificate.
|
||||
final String name;
|
||||
|
||||
/// The local file path to upload.
|
||||
final String filePath;
|
||||
/// The local file path to upload, or null when reusing an existing file.
|
||||
final String? filePath;
|
||||
|
||||
/// The remote URI of an already-uploaded file, used for metadata-only
|
||||
/// updates (e.g. changing only the expiry date).
|
||||
final String? existingFileUri;
|
||||
|
||||
/// The expiry date of the certificate.
|
||||
final DateTime? expiryDate;
|
||||
|
||||
@@ -43,6 +43,12 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
super.initState();
|
||||
_cubit = Modular.get<CertificateUploadCubit>();
|
||||
|
||||
// Pre-populate file path with existing remote URI when editing so
|
||||
// the form is valid without re-picking a file.
|
||||
if (widget.certificate?.fileUri != null) {
|
||||
_cubit.setSelectedFilePath(widget.certificate!.fileUri);
|
||||
}
|
||||
|
||||
if (widget.certificate != null) {
|
||||
_selectedExpiryDate = widget.certificate!.expiresAt;
|
||||
_issuerController.text = widget.certificate!.issuer ?? '';
|
||||
@@ -148,9 +154,7 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<CertificateUploadCubit>.value(
|
||||
value: _cubit..setSelectedFilePath(
|
||||
widget.certificate?.fileUri,
|
||||
),
|
||||
value: _cubit,
|
||||
child: BlocConsumer<CertificateUploadCubit, CertificateUploadState>(
|
||||
listener: (BuildContext context, CertificateUploadState state) {
|
||||
if (state.status == CertificateUploadStatus.success) {
|
||||
@@ -182,7 +186,8 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
PdfFileTypesBanner(
|
||||
message: t.staff_documents.upload.pdf_banner,
|
||||
title: t.staff_documents.upload.pdf_banner_title,
|
||||
description: t.staff_documents.upload.pdf_banner_description,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
@@ -222,18 +227,27 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: CertificateUploadActions(
|
||||
isAttested: state.isAttested,
|
||||
isFormValid: state.selectedFilePath != null &&
|
||||
isFormValid: (state.selectedFilePath != null ||
|
||||
widget.certificate?.fileUri != null) &&
|
||||
state.isAttested &&
|
||||
_nameController.text.isNotEmpty,
|
||||
isUploading: state.status == CertificateUploadStatus.uploading,
|
||||
hasExistingCertificate: widget.certificate != null,
|
||||
onUploadPressed: () {
|
||||
final String? selectedPath = state.selectedFilePath;
|
||||
final bool isLocalFile = selectedPath != null &&
|
||||
!selectedPath.startsWith('http') &&
|
||||
!selectedPath.startsWith('gs://');
|
||||
|
||||
BlocProvider.of<CertificateUploadCubit>(context)
|
||||
.uploadCertificate(
|
||||
UploadCertificateParams(
|
||||
certificateType: _selectedType,
|
||||
name: _nameController.text,
|
||||
filePath: state.selectedFilePath!,
|
||||
filePath: isLocalFile ? selectedPath : null,
|
||||
existingFileUri: !isLocalFile
|
||||
? (selectedPath ?? widget.certificate?.fileUri)
|
||||
: null,
|
||||
expiryDate: _selectedExpiryDate,
|
||||
issuer: _issuerController.text,
|
||||
certificateNumber: _numberController.text,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:core_localization/core_localization.dart';
|
||||
/// Widget for certificate metadata input fields (name, issuer, number).
|
||||
class CertificateMetadataFields extends StatelessWidget {
|
||||
const CertificateMetadataFields({
|
||||
super.key,
|
||||
required this.nameController,
|
||||
required this.issuerController,
|
||||
required this.numberController,
|
||||
@@ -32,9 +33,7 @@ class CertificateMetadataFields extends StatelessWidget {
|
||||
enabled: isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.name_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: UiConstants.radiusLg),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -50,27 +49,20 @@ class CertificateMetadataFields extends StatelessWidget {
|
||||
enabled: isNewCertificate,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.issuer_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: UiConstants.radiusLg),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Certificate Number Field
|
||||
Text(
|
||||
'Certificate Number',
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
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,
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: UiConstants.radiusLg),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -8,6 +8,7 @@ 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({
|
||||
super.key,
|
||||
required this.isAttested,
|
||||
required this.isFormValid,
|
||||
required this.isUploading,
|
||||
@@ -34,10 +35,9 @@ class CertificateUploadActions extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: isAttested,
|
||||
onChanged: (bool? val) =>
|
||||
BlocProvider.of<CertificateUploadCubit>(context).setAttested(
|
||||
val ?? false,
|
||||
),
|
||||
onChanged: (bool? val) => BlocProvider.of<CertificateUploadCubit>(
|
||||
context,
|
||||
).setAttested(val ?? false),
|
||||
activeColor: UiColors.primary,
|
||||
),
|
||||
Expanded(
|
||||
@@ -54,17 +54,11 @@ class CertificateUploadActions extends StatelessWidget {
|
||||
onPressed: isFormValid ? onUploadPressed : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.primary,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
|
||||
shape: RoundedRectangleBorder(borderRadius: UiConstants.radiusLg),
|
||||
),
|
||||
child: isUploading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
)
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: Text(
|
||||
t.staff_certificates.upload_modal.save,
|
||||
style: UiTypography.body1m.white,
|
||||
@@ -87,9 +81,7 @@ class CertificateUploadActions extends StatelessWidget {
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(
|
||||
color: UiColors.destructive,
|
||||
),
|
||||
side: const BorderSide(color: UiColors.destructive),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:core_localization/core_localization.dart';
|
||||
/// Widget for selecting certificate expiry date.
|
||||
class ExpiryDateField extends StatelessWidget {
|
||||
const ExpiryDateField({
|
||||
super.key,
|
||||
required this.selectedDate,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@@ -3,12 +3,24 @@ import 'package:flutter/material.dart';
|
||||
|
||||
/// Banner displaying accepted file types and size limit for PDF upload.
|
||||
class PdfFileTypesBanner extends StatelessWidget {
|
||||
const PdfFileTypesBanner({super.key, required this.message});
|
||||
const PdfFileTypesBanner({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.description,
|
||||
});
|
||||
|
||||
final String message;
|
||||
/// Short title for the banner.
|
||||
final String title;
|
||||
|
||||
/// Optional description with additional details.
|
||||
final String? description;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiNoticeBanner(title: message, icon: UiIcons.info);
|
||||
return UiNoticeBanner(
|
||||
title: title,
|
||||
description: description,
|
||||
icon: UiIcons.info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,23 @@ class CertificatesHeaderSkeleton extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: const BoxDecoration(color: UiColors.primary),
|
||||
child: SafeArea(
|
||||
child: const SafeArea(
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
const UiShimmerCircle(size: 64),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
SizedBox(height: UiConstants.space4),
|
||||
UiShimmerCircle(size: 64),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
UiShimmerLine(
|
||||
width: 120,
|
||||
height: 14,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
SizedBox(height: UiConstants.space2),
|
||||
UiShimmerLine(
|
||||
width: 80,
|
||||
height: 12,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -70,7 +70,8 @@ class DocumentUploadPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
PdfFileTypesBanner(
|
||||
message: t.staff_documents.upload.pdf_banner,
|
||||
title: t.staff_documents.upload.pdf_banner_title,
|
||||
description: t.staff_documents.upload.pdf_banner_description,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
DocumentFileSelector(
|
||||
|
||||
@@ -3,12 +3,24 @@ 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, super.key});
|
||||
const PdfFileTypesBanner({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.description,
|
||||
});
|
||||
|
||||
final String message;
|
||||
/// Short title for the banner.
|
||||
final String title;
|
||||
|
||||
/// Optional description with additional details.
|
||||
final String? description;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UiNoticeBanner(title: message, icon: UiIcons.info);
|
||||
return UiNoticeBanner(
|
||||
title: title,
|
||||
description: description,
|
||||
icon: UiIcons.info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,14 +121,14 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
|
||||
void _handleNext(BuildContext context, int currentStep) {
|
||||
if (currentStep < _steps.length - 1) {
|
||||
context.read<FormI9Cubit>().nextStep(_steps.length);
|
||||
ReadContext(context).read<FormI9Cubit>().nextStep(_steps.length);
|
||||
} else {
|
||||
context.read<FormI9Cubit>().submit();
|
||||
ReadContext(context).read<FormI9Cubit>().submit();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleBack(BuildContext context) {
|
||||
context.read<FormI9Cubit>().previousStep();
|
||||
ReadContext(context).read<FormI9Cubit>().previousStep();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -178,8 +178,9 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, FormI9State state) {
|
||||
if (state.status == FormI9Status.success)
|
||||
if (state.status == FormI9Status.success) {
|
||||
return _buildSuccessView(i18n);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
@@ -458,7 +459,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.first_name,
|
||||
value: state.firstName,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().firstNameChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().firstNameChanged(val),
|
||||
placeholder: i18n.fields.hints.first_name,
|
||||
),
|
||||
),
|
||||
@@ -468,7 +469,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.last_name,
|
||||
value: state.lastName,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().lastNameChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().lastNameChanged(val),
|
||||
placeholder: i18n.fields.hints.last_name,
|
||||
),
|
||||
),
|
||||
@@ -482,7 +483,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.middle_initial,
|
||||
value: state.middleInitial,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().middleInitialChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().middleInitialChanged(val),
|
||||
placeholder: i18n.fields.hints.middle_initial,
|
||||
),
|
||||
),
|
||||
@@ -493,7 +494,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.other_last_names,
|
||||
value: state.otherLastNames,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().otherLastNamesChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().otherLastNamesChanged(val),
|
||||
placeholder: i18n.fields.maiden_name,
|
||||
),
|
||||
),
|
||||
@@ -504,7 +505,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.dob,
|
||||
value: state.dob,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().dobChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().dobChanged(val),
|
||||
placeholder: i18n.fields.hints.dob,
|
||||
keyboardType: TextInputType.datetime,
|
||||
),
|
||||
@@ -517,7 +518,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
onChanged: (String val) {
|
||||
String text = val.replaceAll(RegExp(r'\D'), '');
|
||||
if (text.length > 9) text = text.substring(0, 9);
|
||||
context.read<FormI9Cubit>().ssnChanged(text);
|
||||
ReadContext(context).read<FormI9Cubit>().ssnChanged(text);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -525,7 +526,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.email,
|
||||
value: state.email,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().emailChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().emailChanged(val),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
placeholder: i18n.fields.hints.email,
|
||||
),
|
||||
@@ -534,7 +535,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.phone,
|
||||
value: state.phone,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().phoneChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().phoneChanged(val),
|
||||
keyboardType: TextInputType.phone,
|
||||
placeholder: i18n.fields.hints.phone,
|
||||
),
|
||||
@@ -553,7 +554,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.address_long,
|
||||
value: state.address,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().addressChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().addressChanged(val),
|
||||
placeholder: i18n.fields.hints.address,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -561,7 +562,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.apt,
|
||||
value: state.aptNumber,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().aptNumberChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().aptNumberChanged(val),
|
||||
placeholder: i18n.fields.hints.apt,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -573,7 +574,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.city,
|
||||
value: state.city,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().cityChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().cityChanged(val),
|
||||
placeholder: i18n.fields.hints.city,
|
||||
),
|
||||
),
|
||||
@@ -592,7 +593,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: state.state.isEmpty ? null : state.state,
|
||||
onChanged: (String? val) =>
|
||||
context.read<FormI9Cubit>().stateChanged(val ?? ''),
|
||||
ReadContext(context).read<FormI9Cubit>().stateChanged(val ?? ''),
|
||||
items: _usStates.map((String stateAbbr) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: stateAbbr,
|
||||
@@ -625,7 +626,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.zip,
|
||||
value: state.zipCode,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().zipCodeChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().zipCodeChanged(val),
|
||||
placeholder: i18n.fields.hints.zip,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
@@ -659,7 +660,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
i18n.fields.uscis_number_label,
|
||||
value: state.uscisNumber,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().uscisNumberChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().uscisNumberChanged(val),
|
||||
placeholder: i18n.fields.hints.uscis,
|
||||
),
|
||||
)
|
||||
@@ -717,7 +718,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
}) {
|
||||
final bool isSelected = state.citizenshipStatus == value;
|
||||
return GestureDetector(
|
||||
onTap: () => context.read<FormI9Cubit>().citizenshipStatusChanged(value),
|
||||
onTap: () => ReadContext(context).read<FormI9Cubit>().citizenshipStatusChanged(value),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -802,7 +803,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
CheckboxListTile(
|
||||
value: state.preparerUsed,
|
||||
onChanged: (bool? val) {
|
||||
context.read<FormI9Cubit>().preparerUsedChanged(val ?? false);
|
||||
ReadContext(context).read<FormI9Cubit>().preparerUsedChanged(val ?? false);
|
||||
},
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
@@ -836,7 +837,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
||||
TextPosition(offset: state.signature.length),
|
||||
),
|
||||
onChanged: (String val) =>
|
||||
context.read<FormI9Cubit>().signatureChanged(val),
|
||||
ReadContext(context).read<FormI9Cubit>().signatureChanged(val),
|
||||
decoration: InputDecoration(
|
||||
hintText: i18n.fields.signature_hint,
|
||||
filled: true,
|
||||
|
||||
@@ -111,14 +111,14 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
|
||||
void _handleNext(BuildContext context, int currentStep) {
|
||||
if (currentStep < _steps.length - 1) {
|
||||
context.read<FormW4Cubit>().nextStep(_steps.length);
|
||||
ReadContext(context).read<FormW4Cubit>().nextStep(_steps.length);
|
||||
} else {
|
||||
context.read<FormW4Cubit>().submit();
|
||||
ReadContext(context).read<FormW4Cubit>().submit();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleBack(BuildContext context) {
|
||||
context.read<FormW4Cubit>().previousStep();
|
||||
ReadContext(context).read<FormW4Cubit>().previousStep();
|
||||
}
|
||||
|
||||
int _totalCredits(FormW4State state) {
|
||||
@@ -180,8 +180,9 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, FormW4State state) {
|
||||
if (state.status == FormW4Status.success)
|
||||
if (state.status == FormW4Status.success) {
|
||||
return _buildSuccessView(i18n);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
@@ -457,7 +458,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.first_name,
|
||||
value: state.firstName,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().firstNameChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().firstNameChanged(val),
|
||||
placeholder: i18n.fields.placeholder_john,
|
||||
),
|
||||
),
|
||||
@@ -467,7 +468,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.last_name,
|
||||
value: state.lastName,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().lastNameChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().lastNameChanged(val),
|
||||
placeholder: i18n.fields.placeholder_smith,
|
||||
),
|
||||
),
|
||||
@@ -482,7 +483,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
onChanged: (String val) {
|
||||
String text = val.replaceAll(RegExp(r'\D'), '');
|
||||
if (text.length > 9) text = text.substring(0, 9);
|
||||
context.read<FormW4Cubit>().ssnChanged(text);
|
||||
ReadContext(context).read<FormW4Cubit>().ssnChanged(text);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -490,7 +491,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.address,
|
||||
value: state.address,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().addressChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().addressChanged(val),
|
||||
placeholder: i18n.fields.placeholder_address,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
@@ -498,7 +499,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.city_state_zip,
|
||||
value: state.cityStateZip,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().cityStateZipChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().cityStateZipChanged(val),
|
||||
placeholder: i18n.fields.placeholder_csz,
|
||||
),
|
||||
],
|
||||
@@ -556,7 +557,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
) {
|
||||
final bool isSelected = state.filingStatus == value;
|
||||
return GestureDetector(
|
||||
onTap: () => context.read<FormW4Cubit>().filingStatusChanged(value),
|
||||
onTap: () => ReadContext(context).read<FormW4Cubit>().filingStatusChanged(value),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -640,7 +641,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
GestureDetector(
|
||||
onTap: () => context.read<FormW4Cubit>().multipleJobsChanged(
|
||||
onTap: () => ReadContext(context).read<FormW4Cubit>().multipleJobsChanged(
|
||||
!state.multipleJobs,
|
||||
),
|
||||
child: Container(
|
||||
@@ -751,7 +752,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.children_each,
|
||||
(FormW4State s) => s.qualifyingChildren,
|
||||
(int val) =>
|
||||
context.read<FormW4Cubit>().qualifyingChildrenChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().qualifyingChildrenChanged(val),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
@@ -764,7 +765,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.other_each,
|
||||
(FormW4State s) => s.otherDependents,
|
||||
(int val) =>
|
||||
context.read<FormW4Cubit>().otherDependentsChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().otherDependentsChanged(val),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -880,7 +881,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.other_income,
|
||||
value: state.otherIncome,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().otherIncomeChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().otherIncomeChanged(val),
|
||||
placeholder: i18n.fields.hints.zero,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
@@ -896,7 +897,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.deductions,
|
||||
value: state.deductions,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().deductionsChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().deductionsChanged(val),
|
||||
placeholder: i18n.fields.hints.zero,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
@@ -912,7 +913,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
i18n.fields.extra_withholding,
|
||||
value: state.extraWithholding,
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().extraWithholdingChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().extraWithholdingChanged(val),
|
||||
placeholder: i18n.fields.hints.zero,
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
@@ -995,7 +996,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
||||
TextPosition(offset: state.signature.length),
|
||||
),
|
||||
onChanged: (String val) =>
|
||||
context.read<FormW4Cubit>().signatureChanged(val),
|
||||
ReadContext(context).read<FormW4Cubit>().signatureChanged(val),
|
||||
decoration: InputDecoration(
|
||||
hintText: i18n.fields.signature_hint,
|
||||
filled: true,
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Widget displaying the overall progress of tax form completion.
|
||||
class TaxFormsProgressOverview extends StatelessWidget {
|
||||
const TaxFormsProgressOverview({required this.forms});
|
||||
const TaxFormsProgressOverview({super.key, required this.forms});
|
||||
|
||||
final List<TaxForm> forms;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Widget displaying status badge for a tax form.
|
||||
class TaxFormStatusBadge extends StatelessWidget {
|
||||
const TaxFormStatusBadge({required this.status});
|
||||
const TaxFormStatusBadge({super.key, required this.status});
|
||||
|
||||
final TaxFormStatus status;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user