2022-12-14 02:06:10 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-12-05 04:00:58 +00:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.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';
|
2023-05-02 23:27:36 +00:00
|
|
|
import 'package:result_monad/result_monad.dart';
|
2022-12-14 02:06:10 +00:00
|
|
|
|
2024-12-05 04:00:58 +00:00
|
|
|
import '../controls/async_value_widget.dart';
|
2023-03-20 14:06:44 +00:00
|
|
|
import '../controls/login_aware_cached_network_image.dart';
|
2024-12-05 04:00:58 +00:00
|
|
|
import '../controls/padding.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';
|
2024-12-05 04:00:58 +00:00
|
|
|
import '../models/auth/profile.dart';
|
2023-03-20 18:30:51 +00:00
|
|
|
import '../models/visibility.dart';
|
2024-12-10 11:54:45 +00:00
|
|
|
import '../riverpod_controllers/account_services.dart';
|
2024-12-05 04:00:58 +00:00
|
|
|
import '../riverpod_controllers/gallery_services.dart';
|
2022-12-14 02:06:10 +00:00
|
|
|
import '../serializers/friendica/image_entry_friendica_extensions.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
|
|
|
|
2024-12-05 04:00:58 +00:00
|
|
|
class GalleryScreen extends ConsumerWidget {
|
2022-12-14 02:06:10 +00:00
|
|
|
static final _logger = Logger('$GalleryScreen');
|
|
|
|
final String galleryName;
|
|
|
|
|
|
|
|
const GalleryScreen({super.key, required this.galleryName});
|
|
|
|
|
|
|
|
@override
|
2024-12-05 04:00:58 +00:00
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2023-11-27 19:16:11 +00:00
|
|
|
_logger.finer('Building $galleryName');
|
2024-12-10 11:54:45 +00:00
|
|
|
final profile = ref.watch(activeProfileProvider);
|
2024-12-12 15:22:12 +00:00
|
|
|
final loading = switch (ref.watch(galleryProvider(profile, galleryName))) {
|
|
|
|
AsyncData() => false,
|
|
|
|
_ => true,
|
|
|
|
};
|
2022-12-14 02:06:10 +00:00
|
|
|
return Scaffold(
|
2023-01-29 21:46:22 +00:00
|
|
|
appBar: StandardAppBar.build(context, galleryName, actions: [
|
2024-12-20 01:30:14 +00:00
|
|
|
StatusAndRefreshButton(
|
2024-12-12 15:22:12 +00:00
|
|
|
executing: loading,
|
2024-12-05 04:00:58 +00:00
|
|
|
refreshFunction: () async => ref
|
|
|
|
.read(galleryImagesProvider(profile, galleryName).notifier)
|
|
|
|
.updateGalleryImages(
|
|
|
|
withNextPage: false,
|
|
|
|
nextPageOnly: false,
|
2023-05-02 23:27:36 +00:00
|
|
|
),
|
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(
|
2024-12-05 04:00:58 +00:00
|
|
|
profile: profile,
|
2023-05-02 23:27:36 +00:00
|
|
|
galleryName: galleryName,
|
|
|
|
),
|
2022-12-14 02:06:10 +00:00
|
|
|
);
|
|
|
|
}
|
2023-05-02 23:27:36 +00:00
|
|
|
}
|
|
|
|
|
2024-12-05 04:00:58 +00:00
|
|
|
class _GalleryScreenBody extends ConsumerWidget {
|
2023-05-02 23:27:36 +00:00
|
|
|
static const thumbnailDimension = 350.0;
|
|
|
|
static final _logger = Logger('$_GalleryScreenBody');
|
2024-12-05 04:00:58 +00:00
|
|
|
final Profile profile;
|
2023-05-02 23:27:36 +00:00
|
|
|
final String galleryName;
|
|
|
|
|
2024-12-05 04:00:58 +00:00
|
|
|
const _GalleryScreenBody({required this.profile, required this.galleryName});
|
2023-05-02 23:27:36 +00:00
|
|
|
|
|
|
|
@override
|
2024-12-05 04:00:58 +00:00
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2023-11-27 19:16:11 +00:00
|
|
|
_logger.finer('Building');
|
2024-12-05 04:00:58 +00:00
|
|
|
|
|
|
|
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),
|
|
|
|
);
|
|
|
|
});
|
2023-05-02 23:27:36 +00:00
|
|
|
}
|
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,
|
2024-12-05 04:00:58 +00:00
|
|
|
WidgetRef ref,
|
2023-01-29 21:46:22 +00:00
|
|
|
int expectedPhotoCount,
|
|
|
|
) {
|
2024-12-05 04:00:58 +00:00
|
|
|
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'),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-12-14 02:06:10 +00:00
|
|
|
|
2024-12-05 04:00:58 +00:00
|
|
|
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,
|
2023-05-02 23:27:36 +00:00
|
|
|
),
|
2024-12-05 04:00:58 +00:00
|
|
|
Positioned(
|
|
|
|
top: 5.0,
|
|
|
|
right: 5.0,
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Card(
|
|
|
|
color: Theme.of(context)
|
|
|
|
.scaffoldBackgroundColor
|
2024-12-17 00:46:38 +00:00
|
|
|
.withValues(alpha: 0.7),
|
2024-12-05 04:00:58 +00:00
|
|
|
child: IconButton(
|
|
|
|
onPressed: () => context.push(
|
|
|
|
'/gallery/edit/$galleryName/image/${image.id}',
|
|
|
|
),
|
|
|
|
icon: const Icon(Icons.edit),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Card(
|
|
|
|
color: Theme.of(context)
|
|
|
|
.scaffoldBackgroundColor
|
2024-12-17 00:46:38 +00:00
|
|
|
.withValues(alpha: 0.7),
|
2024-12-05 04:00:58 +00:00
|
|
|
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(
|
2024-12-20 13:47:49 +00:00
|
|
|
onPressed: () async => await showInfoDialog(
|
2024-12-05 04:00:58 +00:00
|
|
|
context,
|
|
|
|
image.description,
|
|
|
|
),
|
|
|
|
style: ElevatedButton.styleFrom(
|
|
|
|
backgroundColor: Theme.of(context)
|
|
|
|
.scaffoldBackgroundColor
|
2024-12-17 00:46:38 +00:00
|
|
|
.withValues(alpha: 0.7)),
|
2024-12-05 04:00:58 +00:00
|
|
|
child: const Text('ALT'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Positioned(
|
|
|
|
bottom: 5.0,
|
|
|
|
right: 5.0,
|
|
|
|
child: Card(
|
|
|
|
color: Theme.of(context)
|
|
|
|
.scaffoldBackgroundColor
|
2024-12-17 00:46:38 +00:00
|
|
|
.withValues(alpha: 0.7),
|
2024-12-05 04:00:58 +00:00
|
|
|
child: Icon(image.visibility.type ==
|
|
|
|
VisibilityType.public
|
|
|
|
? Icons.public
|
|
|
|
: Icons.lock),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
2023-04-30 00:42:34 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2024-12-05 04:00:58 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
onError: (error) => buildErrorBody(error.message),
|
2022-12-14 02:14:42 +00:00
|
|
|
);
|
|
|
|
});
|
2022-12-14 02:06:10 +00:00
|
|
|
}
|
|
|
|
}
|