feat: show dish recognition result as bottom sheet on home screen

Remove "Определить блюдо" from ScanScreen and the /scan/dish route.
The + button on each meal card now triggers dish recognition inline —
picks image, shows loading dialog, then presents DishResultSheet as a
modal bottom sheet. After adding to diary the sheet closes and the user
stays on home.

Also fix Navigator.pop crash: showDialog uses the root navigator by
default, so capture Navigator.of(context, rootNavigator: true) before
the async gap and use it to close the loading dialog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-17 16:37:00 +02:00
parent 227780e1a9
commit a32d2960c4
5 changed files with 212 additions and 136 deletions

View File

@@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import '../../core/theme/app_colors.dart';
import '../../shared/models/diary_entry.dart';
@@ -11,6 +12,8 @@ import '../../shared/models/home_summary.dart';
import '../../shared/models/meal_type.dart';
import '../menu/menu_provider.dart';
import '../profile/profile_provider.dart';
import '../scan/dish_result_screen.dart';
import '../scan/recognition_service.dart';
import 'home_provider.dart';
// ── Root screen ───────────────────────────────────────────────
@@ -727,6 +730,93 @@ class _DailyMealsSection extends ConsumerWidget {
}
}
Future<void> _pickAndShowDishResult(
BuildContext context,
WidgetRef ref,
String mealTypeId,
) async {
// 1. Choose image source
final source = await showModalBottomSheet<ImageSource>(
context: context,
builder: (_) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('Камера'),
onTap: () => Navigator.pop(context, ImageSource.camera),
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Галерея'),
onTap: () => Navigator.pop(context, ImageSource.gallery),
),
],
),
),
);
if (source == null || !context.mounted) return;
// 2. Pick image
final image = await ImagePicker().pickImage(
source: source,
imageQuality: 70,
maxWidth: 1024,
maxHeight: 1024,
);
if (image == null || !context.mounted) return;
// 3. Show loading
// Capture root navigator now (before await) to avoid using the wrong one later.
// showDialog defaults to useRootNavigator: true; Navigator.pop(context) would resolve
// to GoRouter's inner navigator instead, which only has /home and would crash.
final rootNavigator = Navigator.of(context, rootNavigator: true);
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Распознаём...'),
],
),
),
);
// 4. Call API
try {
final dish = await ref.read(recognitionServiceProvider).recognizeDish(image);
if (!context.mounted) return;
rootNavigator.pop(); // close loading
// 5. Show result as bottom sheet
showModalBottomSheet(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (sheetContext) => DishResultSheet(
dish: dish,
preselectedMealType: mealTypeId,
onAdded: () => Navigator.pop(sheetContext),
),
);
} catch (recognitionError) {
debugPrint('Dish recognition error: $recognitionError');
if (context.mounted) {
rootNavigator.pop(); // close loading
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Не удалось распознать. Попробуйте ещё раз.'),
),
);
}
}
}
class _MealCard extends ConsumerWidget {
final MealTypeOption mealTypeOption;
final List<DiaryEntry> entries;
@@ -769,8 +859,8 @@ class _MealCard extends ConsumerWidget {
icon: const Icon(Icons.add, size: 20),
visualDensity: VisualDensity.compact,
tooltip: 'Добавить блюдо',
onPressed: () =>
context.push('/scan', extra: mealTypeOption.id),
onPressed: () => _pickAndShowDishResult(
context, ref, mealTypeOption.id),
),
],
),