Merge dev into feature branch

This commit is contained in:
2026-03-19 13:16:04 +05:30
273 changed files with 7867 additions and 3654 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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),
),
),
],

View File

@@ -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),
),
),
),

View File

@@ -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,
});

View File

@@ -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,
);
}
}

View File

@@ -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),
],
),
),

View File

@@ -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(

View File

@@ -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,
);
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;