relatica/lib/screens/gallery_screen.dart

258 wiersze
8.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:result_monad/result_monad.dart';
import '../controls/login_aware_cached_network_image.dart';
import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/visibility.dart';
import '../serializers/friendica/image_entry_friendica_extensions.dart';
import '../services/gallery_service.dart';
import '../services/network_status_service.dart';
import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart';
import 'media_viewer_screen.dart';
class GalleryScreen extends StatelessWidget {
static final _logger = Logger('$GalleryScreen');
final String galleryName;
const GalleryScreen({super.key, required this.galleryName});
@override
Widget build(BuildContext context) {
_logger.finest('Building $galleryName');
final nss = getIt<NetworkStatusService>();
return Scaffold(
appBar: StandardAppBar.build(context, galleryName, actions: [
StatusAndRefreshButton(
valueListenable: nss.imageGalleryLoadingStatus,
refreshFunction: () async => context
.read<ActiveProfileSelector<GalleryService>>()
.activeEntry
.withResultAsync(
(gs) async => gs.updateGalleryImageList(
galleryName: galleryName,
withNextPage: false,
nextPageOnly: false,
),
),
busyColor: Theme.of(context).appBarTheme.foregroundColor,
),
]),
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) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Error getting images for gallery: $error'),
],
),
);
}
Widget buildBody(
BuildContext context,
GalleryService service,
int expectedPhotoCount,
) {
final imageResult = service.getGalleryImageList(galleryName);
if (imageResult.isFailure) {
return buildErrorBody(imageResult.error.message);
}
final images = imageResult.value;
final attachments = images.map((i) => i.toMediaAttachment()).toList();
if (images.isEmpty && service.loaded) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('No images'),
],
),
);
}
if (images.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Loading 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) {
service.updateGalleryImageList(
galleryName: galleryName,
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
.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)
Positioned(
bottom: 5.0,
left: 5.0,
child: ElevatedButton(
onPressed: () async => await showImageCaption(
context,
image.description,
),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.7)),
child: const Text('ALT'),
),
),
Positioned(
bottom: 5.0,
right: 5.0,
child: Card(
color: Theme.of(context)
.scaffoldBackgroundColor
.withOpacity(0.7),
child: Icon(
image.visibility.type == VisibilityType.public
? Icons.public
: Icons.lock),
),
),
],
),
),
),
);
});
}
Future<void> showImageCaption(BuildContext context, String text) async {
await showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
content: Text(
text,
softWrap: true,
),
actions: <Widget>[
ElevatedButton(
child: const Text('Dismiss'),
onPressed: () {
Navigator.pop(context, true); // showDialog() returns true
},
),
],
);
},
);
}
}