relatica/lib/screens/image_editor_screen.dart

157 wiersze
5.0 KiB
Dart

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<ImageEditorScreen> createState() => _ImageEditorScreenState();
}
class _ImageEditorScreenState extends State<ImageEditorScreen> {
late final Result<ImageEntry, ExecError> originalImageResult;
final altTextController = TextEditingController();
@override
void initState() {
super.initState();
originalImageResult = getIt<ActiveProfileSelector<GalleryService>>()
.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<GalleryService>>()
.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<Widget> 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<Widget> buildError(ExecError error) {
return [Text('Error loading image: $error')];
}
}