import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:result_monad/result_monad.dart'; import '../controls/login_aware_cached_network_image.dart'; import '../controls/padding.dart'; import '../controls/responsive_max_width.dart'; import '../controls/standard_appbar.dart'; import '../globals.dart'; import '../models/exec_error.dart'; import '../models/image_entry.dart'; import '../models/visibility.dart'; import '../services/gallery_service.dart'; import '../utils/active_profile_selector.dart'; import '../utils/snackbar_builder.dart'; class ImageEditorScreen extends StatefulWidget { final String galleryName; final String imageId; const ImageEditorScreen({ super.key, required this.galleryName, required this.imageId, }); @override State createState() => _ImageEditorScreenState(); } class _ImageEditorScreenState extends State { late final Result originalImageResult; final altTextController = TextEditingController(); @override void initState() { super.initState(); originalImageResult = getIt>() .activeEntry .andThen((gs) => gs.getImage(widget.galleryName, widget.imageId)) .withResult((image) { altTextController.text = image.description; }).execErrorCast(); } bool get changed => originalImageResult .transform((image) => image.description != altTextController.text) .getValueOrElse(() => false); @override Widget build(BuildContext context) { return Scaffold( appBar: StandardAppBar.build( context, 'Edit Image', withDrawer: true, ), body: SingleChildScrollView( child: ResponsiveMaxWidth( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ ...originalImageResult.fold( onSuccess: (image) => buildEditor(image), onError: (error) => buildError(error), ), const VerticalPadding(), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () async { if (!changed) { return; } final result = await getIt< ActiveProfileSelector>() .activeEntry .andThenAsync( (gs) async => await gs .updateImage(originalImageResult.value.copy( description: altTextController.text, )), ); if (!mounted) { return; } result.match( onSuccess: (_) => context.pop(), onError: (error) => buildSnackbar(context, 'Error attempting to update image: $error'), ); }, child: const Text('Save')), const HorizontalPadding(), ElevatedButton( onPressed: () async { if (!changed) { context.pop(); } final ok = await showYesNoDialog( context, 'Cancel changes?', ); if (ok == true && mounted) { context.pop(); } }, child: const Text('Cancel')), ], ) ], ), ), ), ), ); } List buildEditor(ImageEntry originalImage) { return [ Row( children: [ const Text('Visibility:'), const HorizontalPadding(), originalImage.visibility.type == VisibilityType.public ? const Icon(Icons.public) : const Icon(Icons.lock), ], ), const VerticalPadding(), LoginAwareCachedNetworkImage(imageUrl: originalImage.thumbnailUrl), const VerticalPadding(), TextField( controller: altTextController, maxLines: 10, decoration: InputDecoration( labelText: 'ALT Text', alignLabelWithHint: true, border: OutlineInputBorder( borderSide: const BorderSide(), borderRadius: BorderRadius.circular(5.0), ), ), ), ]; } List buildError(ExecError error) { return [Text('Error loading image: $error')]; } }