Compare commits

...

9 Commits

10 changed files with 101 additions and 16 deletions

View File

@@ -78,12 +78,7 @@
<data android:host="eatvio.com" />
<data android:host="www.eatvio.com" />
<!-- Development/Subdomains -->
<data android:host="*.eatvio.ru" />
<data android:host="*.eatvio.com" />
<!-- Simplify patterns to allow navigation within the app for all matching hosts -->
<data android:pathPattern="/.*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.

View File

@@ -8,6 +8,8 @@
<array>
<string>applinks:eatvio.ru</string>
<string>applinks:www.eatvio.ru</string>
<string>applinks:eatvio.com</string>
<string>applinks:www.eatvio.com</string>
<string>applinks:5399b03636104c6d920b87c1ad329254.oauth.yandex.ru</string>
</array>
</dict>

View File

@@ -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 &&

View File

@@ -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),
),
),

View File

@@ -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(

View File

@@ -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("Калории"),

View File

@@ -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(

View File

@@ -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": "Дату старта плана невозможно выбрать, пока Вы не привяжите карту",

View File

@@ -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"
}

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.40+129
version: 1.3.42+131
publish_to: 'none'
environment: