refactor: extract attire UI components from pages into dedicated widgets for improved modularity.
This commit is contained in:
@@ -276,4 +276,7 @@ class UiIcons {
|
||||
|
||||
/// Help circle icon for FAQs
|
||||
static const IconData helpCircle = _IconLib.helpCircle;
|
||||
|
||||
/// Gallery icon for gallery
|
||||
static const IconData gallery = _IconLib.galleryVertical;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import 'package:core_localization/core_localization.dart';
|
||||
import '../blocs/attire_cubit.dart';
|
||||
import '../blocs/attire_state.dart';
|
||||
import '../widgets/attestation_checkbox.dart';
|
||||
import '../widgets/attire_capture_page/attire_image_preview.dart';
|
||||
import '../widgets/attire_capture_page/attire_upload_buttons.dart';
|
||||
import '../widgets/attire_capture_page/attire_verification_status_card.dart';
|
||||
|
||||
class AttireCapturePage extends StatefulWidget {
|
||||
const AttireCapturePage({super.key, required this.item});
|
||||
@@ -36,30 +39,6 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
cubit.uploadPhoto(widget.item.id);
|
||||
}
|
||||
|
||||
void _viewEnlargedImage(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxHeight: 500, maxWidth: 500),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(
|
||||
widget.item.imageUrl ??
|
||||
'https://images.unsplash.com/photo-1549298916-b41d501d3772?auto=format&fit=crop&q=80&w=400&h=400',
|
||||
),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AttireCubit cubit = Modular.get<AttireCubit>();
|
||||
@@ -97,46 +76,8 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
// Image Preview
|
||||
GestureDetector(
|
||||
onTap: () => _viewEnlargedImage(context),
|
||||
child: Container(
|
||||
height: 200,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: Color(0x19000000),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(
|
||||
widget.item.imageUrl ??
|
||||
'https://images.unsplash.com/photo-1549298916-b41d501d3772?auto=format&fit=crop&q=80&w=400&h=400',
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: const Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
UiIcons.search,
|
||||
color: UiColors.white,
|
||||
shadows: <Shadow>[
|
||||
Shadow(color: Colors.black, blurRadius: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Image Preview
|
||||
AttireImagePreview(imageUrl: widget.item.imageUrl),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
Text(
|
||||
@@ -147,42 +88,9 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Verification info
|
||||
Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.info,
|
||||
color: UiColors.primary,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Verification Status',
|
||||
style: UiTypography.footnote2m.textPrimary,
|
||||
),
|
||||
Text(
|
||||
statusText,
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AttireVerificationStatusCard(
|
||||
statusText: statusText,
|
||||
statusColor: statusColor,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
@@ -200,23 +108,7 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (!hasPhoto ||
|
||||
true) // Show options even if has photo (allows re-upload)
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiButton.secondary(
|
||||
text: 'Gallery',
|
||||
onPressed: () => _onUpload(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space4),
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
text: 'Camera',
|
||||
onPressed: () => _onUpload(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
AttireUploadButtons(onUpload: _onUpload),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
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:core_localization/core_localization.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../blocs/attire_cubit.dart';
|
||||
import '../blocs/attire_state.dart';
|
||||
import '../widgets/attire_filter_chips.dart';
|
||||
import '../widgets/attire_info_card.dart';
|
||||
import '../widgets/attire_item_card.dart';
|
||||
import 'attire_capture_page.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
class AttirePage extends StatefulWidget {
|
||||
const AttirePage({super.key});
|
||||
@@ -21,46 +22,14 @@ class AttirePage extends StatefulWidget {
|
||||
class _AttirePageState extends State<AttirePage> {
|
||||
String _filter = 'All';
|
||||
|
||||
Widget _buildFilterChip(String label) {
|
||||
final bool isSelected = _filter == label;
|
||||
return GestureDetector(
|
||||
onTap: () => setState(() => _filter = label),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiColors.primary : UiColors.white,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
border: Border.all(
|
||||
color: isSelected ? UiColors.primary : UiColors.border,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: (isSelected
|
||||
? UiTypography.footnote2m.white
|
||||
: UiTypography.footnote2m.textSecondary),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AttireCubit cubit = Modular.get<AttireCubit>();
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: UiAppBar(
|
||||
title: t.staff_profile_attire.title,
|
||||
showBackButton: true,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(1.0),
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: BlocProvider<AttireCubit>.value(
|
||||
value: cubit,
|
||||
@@ -100,17 +69,13 @@ class _AttirePageState extends State<AttirePage> {
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Filter Chips
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildFilterChip('All'),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
_buildFilterChip('Required'),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
_buildFilterChip('Non-Essential'),
|
||||
],
|
||||
),
|
||||
AttireFilterChips(
|
||||
selectedFilter: _filter,
|
||||
onFilterChanged: (String value) {
|
||||
setState(() {
|
||||
_filter = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
@@ -136,7 +101,7 @@ class _AttirePageState extends State<AttirePage> {
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
}),
|
||||
const SizedBox(height: UiConstants.space20),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AttireImagePreview extends StatelessWidget {
|
||||
const AttireImagePreview({super.key, required this.imageUrl});
|
||||
|
||||
final String? imageUrl;
|
||||
|
||||
void _viewEnlargedImage(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxHeight: 500, maxWidth: 500),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(
|
||||
imageUrl ??
|
||||
'https://images.unsplash.com/photo-1549298916-b41d501d3772?auto=format&fit=crop&q=80&w=400&h=400',
|
||||
),
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => _viewEnlargedImage(context),
|
||||
child: Container(
|
||||
height: 200,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
boxShadow: const <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: Color(0x19000000),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
image: DecorationImage(
|
||||
image: NetworkImage(
|
||||
imageUrl ??
|
||||
'https://images.unsplash.com/photo-1549298916-b41d501d3772?auto=format&fit=crop&q=80&w=400&h=400',
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: const Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
UiIcons.search,
|
||||
color: UiColors.white,
|
||||
shadows: <Shadow>[Shadow(color: Colors.black, blurRadius: 4)],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AttireUploadButtons extends StatelessWidget {
|
||||
const AttireUploadButtons({super.key, required this.onUpload});
|
||||
|
||||
final void Function(BuildContext) onUpload;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiButton.secondary(
|
||||
leadingIcon: UiIcons.gallery,
|
||||
text: 'Gallery',
|
||||
onPressed: () => onUpload(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space4),
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
leadingIcon: UiIcons.camera,
|
||||
text: 'Camera',
|
||||
onPressed: () => onUpload(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AttireVerificationStatusCard extends StatelessWidget {
|
||||
const AttireVerificationStatusCard({
|
||||
super.key,
|
||||
required this.statusText,
|
||||
required this.statusColor,
|
||||
});
|
||||
|
||||
final String statusText;
|
||||
final Color statusColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.info, color: UiColors.primary, size: 24),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Verification Status',
|
||||
style: UiTypography.footnote2m.textPrimary,
|
||||
),
|
||||
Text(
|
||||
statusText,
|
||||
style: UiTypography.body2m.copyWith(color: statusColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AttireFilterChips extends StatelessWidget {
|
||||
const AttireFilterChips({
|
||||
super.key,
|
||||
required this.selectedFilter,
|
||||
required this.onFilterChanged,
|
||||
});
|
||||
|
||||
final String selectedFilter;
|
||||
final ValueChanged<String> onFilterChanged;
|
||||
|
||||
Widget _buildFilterChip(String label) {
|
||||
final bool isSelected = selectedFilter == label;
|
||||
return GestureDetector(
|
||||
onTap: () => onFilterChanged(label),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiColors.primary : UiColors.white,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
border: Border.all(
|
||||
color: isSelected ? UiColors.primary : UiColors.border,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: (isSelected
|
||||
? UiTypography.footnote2m.white
|
||||
: UiTypography.footnote2m.textSecondary),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildFilterChip('All'),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
_buildFilterChip('Required'),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
_buildFilterChip('Non-Essential'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ class AttireItemCard extends StatelessWidget {
|
||||
],
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
Row(
|
||||
spacing: UiConstants.space2,
|
||||
children: <Widget>[
|
||||
if (item.isMandatory)
|
||||
const UiChip(
|
||||
@@ -74,7 +75,6 @@ class AttireItemCard extends StatelessWidget {
|
||||
size: UiChipSize.xSmall,
|
||||
variant: UiChipVariant.destructive,
|
||||
),
|
||||
const Spacer(),
|
||||
if (isUploading)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
|
||||
Reference in New Issue
Block a user