import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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 '../riverpod_controllers/account_services.dart'; import '../riverpod_controllers/gallery_services.dart'; import '../utils/snackbar_builder.dart'; class ImageEditorScreen extends ConsumerStatefulWidget { final String galleryName; final String imageId; const ImageEditorScreen({ super.key, required this.galleryName, required this.imageId, }); @override ConsumerState createState() => _ImageEditorScreenState(); } class _ImageEditorScreenState extends ConsumerState { Result? originalImageResult; final altTextController = TextEditingController(); bool get changed => originalImageResult ?.transform((image) => image.description != altTextController.text) .getValueOrElse(() => false) ?? false; @override Widget build(BuildContext context) { final profile = ref.watch(activeProfileProvider); ref .watch(galleryImageProvider(profile, widget.galleryName, widget.imageId) .future) .then((value) { if (originalImageResult == value) { return; } setState(() { originalImageResult = value; altTextController.text = value.isSuccess ? value.value.description : ''; }); }); 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 CircularProgressIndicator()], const VerticalPadding(), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () async { if (!changed) { return; } if (originalImageResult?.isFailure ?? true) { return; } final result = await ref .read(galleryImagesProvider( profile, widget.galleryName) .notifier) .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 && context.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, textCapitalization: TextCapitalization.sentences, 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')]; } }