feat: Implement local image preview and explicit submission for attire capture.
This commit is contained in:
@@ -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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user