refactor: Decompose AttireCapturePage into dedicated widgets for info, image preview, and footer sections, and refine attestation and verification status logic.
This commit is contained in:
@@ -8,19 +8,23 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_cubit.dart';
|
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_cubit.dart';
|
||||||
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_state.dart';
|
import 'package:staff_attire/src/presentation/blocs/attire_capture/attire_capture_state.dart';
|
||||||
|
|
||||||
import '../widgets/attestation_checkbox.dart';
|
import '../widgets/attire_capture_page/footer_section.dart';
|
||||||
import '../widgets/attire_capture_page/attire_image_preview.dart';
|
import '../widgets/attire_capture_page/image_preview_section.dart';
|
||||||
import '../widgets/attire_capture_page/attire_upload_buttons.dart';
|
import '../widgets/attire_capture_page/info_section.dart';
|
||||||
import '../widgets/attire_capture_page/attire_verification_status_card.dart';
|
|
||||||
|
|
||||||
|
/// The [AttireCapturePage] allows users to capture or upload a photo of a specific attire item.
|
||||||
class AttireCapturePage extends StatefulWidget {
|
class AttireCapturePage extends StatefulWidget {
|
||||||
|
/// Creates an [AttireCapturePage].
|
||||||
const AttireCapturePage({
|
const AttireCapturePage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.initialPhotoUrl,
|
this.initialPhotoUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The attire item being captured.
|
||||||
final AttireItem item;
|
final AttireItem item;
|
||||||
|
|
||||||
|
/// Optional initial photo URL if it was already uploaded.
|
||||||
final String? initialPhotoUrl;
|
final String? initialPhotoUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -30,13 +34,21 @@ class AttireCapturePage extends StatefulWidget {
|
|||||||
class _AttireCapturePageState extends State<AttireCapturePage> {
|
class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||||
String? _selectedLocalPath;
|
String? _selectedLocalPath;
|
||||||
|
|
||||||
|
/// Whether a verification status is already present for this item.
|
||||||
|
bool get _hasVerificationStatus => widget.item.verificationStatus != null;
|
||||||
|
|
||||||
|
/// Whether the item is currently pending verification.
|
||||||
|
bool get _isPending =>
|
||||||
|
widget.item.verificationStatus == AttireVerificationStatus.pending;
|
||||||
|
|
||||||
/// On gallery button press
|
/// On gallery button press
|
||||||
Future<void> _onGallery(BuildContext context) async {
|
Future<void> _onGallery(BuildContext context) async {
|
||||||
final AttireCaptureCubit cubit = BlocProvider.of<AttireCaptureCubit>(
|
final AttireCaptureCubit cubit = BlocProvider.of<AttireCaptureCubit>(
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!cubit.state.isAttested) {
|
// Skip attestation check if we already have a verification status
|
||||||
|
if (!_hasVerificationStatus && !cubit.state.isAttested) {
|
||||||
_showAttestationWarning(context);
|
_showAttestationWarning(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -62,7 +74,8 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!cubit.state.isAttested) {
|
// Skip attestation check if we already have a verification status
|
||||||
|
if (!_hasVerificationStatus && !cubit.state.isAttested) {
|
||||||
_showAttestationWarning(context);
|
_showAttestationWarning(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,6 +95,36 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show a bottom sheet for reuploading options.
|
||||||
|
void _onReupload(BuildContext context) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext sheetContext) => SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.photo_library),
|
||||||
|
title: const Text('Gallery'),
|
||||||
|
onTap: () {
|
||||||
|
Modular.to.pop();
|
||||||
|
_onGallery(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.camera_alt),
|
||||||
|
title: const Text('Camera'),
|
||||||
|
onTap: () {
|
||||||
|
Modular.to.pop();
|
||||||
|
_onCamera(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _showAttestationWarning(BuildContext context) {
|
void _showAttestationWarning(BuildContext context) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
@@ -119,6 +162,7 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
return switch (widget.item.verificationStatus) {
|
return switch (widget.item.verificationStatus) {
|
||||||
AttireVerificationStatus.approved => 'Approved',
|
AttireVerificationStatus.approved => 'Approved',
|
||||||
AttireVerificationStatus.rejected => 'Rejected',
|
AttireVerificationStatus.rejected => 'Rejected',
|
||||||
|
AttireVerificationStatus.pending => 'Pending Verification',
|
||||||
_ => hasUploadedPhoto ? 'Pending Verification' : 'Not Uploaded',
|
_ => hasUploadedPhoto ? 'Pending Verification' : 'Not Uploaded',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -127,6 +171,7 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
return switch (widget.item.verificationStatus) {
|
return switch (widget.item.verificationStatus) {
|
||||||
AttireVerificationStatus.approved => UiColors.textSuccess,
|
AttireVerificationStatus.approved => UiColors.textSuccess,
|
||||||
AttireVerificationStatus.rejected => UiColors.textError,
|
AttireVerificationStatus.rejected => UiColors.textError,
|
||||||
|
AttireVerificationStatus.pending => UiColors.textWarning,
|
||||||
_ => hasUploadedPhoto ? UiColors.textWarning : UiColors.textInactive,
|
_ => hasUploadedPhoto ? UiColors.textWarning : UiColors.textInactive,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -155,16 +200,10 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (BuildContext context, AttireCaptureState state) {
|
builder: (BuildContext context, AttireCaptureState state) {
|
||||||
final bool isUploading =
|
|
||||||
state.status == AttireCaptureStatus.uploading;
|
|
||||||
final String? currentPhotoUrl =
|
final String? currentPhotoUrl =
|
||||||
state.photoUrl ?? widget.initialPhotoUrl;
|
state.photoUrl ?? widget.initialPhotoUrl;
|
||||||
final bool hasUploadedPhoto = currentPhotoUrl != null;
|
final bool hasUploadedPhoto = currentPhotoUrl != null;
|
||||||
|
|
||||||
final String statusText = _getStatusText(hasUploadedPhoto);
|
|
||||||
|
|
||||||
final Color statusColor = _getStatusColor(hasUploadedPhoto);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -172,139 +211,38 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Image Preview (Toggle between example, review, and uploaded)
|
ImagePreviewSection(
|
||||||
if (_selectedLocalPath != null) ...<Widget>[
|
selectedLocalPath: _selectedLocalPath,
|
||||||
Text(
|
currentPhotoUrl: currentPhotoUrl,
|
||||||
'Review the attire item',
|
referenceImageUrl: widget.item.imageUrl,
|
||||||
style: UiTypography.body1b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
AttireImagePreview(localPath: _selectedLocalPath),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
Text(
|
|
||||||
'Reference Example',
|
|
||||||
style: UiTypography.body2b.textSecondary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Center(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
UiConstants.radiusBase,
|
|
||||||
),
|
|
||||||
child: Image.network(
|
|
||||||
widget.item.imageUrl ?? '',
|
|
||||||
height: 120,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
errorBuilder: (_, _, _) =>
|
|
||||||
const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
] else if (hasUploadedPhoto) ...<Widget>[
|
|
||||||
Text(
|
|
||||||
'Your Uploaded Photo',
|
|
||||||
style: UiTypography.body1b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space2),
|
|
||||||
AttireImagePreview(imageUrl: currentPhotoUrl),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
Text(
|
|
||||||
'Reference Example',
|
|
||||||
style: UiTypography.body2b.textSecondary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Center(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
UiConstants.radiusBase,
|
|
||||||
),
|
|
||||||
child: Image.network(
|
|
||||||
widget.item.imageUrl ?? '',
|
|
||||||
height: 120,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
errorBuilder: (_, _, _) =>
|
|
||||||
const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
] else ...<Widget>[
|
|
||||||
AttireImagePreview(
|
|
||||||
imageUrl: widget.item.imageUrl,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
Text(
|
|
||||||
'Example of the item that you need to upload.',
|
|
||||||
style: UiTypography.body1b.textSecondary,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
if (widget.item.description != null)
|
|
||||||
Text(
|
|
||||||
widget.item.description!,
|
|
||||||
style: UiTypography.body1r.textSecondary,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space8),
|
|
||||||
|
|
||||||
// Verification info
|
|
||||||
AttireVerificationStatusCard(
|
|
||||||
statusText: statusText,
|
|
||||||
statusColor: statusColor,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
InfoSection(
|
||||||
AttestationCheckbox(
|
description: widget.item.description,
|
||||||
isChecked: state.isAttested,
|
statusText: _getStatusText(hasUploadedPhoto),
|
||||||
onChanged: (bool? val) {
|
statusColor: _getStatusColor(hasUploadedPhoto),
|
||||||
|
isPending: _isPending,
|
||||||
|
showCheckbox: !_hasVerificationStatus,
|
||||||
|
isAttested: state.isAttested,
|
||||||
|
onAttestationChanged: (bool? val) {
|
||||||
cubit.toggleAttestation(val ?? false);
|
cubit.toggleAttestation(val ?? false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SafeArea(
|
FooterSection(
|
||||||
child: Padding(
|
isUploading:
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
state.status == AttireCaptureStatus.uploading,
|
||||||
child: Column(
|
selectedLocalPath: _selectedLocalPath,
|
||||||
mainAxisSize: MainAxisSize.min,
|
hasVerificationStatus: _hasVerificationStatus,
|
||||||
children: <Widget>[
|
hasUploadedPhoto: hasUploadedPhoto,
|
||||||
if (isUploading)
|
updatedItem: state.updatedItem,
|
||||||
const Center(
|
onGallery: () => _onGallery(context),
|
||||||
child: Padding(
|
onCamera: () => _onCamera(context),
|
||||||
padding: EdgeInsets.all(UiConstants.space4),
|
onSubmit: () => _onSubmit(context),
|
||||||
child: CircularProgressIndicator(),
|
onReupload: () => _onReupload(context),
|
||||||
),
|
|
||||||
)
|
|
||||||
else ...<Widget>[
|
|
||||||
AttireUploadButtons(
|
|
||||||
onGallery: () => _onGallery(context),
|
|
||||||
onCamera: () => _onCamera(context),
|
|
||||||
),
|
|
||||||
if (_selectedLocalPath != null) ...<Widget>[
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
UiButton.primary(
|
|
||||||
fullWidth: true,
|
|
||||||
text: 'Submit Image',
|
|
||||||
onPressed: () => _onSubmit(context),
|
|
||||||
),
|
|
||||||
] else if (hasUploadedPhoto) ...<Widget>[
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
UiButton.primary(
|
|
||||||
fullWidth: true,
|
|
||||||
text: 'Submit Image',
|
|
||||||
onPressed: () {
|
|
||||||
Modular.to.pop(state.updatedItem);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import 'attire_upload_buttons.dart';
|
||||||
|
|
||||||
|
/// Handles the primary actions at the bottom of the page.
|
||||||
|
class FooterSection extends StatelessWidget {
|
||||||
|
/// Creates a [FooterSection].
|
||||||
|
const FooterSection({
|
||||||
|
super.key,
|
||||||
|
required this.isUploading,
|
||||||
|
this.selectedLocalPath,
|
||||||
|
required this.hasVerificationStatus,
|
||||||
|
required this.hasUploadedPhoto,
|
||||||
|
this.updatedItem,
|
||||||
|
required this.onGallery,
|
||||||
|
required this.onCamera,
|
||||||
|
required this.onSubmit,
|
||||||
|
required this.onReupload,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Whether a photo is currently being uploaded.
|
||||||
|
final bool isUploading;
|
||||||
|
|
||||||
|
/// The local path of the selected photo.
|
||||||
|
final String? selectedLocalPath;
|
||||||
|
|
||||||
|
/// Whether the item already has a verification status.
|
||||||
|
final bool hasVerificationStatus;
|
||||||
|
|
||||||
|
/// Whether the item has an uploaded photo.
|
||||||
|
final bool hasUploadedPhoto;
|
||||||
|
|
||||||
|
/// The updated attire item, if any.
|
||||||
|
final AttireItem? updatedItem;
|
||||||
|
|
||||||
|
/// Callback to open the gallery.
|
||||||
|
final VoidCallback onGallery;
|
||||||
|
|
||||||
|
/// Callback to open the camera.
|
||||||
|
final VoidCallback onCamera;
|
||||||
|
|
||||||
|
/// Callback to submit the photo.
|
||||||
|
final VoidCallback onSubmit;
|
||||||
|
|
||||||
|
/// Callback to trigger the re-upload flow.
|
||||||
|
final VoidCallback onReupload;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
if (isUploading)
|
||||||
|
const Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(UiConstants.space4),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_buildActionButtons(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionButtons() {
|
||||||
|
if (selectedLocalPath != null) {
|
||||||
|
return UiButton.primary(
|
||||||
|
fullWidth: true,
|
||||||
|
text: 'Submit Image',
|
||||||
|
onPressed: onSubmit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasVerificationStatus) {
|
||||||
|
return UiButton.secondary(
|
||||||
|
fullWidth: true,
|
||||||
|
text: 'Re Upload',
|
||||||
|
onPressed: onReupload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
AttireUploadButtons(onGallery: onGallery, onCamera: onCamera),
|
||||||
|
if (hasUploadedPhoto) ...<Widget>[
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
UiButton.primary(
|
||||||
|
fullWidth: true,
|
||||||
|
text: 'Submit Image',
|
||||||
|
onPressed: () {
|
||||||
|
if (updatedItem != null) {
|
||||||
|
Modular.to.pop(updatedItem);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'attire_image_preview.dart';
|
||||||
|
|
||||||
|
/// Displays the comparison between the reference example and the user's photo.
|
||||||
|
class ImagePreviewSection extends StatelessWidget {
|
||||||
|
/// Creates an [ImagePreviewSection].
|
||||||
|
const ImagePreviewSection({
|
||||||
|
super.key,
|
||||||
|
this.selectedLocalPath,
|
||||||
|
this.currentPhotoUrl,
|
||||||
|
this.referenceImageUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The local file path of the selected image.
|
||||||
|
final String? selectedLocalPath;
|
||||||
|
|
||||||
|
/// The URL of the currently uploaded photo.
|
||||||
|
final String? currentPhotoUrl;
|
||||||
|
|
||||||
|
/// The URL of the reference example image.
|
||||||
|
final String? referenceImageUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (selectedLocalPath != null) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'Review the attire item',
|
||||||
|
style: UiTypography.body1b.textPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
AttireImagePreview(localPath: selectedLocalPath),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
ReferenceExample(imageUrl: referenceImageUrl),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPhotoUrl != null) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Your Uploaded Photo', style: UiTypography.body1b.textPrimary),
|
||||||
|
const SizedBox(height: UiConstants.space2),
|
||||||
|
AttireImagePreview(imageUrl: currentPhotoUrl),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
ReferenceExample(imageUrl: referenceImageUrl),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
AttireImagePreview(imageUrl: referenceImageUrl),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
Text(
|
||||||
|
'Example of the item that you need to upload.',
|
||||||
|
style: UiTypography.body1b.textSecondary,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays the reference item photo as an example.
|
||||||
|
class ReferenceExample extends StatelessWidget {
|
||||||
|
/// Creates a [ReferenceExample].
|
||||||
|
const ReferenceExample({super.key, this.imageUrl});
|
||||||
|
|
||||||
|
/// The URL of the image to display.
|
||||||
|
final String? imageUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Reference Example', style: UiTypography.body2b.textSecondary),
|
||||||
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
Center(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
child: Image.network(
|
||||||
|
imageUrl ?? '',
|
||||||
|
height: 120,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (_, _, _) => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../attestation_checkbox.dart';
|
||||||
|
import 'attire_verification_status_card.dart';
|
||||||
|
|
||||||
|
/// Displays the item details, verification status, and attestation checkbox.
|
||||||
|
class InfoSection extends StatelessWidget {
|
||||||
|
/// Creates an [InfoSection].
|
||||||
|
const InfoSection({
|
||||||
|
super.key,
|
||||||
|
this.description,
|
||||||
|
required this.statusText,
|
||||||
|
required this.statusColor,
|
||||||
|
required this.isPending,
|
||||||
|
required this.showCheckbox,
|
||||||
|
required this.isAttested,
|
||||||
|
required this.onAttestationChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The description of the attire item.
|
||||||
|
final String? description;
|
||||||
|
|
||||||
|
/// The text to display for the verification status.
|
||||||
|
final String statusText;
|
||||||
|
|
||||||
|
/// The color to use for the verification status text.
|
||||||
|
final Color statusColor;
|
||||||
|
|
||||||
|
/// Whether the item is currently pending verification.
|
||||||
|
final bool isPending;
|
||||||
|
|
||||||
|
/// Whether to show the attestation checkbox.
|
||||||
|
final bool showCheckbox;
|
||||||
|
|
||||||
|
/// Whether the user has attested to owning the item.
|
||||||
|
final bool isAttested;
|
||||||
|
|
||||||
|
/// Callback when the attestation status changes.
|
||||||
|
final ValueChanged<bool?> onAttestationChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
if (description != null)
|
||||||
|
Text(
|
||||||
|
description!,
|
||||||
|
style: UiTypography.body1r.textSecondary,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space8),
|
||||||
|
|
||||||
|
// Pending Banner
|
||||||
|
if (isPending) ...<Widget>[
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.tagPending,
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'A Manager will Verify This Item',
|
||||||
|
style: UiTypography.body2b.textWarning,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Verification info
|
||||||
|
AttireVerificationStatusCard(
|
||||||
|
statusText: statusText,
|
||||||
|
statusColor: statusColor,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
|
if (showCheckbox) ...<Widget>[
|
||||||
|
AttestationCheckbox(
|
||||||
|
isChecked: isAttested,
|
||||||
|
onChanged: onAttestationChanged,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user