relatica/lib/screens/gallery_screen.dart

247 wiersze
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import '../controls/async_value_widget.dart';
import '../controls/login_aware_cached_network_image.dart';
import '../controls/padding.dart';
import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/visibility.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/gallery_services.dart';
import '../serializers/friendica/image_entry_friendica_extensions.dart';
import '../utils/snackbar_builder.dart';
import 'media_viewer_screen.dart';
class GalleryScreen extends ConsumerWidget {
static final _logger = Logger('$GalleryScreen');
final String galleryName;
const GalleryScreen({super.key, required this.galleryName});
@override
Widget build(BuildContext context, WidgetRef ref) {
_logger.finer('Building $galleryName');
final profile = ref.watch(activeProfileProvider);
final loading = switch (ref.watch(galleryProvider(profile, galleryName))) {
AsyncData() => false,
_ => true,
};
return Scaffold(
appBar: StandardAppBar.build(context, galleryName, actions: [
StatusAndRefreshButton(
executing: loading,
refreshFunction: () async => ref
.read(galleryImagesProvider(profile, galleryName).notifier)
.updateGalleryImages(
withNextPage: false,
nextPageOnly: false,
),
busyColor: Theme.of(context).appBarTheme.foregroundColor,
),
]),
body: _GalleryScreenBody(
profile: profile,
galleryName: galleryName,
),
);
}
}
class _GalleryScreenBody extends ConsumerWidget {
static const thumbnailDimension = 350.0;
static final _logger = Logger('$_GalleryScreenBody');
final Profile profile;
final String galleryName;
const _GalleryScreenBody({required this.profile, required this.galleryName});
@override
Widget build(BuildContext context, WidgetRef ref) {
_logger.finer('Building');
return AsyncValueWidget(ref.watch(galleryProvider(profile, galleryName)),
valueBuilder: (context, ref, galleriesResult) {
return galleriesResult.fold(
onSuccess: (gallery) => buildBody(context, ref, gallery.count),
onError: (error) => buildErrorBody(error.message),
);
});
}
Widget buildErrorBody(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Error getting images for gallery: $error'),
],
),
);
}
Widget buildBody(
BuildContext context,
WidgetRef ref,
int expectedPhotoCount,
) {
return AsyncValueWidget(
ref.watch(galleryImagesProvider(profile, galleryName)),
loadingBuilder: (_, __) => const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Loading images'),
VerticalPadding(),
CircularProgressIndicator(),
],
),
),
valueBuilder: (context, ref, galleryImagesResult) {
return galleryImagesResult.fold(
onSuccess: (images) {
final attachments =
images.map((i) => i.toMediaAttachment()).toList();
if (images.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('No images'),
],
),
);
}
return GridView.builder(
itemCount: images.length,
padding: const EdgeInsets.all(5.0),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: thumbnailDimension),
itemBuilder: (context, index) {
final image = images[index];
if (images.length < expectedPhotoCount &&
index == images.length - 1) {
ref
.read(galleryImagesProvider(profile, galleryName)
.notifier)
.updateGalleryImages(
withNextPage: true,
nextPageOnly: true,
);
}
return Padding(
padding: const EdgeInsets.all(2.0),
child: GestureDetector(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return MediaViewerScreen(
attachments: attachments,
initialIndex: index,
);
}));
},
child: Card(
child: Stack(
children: [
LoginAwareCachedNetworkImage(
width: thumbnailDimension,
height: thumbnailDimension,
imageUrl: image.thumbnailUrl,
),
Positioned(
top: 5.0,
right: 5.0,
child: Row(
children: [
Card(
color: Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.7),
child: IconButton(
onPressed: () => context.push(
'/gallery/edit/$galleryName/image/${image.id}',
),
icon: const Icon(Icons.edit),
),
),
Card(
color: Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.7),
child: IconButton(
onPressed: () async {
final confirm = await showYesNoDialog(
context, 'Delete image?');
if (confirm != true) {
return;
}
await ref
.read(galleryImagesProvider(
profile, galleryName)
.notifier)
.deleteImage(image)
.withError((error) {
if (context.mounted) {
buildSnackbar(context,
'Error deleting image: $error');
}
});
},
icon: const Icon(Icons.delete),
),
),
],
),
),
if (image.description.isNotEmpty)
Positioned(
bottom: 5.0,
left: 5.0,
child: ElevatedButton(
onPressed: () async => await showInfoDialog(
context,
image.description,
),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.7)),
child: const Text('ALT'),
),
),
Positioned(
bottom: 5.0,
right: 5.0,
child: Card(
color: Theme.of(context)
.scaffoldBackgroundColor
.withValues(alpha: 0.7),
child: Icon(image.visibility.type ==
VisibilityType.public
? Icons.public
: Icons.lock),
),
),
],
),
),
),
);
});
},
onError: (error) => buildErrorBody(error.message),
);
});
}
}