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
|
/// Help circle icon for FAQs
|
||||||
static const IconData helpCircle = _IconLib.helpCircle;
|
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_cubit.dart';
|
||||||
import '../blocs/attire_state.dart';
|
import '../blocs/attire_state.dart';
|
||||||
import '../widgets/attestation_checkbox.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 {
|
class AttireCapturePage extends StatefulWidget {
|
||||||
const AttireCapturePage({super.key, required this.item});
|
const AttireCapturePage({super.key, required this.item});
|
||||||
@@ -36,30 +39,6 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
cubit.uploadPhoto(widget.item.id);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final AttireCubit cubit = Modular.get<AttireCubit>();
|
final AttireCubit cubit = Modular.get<AttireCubit>();
|
||||||
@@ -97,46 +76,8 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Image Preview
|
// Image Preview
|
||||||
GestureDetector(
|
// Image Preview
|
||||||
onTap: () => _viewEnlargedImage(context),
|
AttireImagePreview(imageUrl: widget.item.imageUrl),
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -147,42 +88,9 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
// Verification info
|
// Verification info
|
||||||
Container(
|
AttireVerificationStatusCard(
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
statusText: statusText,
|
||||||
decoration: BoxDecoration(
|
statusColor: statusColor,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
@@ -200,23 +108,7 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
|||||||
const Center(child: CircularProgressIndicator())
|
const Center(child: CircularProgressIndicator())
|
||||||
else if (!hasPhoto ||
|
else if (!hasPhoto ||
|
||||||
true) // Show options even if has photo (allows re-upload)
|
true) // Show options even if has photo (allows re-upload)
|
||||||
Row(
|
AttireUploadButtons(onUpload: _onUpload),
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.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_cubit.dart';
|
||||||
import '../blocs/attire_state.dart';
|
import '../blocs/attire_state.dart';
|
||||||
|
import '../widgets/attire_filter_chips.dart';
|
||||||
import '../widgets/attire_info_card.dart';
|
import '../widgets/attire_info_card.dart';
|
||||||
import '../widgets/attire_item_card.dart';
|
import '../widgets/attire_item_card.dart';
|
||||||
import 'attire_capture_page.dart';
|
import 'attire_capture_page.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
|
||||||
|
|
||||||
class AttirePage extends StatefulWidget {
|
class AttirePage extends StatefulWidget {
|
||||||
const AttirePage({super.key});
|
const AttirePage({super.key});
|
||||||
@@ -21,46 +22,14 @@ class AttirePage extends StatefulWidget {
|
|||||||
class _AttirePageState extends State<AttirePage> {
|
class _AttirePageState extends State<AttirePage> {
|
||||||
String _filter = 'All';
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final AttireCubit cubit = Modular.get<AttireCubit>();
|
final AttireCubit cubit = Modular.get<AttireCubit>();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.background,
|
|
||||||
appBar: UiAppBar(
|
appBar: UiAppBar(
|
||||||
title: t.staff_profile_attire.title,
|
title: t.staff_profile_attire.title,
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(1.0),
|
|
||||||
child: Container(color: UiColors.border, height: 1.0),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
body: BlocProvider<AttireCubit>.value(
|
body: BlocProvider<AttireCubit>.value(
|
||||||
value: cubit,
|
value: cubit,
|
||||||
@@ -100,17 +69,13 @@ class _AttirePageState extends State<AttirePage> {
|
|||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
// Filter Chips
|
// Filter Chips
|
||||||
SingleChildScrollView(
|
AttireFilterChips(
|
||||||
scrollDirection: Axis.horizontal,
|
selectedFilter: _filter,
|
||||||
child: Row(
|
onFilterChanged: (String value) {
|
||||||
children: <Widget>[
|
setState(() {
|
||||||
_buildFilterChip('All'),
|
_filter = value;
|
||||||
const SizedBox(width: UiConstants.space2),
|
});
|
||||||
_buildFilterChip('Required'),
|
},
|
||||||
const SizedBox(width: UiConstants.space2),
|
|
||||||
_buildFilterChip('Non-Essential'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
|
||||||
@@ -136,7 +101,7 @@ class _AttirePageState extends State<AttirePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}),
|
||||||
const SizedBox(height: UiConstants.space20),
|
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),
|
const SizedBox(height: UiConstants.space2),
|
||||||
Row(
|
Row(
|
||||||
|
spacing: UiConstants.space2,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (item.isMandatory)
|
if (item.isMandatory)
|
||||||
const UiChip(
|
const UiChip(
|
||||||
@@ -74,7 +75,6 @@ class AttireItemCard extends StatelessWidget {
|
|||||||
size: UiChipSize.xSmall,
|
size: UiChipSize.xSmall,
|
||||||
variant: UiChipVariant.destructive,
|
variant: UiChipVariant.destructive,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
if (isUploading)
|
if (isUploading)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 16,
|
width: 16,
|
||||||
|
|||||||
Reference in New Issue
Block a user