relatica/lib/riverpod_controllers/gallery_services.dart

223 wiersze
6.9 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
import '../models/gallery_data.dart';
import '../models/image_entry.dart';
import '../models/networking/paging_data.dart';
import 'networking/friendica_gallery_client_services.dart';
import 'networking/friendica_image_client_services.dart';
import 'rp_provider_extension.dart';
part 'gallery_services.g.dart';
final _galleryLogger = Logger('GalleriesProvider');
@riverpod
class _Galleries extends _$Galleries {
@override
Future<Result<Map<String, GalleryData>, ExecError>> build(
Profile profile,
) async {
_galleryLogger.info('Build for $profile');
final result = await updateGalleries();
ref.cacheFor(const Duration(minutes: 30));
return result;
}
Future<Result<Map<String, GalleryData>, ExecError>> updateGalleries() async {
_galleryLogger.info('Updating galleries for $profile');
//may need to force update
final result = await ref.read(galleryDataProvider(profile).future);
if (result.isFailure) {
return result.errorCast();
}
final galleriesMap = {for (final g in result.value) g.name: g};
_galleryLogger
.info(() => 'New gallery data for $profile : ${galleriesMap.values}');
final rval = Result.ok(galleriesMap).execErrorCast();
state = AsyncData(rval);
return rval;
}
}
final _glLogger = Logger('GalleryListProvider');
@riverpod
class GalleryList extends _$GalleryList {
@override
Future<Result<List<GalleryData>, ExecError>> build(Profile profile) async {
_glLogger.info('Build for $profile');
final result = await ref
.watch(_galleriesProvider(profile).future)
.transform((gm) => gm.values.toList())
.execErrorCastAsync();
ref.cacheFor(const Duration(minutes: 30));
return result;
}
Future<Result<Map<String, GalleryData>, ExecError>> updateGalleries() async {
_galleryLogger.info('Updating galleries for $profile');
return await ref
.read(_galleriesProvider(profile).notifier)
.updateGalleries();
}
}
@riverpod
class Gallery extends _$Gallery {
@override
Future<Result<GalleryData, ExecError>> build(
Profile profile, String galleryName) async {
final result = await ref
.watch(_galleriesProvider(profile).future)
.andThen<GalleryData, ExecError>((gm) => gm.containsKey(galleryName)
? Result.ok(gm[galleryName]!)
: buildErrorResult(
type: ErrorType.notFound,
message: '$galleryName does not exist'))
.execErrorCastAsync();
ref.cacheFor(const Duration(minutes: 30));
return result;
}
Future<Result<bool, ExecError>> rename(String newName) async {
if (newName.isEmpty) {
return buildErrorResult(
type: ErrorType.argumentError,
message: 'Gallery name cannot be empty',
);
}
final result = await ref
.read(renameGalleryProvider(profile, galleryName, newName).future)
.withResultAsync(
(_) async => await ref
.read(_galleriesProvider(profile).notifier)
.updateGalleries(),
)
.withResult((_) => ref.invalidateSelf());
return result.execErrorCast();
}
}
@riverpod
class GalleryImages extends _$GalleryImages {
static const _imagesPerPage = 50;
final pages = <PagingData>[];
@override
Future<Result<List<ImageEntry>, ExecError>> build(
Profile profile, String galleryName) async {
final result = await updateGalleryImages(
withNextPage: true,
);
ref.cacheFor(const Duration(minutes: 30));
return result;
}
FutureResult<List<ImageEntry>, ExecError> updateGalleryImages(
{required bool withNextPage, bool nextPageOnly = true}) async {
if (pages.isEmpty) {
pages.add(PagingData(offset: 0, limit: _imagesPerPage));
} else if (withNextPage) {
final offset = pages.last.offset! + _imagesPerPage;
pages.add(PagingData(offset: offset, limit: _imagesPerPage));
}
final imageSet = switch (state) {
AsyncData(:final value) => value.fold(
onSuccess: (images) => images.toSet(),
onError: (_) => <ImageEntry>{}),
_ => <ImageEntry>{},
};
final pagesToUse = nextPageOnly ? [pages.last] : pages;
for (final page in pagesToUse) {
final result = await ref
.read(galleryImagesClientProvider(profile, galleryName, page).future);
if (result.isFailure) {
return result.errorCast();
} else {
imageSet.addAll(result.value);
}
}
final result = Result.ok(imageSet.toList()).execErrorCast();
state = AsyncData(result);
return result;
}
FutureResult<ImageEntry, ExecError> updateImage(ImageEntry image) async {
final List<ImageEntry> images = switch (state) {
AsyncData(:final value) => value.fold(
onSuccess: (images) => List.from(images),
onError: (_) => <ImageEntry>[]),
_ => <ImageEntry>[],
};
final index = images.indexOf(image);
if (index < 0) {
return buildErrorResult(
type: ErrorType.notFound,
message: 'Image ${image.id} does not exist for ${image.album}');
}
final result = await ref
.read(editImageDataProvider(profile, image).future)
.withResult((_) {
images[index] = image;
});
state = AsyncData(result.transform((_) => images).execErrorCast());
return result.execErrorCast();
}
FutureResult<ImageEntry, ExecError> deleteImage(ImageEntry image) async {
final images = switch (state) {
AsyncData(:final value) => value.fold(
onSuccess: (images) => images.toSet(),
onError: (_) => <ImageEntry>{}),
_ => <ImageEntry>{},
};
if (!images.contains(image)) {
return buildErrorResult(
type: ErrorType.notFound,
message: 'Image ${image.id} does not exist for ${image.album}');
}
final result = await ref
.read(deleteImageProvider(profile, image).future)
.withResult((_) => images.remove(image));
state = AsyncData(result.transform((_) => images.toList()).execErrorCast());
return result.execErrorCast();
}
void refresh() {
ref.invalidateSelf();
}
}
@riverpod
Future<Result<ImageEntry, ExecError>> galleryImage(
Ref ref, Profile profile, String galleryName, String id) async {
final result = await ref
.read(galleryImagesProvider(profile, galleryName).future)
.transform((images) => images.where((i) => i.id == id).toList())
.andThen<ImageEntry, ExecError>((images) => images.isNotEmpty
? Result.ok(images.first)
: buildErrorResult(
type: ErrorType.notFound,
message: 'Image $id not found in gallery $galleryName',
))
.execErrorCastAsync();
return result;
}