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> {
String? _selectedLocalPath;
/// On gallery button press
Future<void> _onGallery(BuildContext context) async {
final AttireCaptureCubit cubit = BlocProvider.of<AttireCaptureCubit>(
@@ -43,7 +45,9 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
final GalleryService service = Modular.get<GalleryService>();
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<AttireCapturePage> {
final CameraService service = Modular.get<CameraService>();
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<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
Widget build(BuildContext context) {
return BlocProvider<AttireCaptureCubit>(
@@ -153,8 +173,35 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
padding: const EdgeInsets.all(UiConstants.space5),
child: Column(
children: <Widget>[
// Image Preview (Toggle between example and uploaded)
if (hasUploadedPhoto) ...<Widget>[
// Image Preview (Toggle between example, review, and uploaded)
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(
'Your Uploaded Photo',
style: UiTypography.body1b.textPrimary,
@@ -216,38 +263,50 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
},
),
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: <Widget>[
if (isUploading)
const Center(
child: Padding(
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: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<void>(
@@ -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,