nc-photos/app/lib/widget/photo_list_item.dart

422 lines
11 KiB
Dart
Raw Normal View History

2022-12-08 16:39:13 +01:00
import 'package:flutter/foundation.dart';
2021-05-06 13:29:20 +02:00
import 'package:flutter/material.dart';
2022-06-06 19:37:46 +02:00
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
2021-10-04 15:53:03 +02:00
import 'package:intl/intl.dart';
2021-05-06 13:29:20 +02:00
import 'package:nc_photos/account.dart';
2021-08-10 12:10:22 +02:00
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
2022-06-06 19:37:46 +02:00
import 'package:nc_photos/entity/local_file.dart';
2022-12-18 12:52:57 +01:00
import 'package:nc_photos/flutter_util.dart' as flutter_util;
2022-06-06 19:37:46 +02:00
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
2021-05-06 13:29:20 +02:00
import 'package:nc_photos/theme.dart';
2022-12-18 07:20:51 +01:00
import 'package:nc_photos/widget/network_thumbnail.dart';
2022-06-06 19:37:46 +02:00
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
2022-12-08 16:39:13 +01:00
import 'package:to_string/to_string.dart';
2022-06-06 19:37:46 +02:00
2022-12-08 16:39:13 +01:00
part 'photo_list_item.g.dart';
@toString
2022-06-06 19:37:46 +02:00
abstract class PhotoListFileItem extends SelectableItem {
const PhotoListFileItem({
required this.fileIndex,
required this.file,
required this.shouldShowFavoriteBadge,
});
@override
get isTappable => true;
@override
get isSelectable => true;
@override
operator ==(Object other) =>
other is PhotoListFileItem && file.compareServerIdentity(other.file);
@override
get hashCode => file.fdPath.hashCode;
2022-06-06 19:37:46 +02:00
@override
2022-12-08 16:39:13 +01:00
String toString() => _$toString();
2022-06-06 19:37:46 +02:00
final int fileIndex;
final FileDescriptor file;
2022-06-06 19:37:46 +02:00
final bool shouldShowFavoriteBadge;
}
class PhotoListImageItem extends PhotoListFileItem {
const PhotoListImageItem({
required int fileIndex,
required FileDescriptor file,
2022-06-06 19:37:46 +02:00
required this.account,
required this.previewUrl,
required bool shouldShowFavoriteBadge,
}) : super(
fileIndex: fileIndex,
file: file,
shouldShowFavoriteBadge: shouldShowFavoriteBadge,
);
@override
2022-12-18 07:20:51 +01:00
Widget buildWidget(BuildContext context) => PhotoListImage(
2022-06-06 19:37:46 +02:00
account: account,
previewUrl: previewUrl,
isGif: file.fdMime == "image/gif",
isFavorite: shouldShowFavoriteBadge && file.fdIsFavorite == true,
2022-12-18 12:52:57 +01:00
heroKey: flutter_util.getImageHeroTag(file),
2022-06-06 19:37:46 +02:00
);
final Account account;
final String previewUrl;
}
class PhotoListVideoItem extends PhotoListFileItem {
const PhotoListVideoItem({
required int fileIndex,
required FileDescriptor file,
2022-06-06 19:37:46 +02:00
required this.account,
required this.previewUrl,
required bool shouldShowFavoriteBadge,
}) : super(
fileIndex: fileIndex,
file: file,
shouldShowFavoriteBadge: shouldShowFavoriteBadge,
);
@override
buildWidget(BuildContext context) => PhotoListVideo(
account: account,
previewUrl: previewUrl,
isFavorite: shouldShowFavoriteBadge && file.fdIsFavorite == true,
2022-06-06 19:37:46 +02:00
);
final Account account;
final String previewUrl;
}
class PhotoListDateItem extends SelectableItem {
const PhotoListDateItem({
required this.date,
this.isMonthOnly = false,
});
@override
get isTappable => false;
@override
get isSelectable => false;
@override
get staggeredTile => const StaggeredTile.extent(99, 32);
@override
buildWidget(BuildContext context) => PhotoListDate(
date: date,
isMonthOnly: isMonthOnly,
);
final DateTime date;
final bool isMonthOnly;
}
abstract class PhotoListLocalFileItem extends SelectableItem {
const PhotoListLocalFileItem({
required this.fileIndex,
required this.file,
});
@override
get isTappable => true;
@override
get isSelectable => true;
@override
operator ==(Object other) =>
other is PhotoListLocalFileItem && file.compareIdentity(other.file);
@override
get hashCode => file.identityHashCode;
final int fileIndex;
final LocalFile file;
}
class PhotoListLocalImageItem extends PhotoListLocalFileItem {
const PhotoListLocalImageItem({
required int fileIndex,
required LocalFile file,
}) : super(
fileIndex: fileIndex,
file: file,
);
@override
buildWidget(BuildContext context) {
final ImageProvider provider;
if (file is LocalUriFile) {
provider = ContentUriImage((file as LocalUriFile).uri);
} else {
throw ArgumentError("Invalid file");
}
return Padding(
padding: const EdgeInsets.all(2),
child: FittedBox(
clipBehavior: Clip.hardEdge,
fit: BoxFit.cover,
child: Stack(
children: [
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
2022-11-12 10:55:33 +01:00
color: Theme.of(context).listPlaceholderBackgroundColor,
child: Image(
image: ResizeImage.resizeIfNeeded(
k.photoThumbSize, null, provider),
filterQuality: FilterQuality.high,
fit: BoxFit.cover,
errorBuilder: (context, e, stackTrace) {
return Center(
child: Icon(
Icons.image_not_supported,
size: 64,
2022-11-12 10:55:33 +01:00
color: Theme.of(context).listPlaceholderForegroundColor,
),
);
},
),
),
Container(
// arbitrary size here
constraints: BoxConstraints.tight(const Size(128, 128)),
alignment: AlignmentDirectional.bottomEnd,
padding: const EdgeInsets.all(8),
child: const Icon(
Icons.cloud_off,
size: 20,
color: Colors.white,
),
),
],
2022-06-06 19:37:46 +02:00
),
),
);
}
}
2021-05-06 13:29:20 +02:00
class PhotoListImage extends StatelessWidget {
const PhotoListImage({
2021-07-23 22:05:57 +02:00
Key? key,
required this.account,
required this.previewUrl,
2022-01-15 11:35:15 +01:00
this.padding = const EdgeInsets.all(2),
2021-06-22 07:24:37 +02:00
this.isGif = false,
2022-01-25 11:15:17 +01:00
this.isFavorite = false,
2022-09-07 11:37:50 +02:00
this.heroKey,
2021-05-06 13:29:20 +02:00
}) : super(key: key);
@override
2022-12-18 07:20:51 +01:00
Widget build(BuildContext context) {
Widget buildPlaceholder() => Padding(
padding: const EdgeInsets.all(12),
2022-09-07 11:37:50 +02:00
child: Icon(
Icons.image_not_supported,
2022-11-12 10:55:33 +01:00
color: Theme.of(context).listPlaceholderForegroundColor,
2022-09-07 11:37:50 +02:00
),
);
Widget child;
if (previewUrl == null) {
2022-12-18 07:20:51 +01:00
child = FittedBox(
child: buildPlaceholder(),
);
2022-09-07 11:37:50 +02:00
} else {
2022-12-18 07:20:51 +01:00
child = NetworkRectThumbnail(
account: account,
2022-09-07 11:37:50 +02:00
imageUrl: previewUrl!,
2022-12-18 07:20:51 +01:00
errorBuilder: (_) => buildPlaceholder(),
2022-09-07 11:37:50 +02:00
);
if (heroKey != null) {
child = Hero(
tag: heroKey!,
child: child,
);
}
}
2022-12-18 07:20:51 +01:00
return IconTheme(
data: const IconThemeData(color: Colors.white),
child: Padding(
padding: padding,
2021-08-22 20:05:31 +02:00
child: Stack(
children: [
2021-06-22 07:24:37 +02:00
Container(
2022-11-12 10:55:33 +01:00
color: Theme.of(context).listPlaceholderBackgroundColor,
2022-09-07 11:37:50 +02:00
child: child,
2021-06-22 07:24:37 +02:00
),
2021-08-22 20:05:31 +02:00
if (isGif)
Container(
alignment: AlignmentDirectional.topEnd,
padding: const EdgeInsets.symmetric(horizontal: 2),
2022-12-18 07:20:51 +01:00
child: const Icon(Icons.gif, size: 26),
2021-08-22 20:05:31 +02:00
),
2022-01-25 11:15:17 +01:00
if (isFavorite)
Container(
alignment: AlignmentDirectional.bottomStart,
2022-12-18 07:20:51 +01:00
padding: const EdgeInsets.all(6),
child: const Icon(Icons.star, size: 15),
2022-01-25 11:15:17 +01:00
),
2021-08-22 20:05:31 +02:00
],
),
2021-05-06 13:29:20 +02:00
),
);
}
final Account account;
2022-01-15 11:35:15 +01:00
final String? previewUrl;
2021-06-22 07:24:37 +02:00
final bool isGif;
2022-01-15 11:35:15 +01:00
final EdgeInsetsGeometry padding;
2022-01-25 11:15:17 +01:00
final bool isFavorite;
2022-09-07 11:37:50 +02:00
// if not null, the image will be contained by a Hero widget
final String? heroKey;
2021-05-06 13:29:20 +02:00
}
2021-05-06 13:36:20 +02:00
class PhotoListVideo extends StatelessWidget {
const PhotoListVideo({
2021-07-23 22:05:57 +02:00
Key? key,
required this.account,
required this.previewUrl,
2022-01-25 11:15:17 +01:00
this.isFavorite = false,
2021-05-06 13:36:20 +02:00
}) : super(key: key);
@override
2022-12-18 07:20:51 +01:00
Widget build(BuildContext context) {
return IconTheme(
data: const IconThemeData(color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(2),
2021-08-22 20:05:31 +02:00
child: Stack(
children: [
Container(
2022-11-12 10:55:33 +01:00
color: Theme.of(context).listPlaceholderBackgroundColor,
2022-12-18 07:20:51 +01:00
child: NetworkRectThumbnail(
account: account,
2021-08-22 20:05:31 +02:00
imageUrl: previewUrl,
2022-12-18 07:20:51 +01:00
errorBuilder: (_) => Padding(
padding: const EdgeInsets.all(12),
child: Icon(
Icons.image_not_supported,
color: Theme.of(context).listPlaceholderForegroundColor,
),
),
2021-08-22 20:05:31 +02:00
),
2021-06-22 07:23:28 +02:00
),
2021-08-22 20:05:31 +02:00
Container(
alignment: AlignmentDirectional.topEnd,
2022-12-18 07:20:51 +01:00
padding: const EdgeInsets.all(6),
child: const Icon(Icons.play_circle_outlined, size: 17),
2021-06-22 07:23:28 +02:00
),
2022-01-25 11:15:17 +01:00
if (isFavorite)
Container(
alignment: AlignmentDirectional.bottomStart,
2022-12-18 07:20:51 +01:00
padding: const EdgeInsets.all(6),
child: const Icon(Icons.star, size: 15),
2022-01-25 11:15:17 +01:00
),
2021-08-22 20:05:31 +02:00
],
),
2021-05-06 13:36:20 +02:00
),
);
}
final Account account;
final String previewUrl;
2022-01-25 11:15:17 +01:00
final bool isFavorite;
2021-05-06 13:36:20 +02:00
}
2021-08-10 12:10:22 +02:00
class PhotoListLabel extends StatelessWidget {
const PhotoListLabel({
Key? key,
required this.text,
}) : super(key: key);
@override
build(BuildContext context) {
return Container(
alignment: AlignmentDirectional.centerStart,
padding: const EdgeInsets.symmetric(horizontal: 8),
2021-08-10 12:10:22 +02:00
child: Text(
text,
style: Theme.of(context).textTheme.labelLarge,
2021-08-10 12:10:22 +02:00
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
}
final String text;
}
class PhotoListLabelEdit extends PhotoListLabel {
const PhotoListLabelEdit({
Key? key,
required String text,
required this.onEditPressed,
}) : super(key: key, text: text);
@override
build(BuildContext context) {
return Stack(
children: [
// needed to expand the touch sensitive area to the whole row
Container(
color: Colors.transparent,
),
super.build(context),
PositionedDirectional(
top: 0,
bottom: 0,
end: 0,
child: IconButton(
2021-09-15 08:58:06 +02:00
icon: const Icon(Icons.edit),
tooltip: L10n.global().editTooltip,
2021-08-10 12:10:22 +02:00
onPressed: onEditPressed,
),
),
],
);
}
final VoidCallback? onEditPressed;
}
2021-10-04 15:53:03 +02:00
class PhotoListDate extends StatelessWidget {
const PhotoListDate({
Key? key,
required this.date,
this.isMonthOnly = false,
}) : super(key: key);
@override
build(BuildContext context) {
final pattern =
isMonthOnly ? DateFormat.YEAR_MONTH : DateFormat.YEAR_MONTH_DAY;
final subtitle =
DateFormat(pattern, Localizations.localeOf(context).languageCode)
.format(date.toLocal());
return Align(
alignment: AlignmentDirectional.centerStart,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
subtitle,
2022-11-12 10:55:33 +01:00
style: Theme.of(context).textTheme.labelMedium,
2021-10-04 15:53:03 +02:00
),
),
);
}
final DateTime date;
final bool isMonthOnly;
}