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:
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user