mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 12:23:31 +00:00
Add gallery rename and image edit/delete capabilities
This commit is contained in:
parent
0e07919af4
commit
2bb4492ccf
8 changed files with 482 additions and 30 deletions
|
@ -150,6 +150,25 @@ class GalleryClient extends FriendicaClient {
|
||||||
_networkStatusService.finishGalleryLoading();
|
_networkStatusService.finishGalleryLoading();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<bool, ExecError> renameGallery(
|
||||||
|
String oldGalleryName, String newGalleryName) async {
|
||||||
|
_networkStatusService.startGalleryLoading();
|
||||||
|
_logger.finest(() => 'Getting gallery data');
|
||||||
|
final url =
|
||||||
|
Uri.parse('https://$serverName/api/friendica/photoalbum/update');
|
||||||
|
final body = {
|
||||||
|
'album': oldGalleryName,
|
||||||
|
'album_new': newGalleryName,
|
||||||
|
};
|
||||||
|
final result = await postUrl(
|
||||||
|
url,
|
||||||
|
body,
|
||||||
|
headers: _headers,
|
||||||
|
).transform((_) => true);
|
||||||
|
_networkStatusService.finishGalleryLoading();
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupsClient extends FriendicaClient {
|
class GroupsClient extends FriendicaClient {
|
||||||
|
@ -272,6 +291,35 @@ class GroupsClient extends FriendicaClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ImageClient extends FriendicaClient {
|
||||||
|
ImageClient(super.credentials) : super();
|
||||||
|
|
||||||
|
FutureResult<ImageEntry, ExecError> editImageData(ImageEntry image) async {
|
||||||
|
_networkStatusService.startGalleryLoading();
|
||||||
|
final uri = Uri.parse('https://$serverName/api/friendica/photo/update');
|
||||||
|
final body = {
|
||||||
|
'album': image.album,
|
||||||
|
'desc': image.description,
|
||||||
|
'photo_id': image.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = await postUrl(uri, body, headers: _headers)
|
||||||
|
.andThen((_) => Result.ok(image));
|
||||||
|
_networkStatusService.finishGalleryLoading();
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureResult<ImageEntry, ExecError> deleteImage(ImageEntry image) async {
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'https://$serverName/api/friendica/photo/delete?photo_id=${image.id}',
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await postUrl(uri, {}, headers: _headers)
|
||||||
|
.andThen((_) => Result.ok(image));
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class InteractionsClient extends FriendicaClient {
|
class InteractionsClient extends FriendicaClient {
|
||||||
static final _logger = Logger('$InteractionsClient');
|
static final _logger = Logger('$InteractionsClient');
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,10 @@ class GalleryData {
|
||||||
final DateTime created;
|
final DateTime created;
|
||||||
|
|
||||||
GalleryData({required this.count, required this.name, required this.created});
|
GalleryData({required this.count, required this.name, required this.created});
|
||||||
|
|
||||||
|
GalleryData copy({String? name}) => GalleryData(
|
||||||
|
count: count,
|
||||||
|
name: name ?? this.name,
|
||||||
|
created: created,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,22 @@ class ImageEntry {
|
||||||
required this.scales,
|
required this.scales,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ImageEntry copy({
|
||||||
|
String? description,
|
||||||
|
}) =>
|
||||||
|
ImageEntry(
|
||||||
|
id: id,
|
||||||
|
album: album,
|
||||||
|
filename: filename,
|
||||||
|
description: description ?? this.description,
|
||||||
|
thumbnailUrl: thumbnailUrl,
|
||||||
|
created: created,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
visibility: visibility,
|
||||||
|
scales: scales,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'screens/group_create_screen.dart';
|
||||||
import 'screens/group_editor_screen.dart';
|
import 'screens/group_editor_screen.dart';
|
||||||
import 'screens/group_management_screen.dart';
|
import 'screens/group_management_screen.dart';
|
||||||
import 'screens/home.dart';
|
import 'screens/home.dart';
|
||||||
|
import 'screens/image_editor_screen.dart';
|
||||||
import 'screens/interactions_viewer_screen.dart';
|
import 'screens/interactions_viewer_screen.dart';
|
||||||
import 'screens/message_thread_screen.dart';
|
import 'screens/message_thread_screen.dart';
|
||||||
import 'screens/message_threads_browser_screen.dart';
|
import 'screens/message_threads_browser_screen.dart';
|
||||||
|
@ -162,9 +163,16 @@ final appRouter = GoRouter(
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'show/:name',
|
path: 'show',
|
||||||
builder: (context, state) => GalleryScreen(
|
builder: (context, state) => GalleryScreen(
|
||||||
|
galleryName: state.extra!.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'edit/:name/image/:id',
|
||||||
|
builder: (context, state) => ImageEditorScreen(
|
||||||
galleryName: state.params['name']!,
|
galleryName: state.params['name']!,
|
||||||
|
imageId: state.params['id']!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -7,6 +7,7 @@ import '../controls/padding.dart';
|
||||||
import '../controls/standard_appbar.dart';
|
import '../controls/standard_appbar.dart';
|
||||||
import '../controls/status_and_refresh_button.dart';
|
import '../controls/status_and_refresh_button.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
|
import '../models/gallery_data.dart';
|
||||||
import '../services/gallery_service.dart';
|
import '../services/gallery_service.dart';
|
||||||
import '../services/network_status_service.dart';
|
import '../services/network_status_service.dart';
|
||||||
import '../utils/active_profile_selector.dart';
|
import '../utils/active_profile_selector.dart';
|
||||||
|
@ -14,6 +15,71 @@ import '../utils/active_profile_selector.dart';
|
||||||
class GalleryBrowsersScreen extends StatelessWidget {
|
class GalleryBrowsersScreen extends StatelessWidget {
|
||||||
static final _logger = Logger('$GalleryBrowsersScreen');
|
static final _logger = Logger('$GalleryBrowsersScreen');
|
||||||
|
|
||||||
|
String? validNameChecker(String? text) {
|
||||||
|
final newName = text ?? '';
|
||||||
|
if (newName.isEmpty) {
|
||||||
|
return 'Name cannot be empty';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RegExp(
|
||||||
|
r"^[a-zA-Z0-9 ]+$",
|
||||||
|
).hasMatch(newName)) {
|
||||||
|
return 'Name must be only letters and numbers';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> renameGallery(
|
||||||
|
BuildContext context,
|
||||||
|
GalleryService service,
|
||||||
|
GalleryData gallery,
|
||||||
|
) async {
|
||||||
|
final newName = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
var controller = TextEditingController(text: gallery.name);
|
||||||
|
return Form(
|
||||||
|
child: AlertDialog(
|
||||||
|
title: const Text('Rename Gallery'),
|
||||||
|
content: TextFormField(
|
||||||
|
controller: controller,
|
||||||
|
autovalidateMode: AutovalidateMode.always,
|
||||||
|
validator: validNameChecker,
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('OK'),
|
||||||
|
onPressed: () {
|
||||||
|
if (validNameChecker(controller.text) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.pop(context,
|
||||||
|
controller.text); // showDialog() returns true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(
|
||||||
|
context, gallery.name); // showDialog() returns true
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
) ??
|
||||||
|
'';
|
||||||
|
|
||||||
|
if (newName.isEmpty || newName == gallery.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await service.renameGallery(gallery, newName);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_logger.finest('Building');
|
_logger.finest('Building');
|
||||||
|
@ -67,17 +133,20 @@ class GalleryBrowsersScreen extends StatelessWidget {
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final gallery = galleries[index];
|
final gallery = galleries[index];
|
||||||
return InkWell(
|
return ListTile(
|
||||||
onTap: () {
|
onTap: () => context.push('/gallery/show', extra: gallery.name),
|
||||||
context.push('/gallery/show/${gallery.name}');
|
title: Text(gallery.name),
|
||||||
},
|
subtitle: Text(
|
||||||
child: ListTile(
|
'#Photos: ${gallery.count}, Created: ${gallery.created}',
|
||||||
title: Text(gallery.name),
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
subtitle: Text(
|
),
|
||||||
'Created: ${gallery.created}',
|
trailing: ElevatedButton(
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
onPressed: () async => await renameGallery(
|
||||||
|
context,
|
||||||
|
service,
|
||||||
|
gallery,
|
||||||
),
|
),
|
||||||
trailing: Text('${gallery.count} Images'),
|
child: const Text('Rename'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:result_monad/result_monad.dart';
|
||||||
|
|
||||||
import '../controls/login_aware_cached_network_image.dart';
|
import '../controls/login_aware_cached_network_image.dart';
|
||||||
import '../controls/standard_appbar.dart';
|
import '../controls/standard_appbar.dart';
|
||||||
|
@ -11,10 +13,10 @@ import '../serializers/friendica/image_entry_friendica_extensions.dart';
|
||||||
import '../services/gallery_service.dart';
|
import '../services/gallery_service.dart';
|
||||||
import '../services/network_status_service.dart';
|
import '../services/network_status_service.dart';
|
||||||
import '../utils/active_profile_selector.dart';
|
import '../utils/active_profile_selector.dart';
|
||||||
|
import '../utils/snackbar_builder.dart';
|
||||||
import 'media_viewer_screen.dart';
|
import 'media_viewer_screen.dart';
|
||||||
|
|
||||||
class GalleryScreen extends StatelessWidget {
|
class GalleryScreen extends StatelessWidget {
|
||||||
static const thumbnailDimension = 350.0;
|
|
||||||
static final _logger = Logger('$GalleryScreen');
|
static final _logger = Logger('$GalleryScreen');
|
||||||
final String galleryName;
|
final String galleryName;
|
||||||
|
|
||||||
|
@ -22,32 +24,52 @@ class GalleryScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_logger.finest('Building');
|
_logger.finest('Building $galleryName');
|
||||||
final nss = getIt<NetworkStatusService>();
|
final nss = getIt<NetworkStatusService>();
|
||||||
final service = context
|
|
||||||
.watch<ActiveProfileSelector<GalleryService>>()
|
|
||||||
.activeEntry
|
|
||||||
.value;
|
|
||||||
final body = service.getGallery(galleryName).fold(
|
|
||||||
onSuccess: (galleryData) =>
|
|
||||||
buildBody(context, service, galleryData.count),
|
|
||||||
onError: (error) => buildErrorBody(error.message),
|
|
||||||
);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: StandardAppBar.build(context, galleryName, actions: [
|
appBar: StandardAppBar.build(context, galleryName, actions: [
|
||||||
StatusAndRefreshButton(
|
StatusAndRefreshButton(
|
||||||
valueListenable: nss.imageGalleryLoadingStatus,
|
valueListenable: nss.imageGalleryLoadingStatus,
|
||||||
refreshFunction: () async => await service.updateGalleryImageList(
|
refreshFunction: () async => context
|
||||||
galleryName: galleryName,
|
.read<ActiveProfileSelector<GalleryService>>()
|
||||||
withNextPage: false,
|
.activeEntry
|
||||||
nextPageOnly: false,
|
.withResultAsync(
|
||||||
),
|
(gs) async => gs.updateGalleryImageList(
|
||||||
|
galleryName: galleryName,
|
||||||
|
withNextPage: false,
|
||||||
|
nextPageOnly: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
busyColor: Theme.of(context).appBarTheme.foregroundColor,
|
busyColor: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
body: body,
|
body: _GalleryScreenBody(
|
||||||
|
galleryName: galleryName,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GalleryScreenBody extends StatelessWidget {
|
||||||
|
static const thumbnailDimension = 350.0;
|
||||||
|
static final _logger = Logger('$_GalleryScreenBody');
|
||||||
|
final String galleryName;
|
||||||
|
|
||||||
|
const _GalleryScreenBody({required this.galleryName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_logger.finest('Building');
|
||||||
|
final service = context
|
||||||
|
.watch<ActiveProfileSelector<GalleryService>>()
|
||||||
|
.activeEntry
|
||||||
|
.value;
|
||||||
|
return service.getGallery(galleryName).fold(
|
||||||
|
onSuccess: (galleryData) =>
|
||||||
|
buildBody(context, service, galleryData.count),
|
||||||
|
onError: (error) => buildErrorBody(error.message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildErrorBody(String error) {
|
Widget buildErrorBody(String error) {
|
||||||
return Center(
|
return Center(
|
||||||
|
@ -131,6 +153,48 @@ class GalleryScreen extends StatelessWidget {
|
||||||
height: thumbnailDimension,
|
height: thumbnailDimension,
|
||||||
imageUrl: image.thumbnailUrl,
|
imageUrl: image.thumbnailUrl,
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 5.0,
|
||||||
|
right: 5.0,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.scaffoldBackgroundColor
|
||||||
|
.withOpacity(0.7),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () => context.push(
|
||||||
|
'/gallery/edit/$galleryName/image/${image.id}',
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.scaffoldBackgroundColor
|
||||||
|
.withOpacity(0.7),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final confirm = await showYesNoDialog(
|
||||||
|
context, 'Delete image?');
|
||||||
|
if (confirm != true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await service
|
||||||
|
.deleteImage(image)
|
||||||
|
.withError((error) {
|
||||||
|
if (context.mounted) {
|
||||||
|
buildSnackbar(context,
|
||||||
|
'Error deleting image: $error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
if (image.description.isNotEmpty)
|
if (image.description.isNotEmpty)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 5.0,
|
bottom: 5.0,
|
||||||
|
|
156
lib/screens/image_editor_screen.dart
Normal file
156
lib/screens/image_editor_screen.dart
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
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')];
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,6 +56,11 @@ class GalleryService extends ChangeNotifier {
|
||||||
return result.errorCast();
|
return result.errorCast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final galleriesReturned = result.value.map((g) => g.name).toList();
|
||||||
|
_galleries.clear();
|
||||||
|
_galleryPages.removeWhere((key, value) => !galleriesReturned.contains(key));
|
||||||
|
_images.removeWhere((key, value) => !galleriesReturned.contains(key));
|
||||||
|
|
||||||
for (final gallery in result.value) {
|
for (final gallery in result.value) {
|
||||||
_galleries[gallery.name] = gallery;
|
_galleries[gallery.name] = gallery;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +88,21 @@ class GalleryService extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<GalleryData, ExecError> renameGallery(
|
||||||
|
GalleryData gallery, String newName) async {
|
||||||
|
if (!_galleries.containsKey(gallery.name)) {
|
||||||
|
return buildErrorResult(
|
||||||
|
type: ErrorType.notFound,
|
||||||
|
message: 'Unknown gallery: ${gallery.name}');
|
||||||
|
}
|
||||||
|
final result = await GalleryClient(profile)
|
||||||
|
.renameGallery(gallery.name, newName)
|
||||||
|
.transform((_) => gallery.copy(name: newName))
|
||||||
|
.withResultAsync((_) async => await updateGalleries());
|
||||||
|
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
|
|
||||||
//TODO Paging
|
//TODO Paging
|
||||||
FutureResult<List<ImageEntry>, ExecError> updateGalleryImageList(
|
FutureResult<List<ImageEntry>, ExecError> updateGalleryImageList(
|
||||||
{required String galleryName,
|
{required String galleryName,
|
||||||
|
@ -108,8 +128,6 @@ class GalleryService extends ChangeNotifier {
|
||||||
return result.errorCast();
|
return result.errorCast();
|
||||||
}
|
}
|
||||||
|
|
||||||
print(result.value.length);
|
|
||||||
|
|
||||||
imageSet.addAll(result.value);
|
imageSet.addAll(result.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,4 +135,71 @@ class GalleryService extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return Result.ok(imageSet.toList(growable: false));
|
return Result.ok(imageSet.toList(growable: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<ImageEntry, ExecError> getImage(String galleryName, String id) {
|
||||||
|
if (!_images.containsKey(galleryName)) {
|
||||||
|
return buildErrorResult(
|
||||||
|
type: ErrorType.notFound,
|
||||||
|
message: 'Image gallery $galleryName not known.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final potentialImages =
|
||||||
|
_images[galleryName]?.where((i) => i.id == id).toList() ?? [];
|
||||||
|
|
||||||
|
if (potentialImages.isEmpty) {
|
||||||
|
return buildErrorResult(
|
||||||
|
type: ErrorType.notFound,
|
||||||
|
message: 'Image $id not found in gallery $galleryName',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(potentialImages.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureResult<ImageEntry, ExecError> updateImage(ImageEntry image) async {
|
||||||
|
final images = _images[image.album];
|
||||||
|
if (images == null) {
|
||||||
|
buildErrorResult(
|
||||||
|
type: ErrorType.notFound, message: 'Album not found ${image.album}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final index = _images[image.album]!.indexOf(image);
|
||||||
|
if (index < 0) {
|
||||||
|
return buildErrorResult(
|
||||||
|
type: ErrorType.notFound,
|
||||||
|
message: 'Image ${image.id} does not exist for ${image.album}');
|
||||||
|
}
|
||||||
|
final result =
|
||||||
|
await ImageClient(profile).editImageData(image).withResult((_) {
|
||||||
|
images!.removeAt(index);
|
||||||
|
images.insert(index, image);
|
||||||
|
});
|
||||||
|
notifyListeners();
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureResult<ImageEntry, ExecError> deleteImage(ImageEntry image) async {
|
||||||
|
final images = _images[image.album];
|
||||||
|
if (images == null) {
|
||||||
|
buildErrorResult(
|
||||||
|
type: ErrorType.notFound, message: 'Album not found ${image.album}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final index = _images[image.album]!.indexOf(image);
|
||||||
|
if (index < 0) {
|
||||||
|
return buildErrorResult(
|
||||||
|
type: ErrorType.notFound,
|
||||||
|
message: 'Image ${image.id} does not exist for ${image.album}');
|
||||||
|
}
|
||||||
|
final result = await ImageClient(profile)
|
||||||
|
.deleteImage(image)
|
||||||
|
.withResultAsync((_) async {
|
||||||
|
images!.removeAt(index);
|
||||||
|
await updateGalleries();
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.execErrorCast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue