// lib/widgets/map_edit_tools/note_photo_gallery.dart // // Fotó galéria a MapFeatureSaveSheet-ben: // - Vízszintes görgetős sor // - + gomb: kamera / galéria választó // - Fotóra koppintva: teljes képernyős nézet + felirat szerkesztés // - Fotóra hosszan nyomva: törlés import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../models/note_item_photo.dart'; import '../../services/note_photo_service.dart'; class NotePhotoGallery extends StatefulWidget { /// A szerkesztett NoteItem id-ja — null ha az elem még nincs elmentve final int? noteItemId; const NotePhotoGallery({super.key, required this.noteItemId}); @override State createState() => _NotePhotoGalleryState(); } class _NotePhotoGalleryState extends State { List _photos = []; bool _loading = false; @override void initState() { super.initState(); _loadPhotos(); } Future _loadPhotos() async { if (widget.noteItemId == null) return; setState(() => _loading = true); _photos = await NotePhotoService.to.loadPhotos(widget.noteItemId!); if (mounted) setState(() => _loading = false); } @override Widget build(BuildContext context) { // NoteItem nem mentett még — nem lehet fotót hozzáadni if (widget.noteItemId == null) { return const _DisabledGallery(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Text('Fotók', style: TextStyle(fontSize: 13, color: Colors.grey.shade600)), const SizedBox(width: 6), if (_photos.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(10), ), child: Text( '${_photos.length}', style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600), ), ), ]), const SizedBox(height: 8), if (_loading) const SizedBox( height: 80, child: Center(child: CircularProgressIndicator()), ) else SizedBox( height: 88, child: ListView( scrollDirection: Axis.horizontal, children: [ // Meglévő fotók ..._photos.map((photo) => _PhotoThumb( photo: photo, onTap: () => _openViewer(photo), onDelete: () => _delete(photo), )), // + Fotó hozzáadása gomb _AddPhotoButton( onCamera: () => _addPhoto(fromCamera: true), onGallery: () => _addPhoto(fromCamera: false), ), ], ), ), ], ); } Future _addPhoto({required bool fromCamera}) async { final svc = NotePhotoService.to; final photo = fromCamera ? await svc.takePhoto(widget.noteItemId!) : await svc.pickFromGallery(widget.noteItemId!); if (photo != null && mounted) { setState(() => _photos.add(photo)); } } Future _delete(NoteItemPhoto photo) async { final ok = await Get.dialog(AlertDialog( title: const Text('Fotó törlése'), content: const Text('Ez a fotó véglegesen törlődik.'), actions: [ TextButton(onPressed: Get.back, child: const Text('Mégse')), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Get.back(result: true), child: const Text('Törlés'), ), ], )); if (ok == true && mounted) { await NotePhotoService.to.deletePhoto(photo); setState(() => _photos.removeWhere((p) => p.id == photo.id)); } } void _openViewer(NoteItemPhoto photo) { Get.to(() => _PhotoViewerPage( photos: _photos, initialIndex: _photos.indexWhere((p) => p.id == photo.id), onCaptionSaved: (updated) { setState(() { final idx = _photos.indexWhere((p) => p.id == updated.id); if (idx >= 0) _photos[idx] = updated; }); }, )); } } // ─── Fotó bélyegkép ────────────────────────────────────────────────────────── class _PhotoThumb extends StatelessWidget { final NoteItemPhoto photo; final VoidCallback onTap; final VoidCallback onDelete; const _PhotoThumb({ required this.photo, required this.onTap, required this.onDelete, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 8), child: GestureDetector( onTap: onTap, onLongPress: onDelete, child: Stack(children: [ ClipRRect( borderRadius: BorderRadius.circular(8), child: photo.fileExists ? Image.file( photo.file, width: 80, height: 80, fit: BoxFit.cover, cacheWidth: 160, // memória optimalizálás ) : Container( width: 80, height: 80, color: Colors.grey.shade200, child: const Icon(Icons.broken_image, color: Colors.grey), ), ), // Felirat jelzése ha van if (photo.caption.isNotEmpty) Positioned( bottom: 0, left: 0, right: 0, child: Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.black.withOpacity(0.7), Colors.transparent, ], ), borderRadius: const BorderRadius.vertical(bottom: Radius.circular(8)), ), child: Text( photo.caption, style: const TextStyle(color: Colors.white, fontSize: 9), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), // GPS jelzése ha van if (photo.location != null) Positioned( top: 4, right: 4, child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(4), ), child: const Icon(Icons.location_on, color: Colors.white, size: 10), ), ), ]), ), ); } } // ─── Hozzáadás gomb ────────────────────────────────────────────────────────── class _AddPhotoButton extends StatelessWidget { final VoidCallback onCamera; final VoidCallback onGallery; const _AddPhotoButton({ required this.onCamera, required this.onGallery, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: () => _showPicker(context), child: Container( width: 80, height: 80, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.grey.shade300, width: 1.5, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.add_a_photo_outlined, size: 24, color: Colors.grey.shade500), const SizedBox(height: 4), Text('Fotó', style: TextStyle(fontSize: 10, color: Colors.grey.shade500)), ], ), ), ); } void _showPicker(BuildContext context) { showModalBottomSheet( context: context, builder: (_) => SafeArea( child: Column(mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.camera_alt_outlined), title: const Text('Kamera'), onTap: () { Navigator.pop(context); onCamera(); }, ), ListTile( leading: const Icon(Icons.photo_library_outlined), title: const Text('Galéria'), onTap: () { Navigator.pop(context); onGallery(); }, ), ]), ), ); } } // ─── Letiltott galéria (elem még nincs mentve) ─────────────────────────────── class _DisabledGallery extends StatelessWidget { const _DisabledGallery(); @override Widget build(BuildContext context) { return Container( height: 50, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), ), child: Text( 'Mentés után adhatók hozzá fotók', style: TextStyle(fontSize: 12, color: Colors.grey.shade500), ), ); } } // ─── Teljes képernyős fotónézegető ─────────────────────────────────────────── class _PhotoViewerPage extends StatefulWidget { final List photos; final int initialIndex; final ValueChanged onCaptionSaved; const _PhotoViewerPage({ required this.photos, required this.initialIndex, required this.onCaptionSaved, }); @override State<_PhotoViewerPage> createState() => _PhotoViewerPageState(); } class _PhotoViewerPageState extends State<_PhotoViewerPage> { late PageController _pageCtrl; late int _currentIdx; @override void initState() { super.initState(); _currentIdx = widget.initialIndex; _pageCtrl = PageController(initialPage: widget.initialIndex); } @override void dispose() { _pageCtrl.dispose(); super.dispose(); } NoteItemPhoto get _current => widget.photos[_currentIdx]; @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.black, foregroundColor: Colors.white, title: widget.photos.length > 1 ? Text('${_currentIdx + 1} / ${widget.photos.length}') : null, actions: [ // Felirat szerkesztés IconButton( icon: const Icon(Icons.edit_outlined, color: Colors.white), tooltip: 'Felirat szerkesztése', onPressed: _editCaption, ), ], ), body: Column(children: [ // Fotó Expanded( child: PageView.builder( controller: _pageCtrl, itemCount: widget.photos.length, onPageChanged: (i) => setState(() => _currentIdx = i), itemBuilder: (_, i) { final photo = widget.photos[i]; return InteractiveViewer( child: Center( child: photo.fileExists ? Image.file(photo.file, fit: BoxFit.contain) : const Icon(Icons.broken_image, color: Colors.white54, size: 64), ), ); }, ), ), // Felirat + helyadatok if (_current.caption.isNotEmpty || _current.location != null) Container( color: Colors.black.withOpacity(0.7), padding: EdgeInsets.fromLTRB( 16, 10, 16, 10 + MediaQuery.of(context).padding.bottom, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (_current.caption.isNotEmpty) Text(_current.caption, style: const TextStyle(color: Colors.white, fontSize: 14)), if (_current.location != null) Text( '📍 ${_current.latitude!.toStringAsFixed(6)}, ' '${_current.longitude!.toStringAsFixed(6)}', style: const TextStyle(color: Colors.white54, fontSize: 11), ), ], ), ), ]), ); } Future _editCaption() async { final ctrl = TextEditingController(text: _current.caption); final result = await Get.dialog(AlertDialog( title: const Text('Felirat'), content: TextField( controller: ctrl, decoration: const InputDecoration( hintText: 'Fotó leírása...', ), autofocus: true, maxLines: 3, ), actions: [ TextButton(onPressed: Get.back, child: const Text('Mégse')), FilledButton( onPressed: () => Get.back(result: ctrl.text.trim()), child: const Text('Mentés'), ), ], )); if (result != null) { final updated = await NotePhotoService.to.updateCaption(_current, result); widget.onCaptionSaved(updated); setState(() {}); } } }