forked from eatvio/eatvio_app
feat(image_validation): image validation fix
This commit is contained in:
@@ -37,6 +37,17 @@ class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
|
||||
}
|
||||
}
|
||||
|
||||
void setImagesError(String? error) {
|
||||
if (state.imagesError == error &&
|
||||
state.formError == null &&
|
||||
state.errorMessage == null) {
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
state.copyWith(imagesError: error, formError: null, errorMessage: null),
|
||||
);
|
||||
}
|
||||
|
||||
void resetFormErrors() {
|
||||
if (!state.hasValidationErrors &&
|
||||
!state.isSuccess &&
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:eatvio_app/app_keys.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
@@ -38,11 +39,15 @@ class BlogPostCreateScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
static const _minImageWidth = 500;
|
||||
static const _minImageHeight = 500;
|
||||
|
||||
final blogPostCubit = sl<BlogPostCreateCubit>();
|
||||
bool shrinkImage = false;
|
||||
late StreamSubscription<bool> keyboardSubscription;
|
||||
final List<FeedGalleryImage> savedImages = [];
|
||||
final List<XFile> pendingImages = [];
|
||||
final Set<String> _undersizedPendingPaths = {};
|
||||
final List<int> mediaIdsToDelete = [];
|
||||
final titleController = TextEditingController();
|
||||
final descriptionController = TextEditingController();
|
||||
@@ -531,10 +536,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
source: ImageSource.camera,
|
||||
);
|
||||
if (image != null && _remainingSlots > 0) {
|
||||
setState(() {
|
||||
pendingImages.add(image);
|
||||
});
|
||||
blogPostCubit.clearImagesError();
|
||||
await _addPendingImages([image]);
|
||||
}
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
},
|
||||
@@ -561,10 +563,9 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
limit: _remainingSlots,
|
||||
);
|
||||
if (images.isNotEmpty) {
|
||||
setState(() {
|
||||
pendingImages.addAll(images.take(_remainingSlots));
|
||||
});
|
||||
blogPostCubit.clearImagesError();
|
||||
await _addPendingImages(
|
||||
images.take(_remainingSlots).toList(),
|
||||
);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
AppRouter.navigatorKey.currentContext?.pop();
|
||||
@@ -603,9 +604,12 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
}
|
||||
|
||||
void _removePendingImage(int index) {
|
||||
final path = pendingImages[index].path;
|
||||
setState(() {
|
||||
pendingImages.removeAt(index);
|
||||
_undersizedPendingPaths.remove(path);
|
||||
});
|
||||
_syncImagesSizeError();
|
||||
}
|
||||
|
||||
void _onReorderGallery(int oldIndex, int newIndex) {
|
||||
@@ -839,6 +843,11 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_undersizedPendingPaths.isNotEmpty) {
|
||||
blogPostCubit.setImagesError(S.current.blog_post_image_min_size_error);
|
||||
return;
|
||||
}
|
||||
|
||||
final title = titleController.text.trim();
|
||||
final description = descriptionController.text.trim();
|
||||
final videoUrl = videoUrlController.text.trim();
|
||||
@@ -907,6 +916,50 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addPendingImages(List<XFile> images) async {
|
||||
if (images.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final undersizedPaths = <String>{};
|
||||
for (final image in images) {
|
||||
if (!await _isImageLargeEnough(image)) {
|
||||
undersizedPaths.add(image.path);
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
pendingImages.addAll(images);
|
||||
_undersizedPendingPaths.addAll(undersizedPaths);
|
||||
});
|
||||
_syncImagesSizeError();
|
||||
}
|
||||
|
||||
void _syncImagesSizeError() {
|
||||
final minSizeError = S.current.blog_post_image_min_size_error;
|
||||
if (_undersizedPendingPaths.isNotEmpty) {
|
||||
blogPostCubit.setImagesError(minSizeError);
|
||||
return;
|
||||
}
|
||||
if (blogPostCubit.state.imagesError == minSizeError) {
|
||||
blogPostCubit.clearImagesError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _isImageLargeEnough(XFile image) async {
|
||||
try {
|
||||
final bytes = await File(image.path).readAsBytes();
|
||||
final descriptor = await ui.ImmutableBuffer.fromUint8List(bytes);
|
||||
final codec = await ui.instantiateImageCodecFromBuffer(descriptor);
|
||||
final frame = await codec.getNextFrame();
|
||||
final width = frame.image.width;
|
||||
final height = frame.image.height;
|
||||
return width >= _minImageWidth && height >= _minImageHeight;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildGallery() {
|
||||
final itemHeight = shrinkImage ? 72.0 : 112.0;
|
||||
|
||||
@@ -946,6 +999,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
child: _buildGalleryTile(
|
||||
index: index,
|
||||
height: itemHeight,
|
||||
hasError: _undersizedPendingPaths.contains(pending.path),
|
||||
imageProvider: FileImage(File(pending.path)),
|
||||
onRemove: () => _removePendingImage(pendingIndex),
|
||||
),
|
||||
@@ -985,6 +1039,7 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
required double height,
|
||||
required ImageProvider imageProvider,
|
||||
required VoidCallback onRemove,
|
||||
bool hasError = false,
|
||||
}) {
|
||||
return ReorderableDelayedDragStartListener(
|
||||
index: index,
|
||||
@@ -992,12 +1047,16 @@ class _BlogPostCreateScreenState extends State<BlogPostCreateScreen> {
|
||||
width: height,
|
||||
height: height,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: hasError ? Border.all(color: appTheme.red, width: 2) : null,
|
||||
),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Image(image: imageProvider, fit: BoxFit.cover),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -137,6 +137,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Средние показатели давления:",
|
||||
),
|
||||
"back_to_chat": MessageLookupByLibrary.simpleMessage("Вернуться в чат"),
|
||||
"blog_post_image_min_size_error": MessageLookupByLibrary.simpleMessage(
|
||||
"Минимальный размер изображения — 500×500 px",
|
||||
),
|
||||
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
|
||||
"by_default": MessageLookupByLibrary.simpleMessage("По умолчанию"),
|
||||
"calls_for_bullying": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -55,6 +55,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"average_pressure": MessageLookupByLibrary.simpleMessage(
|
||||
"Средние показатели давления:",
|
||||
),
|
||||
"blog_post_image_min_size_error": MessageLookupByLibrary.simpleMessage(
|
||||
"Минимальный размер изображения — 500×500 px",
|
||||
),
|
||||
"bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"),
|
||||
"by_default": MessageLookupByLibrary.simpleMessage("По умолчанию"),
|
||||
"calories": MessageLookupByLibrary.simpleMessage("Калории"),
|
||||
|
||||
@@ -2569,6 +2569,16 @@ class S {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Минимальный размер изображения — 500×500 px`
|
||||
String get blog_post_image_min_size_error {
|
||||
return Intl.message(
|
||||
'Минимальный размер изображения — 500×500 px',
|
||||
name: 'blog_post_image_min_size_error',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Информация о заказе`
|
||||
String get infoAboutOrder {
|
||||
return Intl.message(
|
||||
|
||||
@@ -317,6 +317,7 @@
|
||||
"could_not_publish_blog_post": "Не удалось опубликовать пост",
|
||||
"could_not_update_blog_post": "Не удалось обновить пост",
|
||||
"could_not_delete_blog_post": "Не удалось удалить пост",
|
||||
"blog_post_image_min_size_error": "Минимальный размер изображения — 500×500 px",
|
||||
"infoAboutOrder": "Информация о заказе",
|
||||
"to_order": "Заказать",
|
||||
"can_not_choose_start_date": "Дату старта плана невозможно выбрать, пока Вы не привяжите карту",
|
||||
|
||||
@@ -270,5 +270,6 @@
|
||||
"video_call_close": "Закрыть",
|
||||
"video_call_notification_in_progress": "Звонок идёт",
|
||||
"video_call_notification_return": "Вернуться к звонку",
|
||||
"video_call_notification_end": "Завершить звонок"
|
||||
"video_call_notification_end": "Завершить звонок",
|
||||
"blog_post_image_min_size_error": "Минимальный размер изображения — 500×500 px"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user