feat: Implement local image preview and explicit submission for attire capture.

This commit is contained in:
Achintha Isuru
2026-02-25 13:06:11 -05:00
parent ed2b4f0563
commit 74d8d4d4d9
2 changed files with 105 additions and 42 deletions

View File

@@ -28,6 +28,8 @@ class AttireCapturePage extends StatefulWidget {
} }
class _AttireCapturePageState extends State<AttireCapturePage> { class _AttireCapturePageState extends State<AttireCapturePage> {
String? _selectedLocalPath;
/// 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>(
@@ -43,7 +45,9 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
final GalleryService service = Modular.get<GalleryService>(); final GalleryService service = Modular.get<GalleryService>();
final String? path = await service.pickImage(); final String? path = await service.pickImage();
if (path != null && context.mounted) { if (path != null && context.mounted) {
await cubit.uploadPhoto(widget.item.id, path); setState(() {
_selectedLocalPath = path;
});
} }
} catch (e) { } catch (e) {
if (context.mounted) { if (context.mounted) {
@@ -67,7 +71,9 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
final CameraService service = Modular.get<CameraService>(); final CameraService service = Modular.get<CameraService>();
final String? path = await service.takePhoto(); final String? path = await service.takePhoto();
if (path != null && context.mounted) { if (path != null && context.mounted) {
await cubit.uploadPhoto(widget.item.id, path); setState(() {
_selectedLocalPath = path;
});
} }
} catch (e) { } catch (e) {
if (context.mounted) { if (context.mounted) {
@@ -95,6 +101,20 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
); );
} }
Future<void> _onSubmit(BuildContext context) async {
final AttireCaptureCubit cubit = BlocProvider.of<AttireCaptureCubit>(
context,
);
if (_selectedLocalPath == null) return;
await cubit.uploadPhoto(widget.item.id, _selectedLocalPath!);
if (context.mounted && cubit.state.status == AttireCaptureStatus.success) {
setState(() {
_selectedLocalPath = null;
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<AttireCaptureCubit>( return BlocProvider<AttireCaptureCubit>(
@@ -153,8 +173,35 @@ 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 and uploaded) // Image Preview (Toggle between example, review, and uploaded)
if (hasUploadedPhoto) ...<Widget>[ if (_selectedLocalPath != null) ...<Widget>[
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) ...<Widget>[
Text( Text(
'Your Uploaded Photo', 'Your Uploaded Photo',
style: UiTypography.body1b.textPrimary, style: UiTypography.body1b.textPrimary,
@@ -216,38 +263,50 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
}, },
), ),
const SizedBox(height: UiConstants.space6), 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(
SafeArea( child: Padding(
child: Padding( padding: const EdgeInsets.all(UiConstants.space5),
padding: const EdgeInsets.all(UiConstants.space5), child: Column(
child: SizedBox( mainAxisSize: MainAxisSize.min,
width: double.infinity, children: <Widget>[
child: UiButton.primary( if (isUploading)
text: 'Submit Image', const Center(
onPressed: () { child: Padding(
Modular.to.pop(currentPhotoUrl); padding: EdgeInsets.all(UiConstants.space4),
}, child: CircularProgressIndicator(),
), ),
), )
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(currentPhotoUrl);
},
),
],
],
],
), ),
), ),
),
], ],
); );
}, },

View File

@@ -1,10 +1,23 @@
import 'dart:io';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AttireImagePreview extends StatelessWidget { class AttireImagePreview extends StatelessWidget {
const AttireImagePreview({super.key, required this.imageUrl}); const AttireImagePreview({super.key, this.imageUrl, this.localPath});
final String? imageUrl; 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) { void _viewEnlargedImage(BuildContext context) {
showDialog<void>( showDialog<void>(
@@ -17,10 +30,7 @@ class AttireImagePreview extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
image: DecorationImage( image: DecorationImage(
image: NetworkImage( image: _imageProvider,
imageUrl ??
'https://images.unsplash.com/photo-1549298916-b41d501d3772?auto=format&fit=crop&q=80&w=400&h=400',
),
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@@ -47,13 +57,7 @@ class AttireImagePreview extends StatelessWidget {
offset: Offset(0, 2), offset: Offset(0, 2),
), ),
], ],
image: DecorationImage( image: DecorationImage(image: _imageProvider, fit: BoxFit.cover),
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( child: const Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,