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:
parent
4b209d6752
commit
c789cd1027
8 changed files with 136 additions and 40 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ import '../db/enums.dart';
|
|||
import '../i18n/strings.g.dart';
|
||||
import '../state/variety_detail_cubit.dart';
|
||||
import 'harvest_date_picker.dart';
|
||||
import 'photo_pick.dart';
|
||||
import 'quantity_kind_l10n.dart';
|
||||
import 'quantity_picker.dart';
|
||||
import 'seed_glyph.dart';
|
||||
|
|
@ -854,12 +856,7 @@ class _PhotoGalleryState extends State<_PhotoGallery> {
|
|||
}
|
||||
|
||||
Future<void> _addPhoto(VarietyDetailCubit cubit) async {
|
||||
final file = await ImagePicker().pickImage(
|
||||
source: ImageSource.gallery,
|
||||
maxWidth: 1280,
|
||||
imageQuality: 80,
|
||||
);
|
||||
final bytes = await file?.readAsBytes();
|
||||
final bytes = await pickPhoto(context);
|
||||
if (bytes != null) await cubit.addPhoto(bytes);
|
||||
}
|
||||
|
||||
|
|
@ -1042,16 +1039,49 @@ class _PhotoViewer extends StatelessWidget {
|
|||
body: PageView.builder(
|
||||
controller: PageController(initialPage: initialIndex),
|
||||
itemCount: photos.length,
|
||||
itemBuilder: (_, i) => InteractiveViewer(
|
||||
minScale: 1,
|
||||
maxScale: 5,
|
||||
child: Center(child: Image.memory(photos[i].bytes)),
|
||||
),
|
||||
itemBuilder: (_, i) => _ZoomablePhoto(bytes: photos[i].bytes),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pinch-to-zoom photo that only pans while zoomed, so a plain horizontal
|
||||
/// swipe still pages the surrounding [PageView].
|
||||
class _ZoomablePhoto extends StatefulWidget {
|
||||
const _ZoomablePhoto({required this.bytes});
|
||||
|
||||
final Uint8List bytes;
|
||||
|
||||
@override
|
||||
State<_ZoomablePhoto> createState() => _ZoomablePhotoState();
|
||||
}
|
||||
|
||||
class _ZoomablePhotoState extends State<_ZoomablePhoto> {
|
||||
final _controller = TransformationController();
|
||||
bool _zoomed = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InteractiveViewer(
|
||||
transformationController: _controller,
|
||||
minScale: 1,
|
||||
maxScale: 5,
|
||||
panEnabled: _zoomed,
|
||||
onInteractionEnd: (_) {
|
||||
final zoomed = _controller.value.getMaxScaleOnAxis() > 1.01;
|
||||
if (zoomed != _zoomed) setState(() => _zoomed = zoomed);
|
||||
},
|
||||
child: Center(child: Image.memory(widget.bytes)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionTitle extends StatelessWidget {
|
||||
const _SectionTitle(this.text);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue