From 74d8d4d4d90bf12145f01fd0e69af96059a76851 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Wed, 25 Feb 2026 13:06:11 -0500 Subject: [PATCH] feat: Implement local image preview and explicit submission for attire capture. --- .../pages/attire_capture_page.dart | 119 +++++++++++++----- .../attire_image_preview.dart | 28 +++-- 2 files changed, 105 insertions(+), 42 deletions(-) diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_capture_page.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_capture_page.dart index 9e6e55e4..138dceff 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_capture_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/pages/attire_capture_page.dart @@ -28,6 +28,8 @@ class AttireCapturePage extends StatefulWidget { } class _AttireCapturePageState extends State { + String? _selectedLocalPath; + /// On gallery button press Future _onGallery(BuildContext context) async { final AttireCaptureCubit cubit = BlocProvider.of( @@ -43,7 +45,9 @@ class _AttireCapturePageState extends State { final GalleryService service = Modular.get(); final String? path = await service.pickImage(); if (path != null && context.mounted) { - await cubit.uploadPhoto(widget.item.id, path); + setState(() { + _selectedLocalPath = path; + }); } } catch (e) { if (context.mounted) { @@ -67,7 +71,9 @@ class _AttireCapturePageState extends State { final CameraService service = Modular.get(); final String? path = await service.takePhoto(); if (path != null && context.mounted) { - await cubit.uploadPhoto(widget.item.id, path); + setState(() { + _selectedLocalPath = path; + }); } } catch (e) { if (context.mounted) { @@ -95,6 +101,20 @@ class _AttireCapturePageState extends State { ); } + Future _onSubmit(BuildContext context) async { + final AttireCaptureCubit cubit = BlocProvider.of( + context, + ); + if (_selectedLocalPath == null) return; + + await cubit.uploadPhoto(widget.item.id, _selectedLocalPath!); + if (context.mounted && cubit.state.status == AttireCaptureStatus.success) { + setState(() { + _selectedLocalPath = null; + }); + } + } + @override Widget build(BuildContext context) { return BlocProvider( @@ -153,8 +173,35 @@ class _AttireCapturePageState extends State { padding: const EdgeInsets.all(UiConstants.space5), child: Column( children: [ - // Image Preview (Toggle between example and uploaded) - if (hasUploadedPhoto) ...[ + // Image Preview (Toggle between example, review, and uploaded) + if (_selectedLocalPath != null) ...[ + Text( + 'Review the attire item', + 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) ...[ Text( 'Your Uploaded Photo', style: UiTypography.body1b.textPrimary, @@ -216,38 +263,50 @@ class _AttireCapturePageState extends State { }, ), const SizedBox(height: UiConstants.space6), - - if (isUploading) - const Center( - child: Padding( - padding: EdgeInsets.all(UiConstants.space8), - child: CircularProgressIndicator(), - ), - ) - else - AttireUploadButtons( - onGallery: () => _onGallery(context), - onCamera: () => _onCamera(context), - ), ], ), ), ), - if (hasUploadedPhoto) - SafeArea( - child: Padding( - padding: const EdgeInsets.all(UiConstants.space5), - child: SizedBox( - width: double.infinity, - child: UiButton.primary( - text: 'Submit Image', - onPressed: () { - Modular.to.pop(currentPhotoUrl); - }, - ), - ), + SafeArea( + child: Padding( + padding: const EdgeInsets.all(UiConstants.space5), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (isUploading) + const Center( + child: Padding( + padding: EdgeInsets.all(UiConstants.space4), + child: CircularProgressIndicator(), + ), + ) + else ...[ + AttireUploadButtons( + onGallery: () => _onGallery(context), + onCamera: () => _onCamera(context), + ), + if (_selectedLocalPath != null) ...[ + const SizedBox(height: UiConstants.space4), + UiButton.primary( + fullWidth: true, + text: 'Submit Image', + onPressed: () => _onSubmit(context), + ), + ] else if (hasUploadedPhoto) ...[ + const SizedBox(height: UiConstants.space4), + UiButton.primary( + fullWidth: true, + text: 'Submit Image', + onPressed: () { + Modular.to.pop(currentPhotoUrl); + }, + ), + ], + ], + ], ), ), + ), ], ); }, diff --git a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_capture_page/attire_image_preview.dart b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_capture_page/attire_image_preview.dart index 5adfeec2..0e670951 100644 --- a/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_capture_page/attire_image_preview.dart +++ b/apps/mobile/packages/features/staff/profile_sections/onboarding/attire/lib/src/presentation/widgets/attire_capture_page/attire_image_preview.dart @@ -1,10 +1,23 @@ +import 'dart:io'; + import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; class AttireImagePreview extends StatelessWidget { - const AttireImagePreview({super.key, required this.imageUrl}); + const AttireImagePreview({super.key, this.imageUrl, this.localPath}); final String? imageUrl; + final String? localPath; + + ImageProvider get _imageProvider { + if (localPath != null) { + return FileImage(File(localPath!)); + } + return NetworkImage( + imageUrl ?? + 'https://images.unsplash.com/photo-1549298916-b41d501d3772?auto=format&fit=crop&q=80&w=400&h=400', + ); + } void _viewEnlargedImage(BuildContext context) { showDialog( @@ -17,10 +30,7 @@ class AttireImagePreview extends StatelessWidget { 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', - ), + image: _imageProvider, fit: BoxFit.contain, ), ), @@ -47,13 +57,7 @@ class AttireImagePreview extends StatelessWidget { 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, - ), + image: DecorationImage(image: _imageProvider, fit: BoxFit.cover), ), child: const Align( alignment: Alignment.bottomRight,