relatica/lib/screens/image_editor_screen.dart

167 wiersze
5.3 KiB
Dart

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<ImageEditorScreen> createState() => _ImageEditorScreenState();
}
class _ImageEditorScreenState extends ConsumerState<ImageEditorScreen> {
Result<ImageEntry, ExecError>? 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<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,
textCapitalization: TextCapitalization.sentences,
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')];
}
}