feat(photos): camera-or-gallery picker + swipeable full-screen viewer

- Adding a photo (quick-add and detail) now offers a Camera / Gallery chooser
  (shared pickPhoto helper); camera errors on desktop degrade gracefully.
- Full-screen viewer: pinch-to-zoom pans only while zoomed, so a plain
  horizontal swipe pages between photos again.

54 tests green; Linux runs.
This commit is contained in:
vjrj 2026-07-08 14:15:13 +02:00
parent 4b209d6752
commit c789cd1027
8 changed files with 136 additions and 40 deletions

View file

@ -1,46 +1,28 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import '../data/variety_repository.dart';
import '../i18n/strings.g.dart';
import '../state/quick_add_cubit.dart';
import 'photo_pick.dart';
import 'quantity_picker.dart';
/// Picks a photo and returns its bytes (or null if cancelled). Injected so
/// widget tests can supply a fake instead of the camera plugin.
typedef PhotoPicker = Future<Uint8List?> Function();
Future<void> showQuickAddSheet(
BuildContext context, {
required VarietyRepository repository,
PhotoPicker? photoPicker,
}) {
return showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (_) => BlocProvider(
create: (_) => QuickAddCubit(repository),
child: QuickAddSheet(photoPicker: photoPicker ?? _cameraPhotoPicker),
child: const QuickAddSheet(),
),
);
}
Future<Uint8List?> _cameraPhotoPicker() async {
final file = await ImagePicker().pickImage(
source: ImageSource.camera,
maxWidth: 1280,
imageQuality: 80,
);
return file?.readAsBytes();
}
class QuickAddSheet extends StatelessWidget {
const QuickAddSheet({required this.photoPicker, super.key});
final PhotoPicker photoPicker;
const QuickAddSheet({super.key});
@override
Widget build(BuildContext context) {
@ -111,7 +93,7 @@ class QuickAddSheet extends StatelessWidget {
label: Text(t.quickAdd.more),
),
),
if (state.expanded) _MoreSection(photoPicker: photoPicker),
if (state.expanded) const _MoreSection(),
const SizedBox(height: 8),
Row(
children: [
@ -136,9 +118,7 @@ class QuickAddSheet extends StatelessWidget {
}
class _MoreSection extends StatelessWidget {
const _MoreSection({required this.photoPicker});
final PhotoPicker photoPicker;
const _MoreSection();
@override
Widget build(BuildContext context) {
@ -162,7 +142,7 @@ class _MoreSection extends StatelessWidget {
alignment: Alignment.centerLeft,
child: OutlinedButton.icon(
onPressed: () async {
final bytes = await photoPicker();
final bytes = await pickPhoto(context);
if (bytes != null) cubit.photoPicked(bytes);
},
icon: const Icon(Icons.photo_camera_outlined),