2022-12-14 02:06:10 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2023-05-02 23:27:36 +00:00
|
|
|
import 'package:go_router/go_router.dart';
|
2022-12-14 02:06:10 +00:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
2023-05-02 23:27:36 +00:00
|
|
|
import 'package:result_monad/result_monad.dart';
|
2022-12-14 02:06:10 +00:00
|
|
|
|
2023-03-20 14:06:44 +00:00
|
|
|
import '../controls/login_aware_cached_network_image.dart';
|
2023-01-19 17:50:11 +00:00
|
|
|
import '../controls/standard_appbar.dart';
|
2023-01-30 23:14:25 +00:00
|
|
|
import '../controls/status_and_refresh_button.dart';
|
2023-01-29 21:46:22 +00:00
|
|
|
import '../globals.dart';
|
2023-03-20 18:30:51 +00:00
|
|
|
import '../models/visibility.dart';
|
2022-12-14 02:06:10 +00:00
|
|
|
import '../serializers/friendica/image_entry_friendica_extensions.dart';
|
|
|
|
import '../services/gallery_service.dart';
|
2023-01-29 21:46:22 +00:00
|
|
|
import '../services/network_status_service.dart';
|
2023-03-14 03:47:40 +00:00
|
|
|
import '../utils/active_profile_selector.dart';
|
2023-05-02 23:27:36 +00:00
|
|
|
import '../utils/snackbar_builder.dart';
|
2023-04-22 12:45:06 +00:00
|
|
|
import 'media_viewer_screen.dart';
|
2022-12-14 02:06:10 +00:00
|
|
|
|
|
|
|
class GalleryScreen extends StatelessWidget {
|
|
|
|
static final _logger = Logger('$GalleryScreen');
|
|
|
|
final String galleryName;
|
|
|
|
|
|
|
|
const GalleryScreen({super.key, required this.galleryName});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-05-02 23:27:36 +00:00
|
|
|
_logger.finest('Building $galleryName');
|
2023-01-29 21:46:22 +00:00
|
|
|
final nss = getIt<NetworkStatusService>();
|
2022-12-14 02:06:10 +00:00
|
|
|
return Scaffold(
|
2023-01-29 21:46:22 +00:00
|
|
|
appBar: StandardAppBar.build(context, galleryName, actions: [
|
2023-01-30 23:14:25 +00:00
|
|
|
StatusAndRefreshButton(
|
|
|
|
valueListenable: nss.imageGalleryLoadingStatus,
|
2023-05-02 23:27:36 +00:00
|
|
|
refreshFunction: () async => context
|
|
|
|
.read<ActiveProfileSelector<GalleryService>>()
|
|
|
|
.activeEntry
|
|
|
|
.withResultAsync(
|
|
|
|
(gs) async => gs.updateGalleryImageList(
|
|
|
|
galleryName: galleryName,
|
|
|
|
withNextPage: false,
|
|
|
|
nextPageOnly: false,
|
|
|
|
),
|
|
|
|
),
|
2023-01-30 23:14:25 +00:00
|
|
|
busyColor: Theme.of(context).appBarTheme.foregroundColor,
|
|
|
|
),
|
2023-01-29 21:46:22 +00:00
|
|
|
]),
|
2023-05-02 23:27:36 +00:00
|
|
|
body: _GalleryScreenBody(
|
|
|
|
galleryName: galleryName,
|
|
|
|
),
|
2022-12-14 02:06:10 +00:00
|
|
|
);
|
|
|
|
}
|
2023-05-02 23:27:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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),
|
|
|
|
);
|
|
|
|
}
|
2022-12-14 02:06:10 +00:00
|
|
|
|
2023-01-29 21:46:22 +00:00
|
|
|
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,
|
|
|
|
) {
|
2022-12-14 02:06:10 +00:00
|
|
|
final imageResult = service.getGalleryImageList(galleryName);
|
|
|
|
if (imageResult.isFailure) {
|
2023-01-29 21:46:22 +00:00
|
|
|
return buildErrorBody(imageResult.error.message);
|
2022-12-14 02:06:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
final images = imageResult.value;
|
2023-01-29 22:21:47 +00:00
|
|
|
final attachments = images.map((i) => i.toMediaAttachment()).toList();
|
2022-12-14 02:06:10 +00:00
|
|
|
if (images.isEmpty && service.loaded) {
|
2023-01-29 21:46:22 +00:00
|
|
|
return Center(
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
children: const [
|
|
|
|
Text('No images'),
|
|
|
|
],
|
2022-12-14 02:06:10 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (images.isEmpty) {
|
|
|
|
return Center(
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
children: const [
|
|
|
|
Text('Loading images'),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-14 02:14:42 +00:00
|
|
|
return GridView.builder(
|
|
|
|
itemCount: images.length,
|
2023-01-29 21:46:22 +00:00
|
|
|
padding: const EdgeInsets.all(5.0),
|
2022-12-14 02:14:42 +00:00
|
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
|
|
maxCrossAxisExtent: thumbnailDimension),
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
final image = images[index];
|
2023-01-29 21:46:22 +00:00
|
|
|
if (images.length < expectedPhotoCount &&
|
|
|
|
index == images.length - 1) {
|
|
|
|
service.updateGalleryImageList(
|
|
|
|
galleryName: galleryName,
|
|
|
|
withNextPage: true,
|
|
|
|
nextPageOnly: true,
|
|
|
|
);
|
|
|
|
}
|
2022-12-14 02:14:42 +00:00
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(2.0),
|
2022-12-29 05:24:44 +00:00
|
|
|
child: GestureDetector(
|
2023-01-29 21:46:22 +00:00
|
|
|
onTap: () {
|
2022-12-14 02:14:42 +00:00
|
|
|
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
2023-04-22 12:45:06 +00:00
|
|
|
return MediaViewerScreen(
|
2023-01-29 22:21:47 +00:00
|
|
|
attachments: attachments,
|
|
|
|
initialIndex: index,
|
|
|
|
);
|
2022-12-14 02:14:42 +00:00
|
|
|
}));
|
|
|
|
},
|
2023-03-20 18:30:51 +00:00
|
|
|
child: Card(
|
|
|
|
child: Stack(
|
|
|
|
children: [
|
|
|
|
LoginAwareCachedNetworkImage(
|
|
|
|
width: thumbnailDimension,
|
|
|
|
height: thumbnailDimension,
|
|
|
|
imageUrl: image.thumbnailUrl,
|
|
|
|
),
|
2023-05-02 23:27:36 +00:00
|
|
|
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),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
2023-04-30 00:42:34 +00:00
|
|
|
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'),
|
|
|
|
),
|
|
|
|
),
|
2023-03-20 18:30:51 +00:00
|
|
|
Positioned(
|
|
|
|
bottom: 5.0,
|
|
|
|
right: 5.0,
|
2023-04-30 00:42:34 +00:00
|
|
|
child: Card(
|
|
|
|
color: Theme.of(context)
|
|
|
|
.scaffoldBackgroundColor
|
|
|
|
.withOpacity(0.7),
|
|
|
|
child: Icon(
|
|
|
|
image.visibility.type == VisibilityType.public
|
|
|
|
? Icons.public
|
|
|
|
: Icons.lock),
|
|
|
|
),
|
2023-03-20 18:30:51 +00:00
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
2022-12-14 02:14:42 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
2022-12-14 02:06:10 +00:00
|
|
|
}
|
2023-04-30 00:42:34 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2022-12-14 02:06:10 +00:00
|
|
|
}
|