2023-10-22 08:05:10 -07:00
|
|
|
import 'dart:math' as math;
|
2024-01-04 22:20:55 -07:00
|
|
|
import 'package:auto_route/auto_route.dart';
|
2023-11-24 14:29:49 -07:00
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
2023-10-22 08:05:10 -07:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
2023-11-09 09:19:53 -07:00
|
|
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
2023-10-22 08:05:10 -07:00
|
|
|
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
|
|
|
|
import 'package:immich_mobile/modules/shared_link/models/shared_link.dart';
|
|
|
|
import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart';
|
|
|
|
import 'package:immich_mobile/routing/router.dart';
|
2024-01-03 19:54:48 -07:00
|
|
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
2023-10-22 08:05:10 -07:00
|
|
|
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
|
|
|
|
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
|
|
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
|
|
|
import 'package:immich_mobile/utils/url_helper.dart';
|
|
|
|
|
|
|
|
class SharedLinkItem extends ConsumerWidget {
|
|
|
|
final SharedLink sharedLink;
|
|
|
|
|
|
|
|
const SharedLinkItem(this.sharedLink, {super.key});
|
|
|
|
|
|
|
|
bool isExpired() {
|
|
|
|
if (sharedLink.expiresAt != null) {
|
|
|
|
return DateTime.now().isAfter(sharedLink.expiresAt!);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget getExpiryDuration(bool isDarkMode) {
|
2023-11-24 14:29:49 -07:00
|
|
|
var expiresText = "shared_link_expires_never".tr();
|
2023-10-22 08:05:10 -07:00
|
|
|
if (sharedLink.expiresAt != null) {
|
|
|
|
if (isExpired()) {
|
|
|
|
return Text(
|
2023-11-24 14:29:49 -07:00
|
|
|
"shared_link_expired",
|
2023-10-22 08:05:10 -07:00
|
|
|
style: TextStyle(color: Colors.red[300]),
|
2023-11-24 14:29:49 -07:00
|
|
|
).tr();
|
2023-10-22 08:05:10 -07:00
|
|
|
}
|
|
|
|
final difference = sharedLink.expiresAt!.difference(DateTime.now());
|
|
|
|
debugPrint("Difference: $difference");
|
|
|
|
if (difference.inDays > 0) {
|
|
|
|
var dayDifference = difference.inDays;
|
|
|
|
if (difference.inHours % 24 > 12) {
|
|
|
|
dayDifference += 1;
|
|
|
|
}
|
2023-12-24 09:20:34 -07:00
|
|
|
expiresText =
|
|
|
|
"shared_link_expires_days".tr(args: [dayDifference.toString()]);
|
2023-10-22 08:05:10 -07:00
|
|
|
} else if (difference.inHours > 0) {
|
2023-12-24 09:20:34 -07:00
|
|
|
expiresText = "shared_link_expires_hours"
|
|
|
|
.tr(args: [difference.inHours.toString()]);
|
2023-10-22 08:05:10 -07:00
|
|
|
} else if (difference.inMinutes > 0) {
|
2023-12-24 09:20:34 -07:00
|
|
|
expiresText = "shared_link_expires_minutes"
|
|
|
|
.tr(args: [difference.inMinutes.toString()]);
|
2023-10-22 08:05:10 -07:00
|
|
|
} else if (difference.inSeconds > 0) {
|
2023-12-24 09:20:34 -07:00
|
|
|
expiresText = "shared_link_expires_seconds"
|
|
|
|
.tr(args: [difference.inSeconds.toString()]);
|
2023-10-22 08:05:10 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return Text(
|
|
|
|
expiresText,
|
|
|
|
style: TextStyle(color: isDarkMode ? Colors.grey[400] : Colors.grey[600]),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2023-11-09 09:19:53 -07:00
|
|
|
final themeData = context.themeData;
|
2023-10-22 08:05:10 -07:00
|
|
|
final isDarkMode = themeData.brightness == Brightness.dark;
|
|
|
|
final thumbnailUrl = sharedLink.thumbAssetId != null
|
|
|
|
? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!)
|
|
|
|
: null;
|
2023-11-09 09:19:53 -07:00
|
|
|
final imageSize = math.min(context.width / 4, 100.0);
|
2023-10-22 08:05:10 -07:00
|
|
|
|
|
|
|
void copyShareLinkToClipboard() {
|
2024-01-03 19:54:48 -07:00
|
|
|
final externalDomain = ref.read(
|
|
|
|
serverInfoProvider.select((s) => s.serverConfig.externalDomain),
|
|
|
|
);
|
|
|
|
final serverUrl =
|
|
|
|
externalDomain.isNotEmpty ? externalDomain : getServerUrl();
|
2023-10-22 08:05:10 -07:00
|
|
|
if (serverUrl == null) {
|
|
|
|
ImmichToast.show(
|
|
|
|
context: context,
|
|
|
|
gravity: ToastGravity.BOTTOM,
|
|
|
|
toastType: ToastType.error,
|
2023-11-24 14:29:49 -07:00
|
|
|
msg: "shared_link_error_server_url_fetch".tr(),
|
2023-10-22 08:05:10 -07:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Clipboard.setData(
|
|
|
|
ClipboardData(
|
|
|
|
text: "$serverUrl/share/${sharedLink.key}",
|
|
|
|
),
|
|
|
|
).then((_) {
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
2023-11-24 14:29:49 -07:00
|
|
|
SnackBar(
|
2023-11-28 21:17:29 -07:00
|
|
|
content: Text(
|
|
|
|
"shared_link_clipboard_copied_massage",
|
|
|
|
style: context.textTheme.bodyLarge?.copyWith(
|
|
|
|
color: context.primaryColor,
|
|
|
|
),
|
|
|
|
).tr(),
|
2023-11-24 14:29:49 -07:00
|
|
|
duration: const Duration(seconds: 2),
|
2023-10-22 08:05:10 -07:00
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> deleteShareLink() async {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (BuildContext context) {
|
|
|
|
return ConfirmDialog(
|
|
|
|
title: "delete_shared_link_dialog_title",
|
|
|
|
content: "delete_shared_link_dialog_content",
|
|
|
|
onOk: () => ref
|
|
|
|
.read(sharedLinksStateProvider.notifier)
|
|
|
|
.deleteLink(sharedLink.id),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget buildThumbnail() {
|
|
|
|
if (thumbnailUrl == null) {
|
|
|
|
return Container(
|
|
|
|
height: imageSize * 1.2,
|
|
|
|
width: imageSize,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
|
|
|
|
),
|
|
|
|
child: Center(
|
|
|
|
child: Icon(
|
|
|
|
Icons.image_not_supported_outlined,
|
|
|
|
color: isDarkMode ? Colors.grey[100] : Colors.grey[700],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return SizedBox(
|
|
|
|
height: imageSize * 1.2,
|
|
|
|
width: imageSize,
|
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.only(right: 4.0),
|
|
|
|
child: ThumbnailWithInfo(
|
|
|
|
imageUrl: thumbnailUrl,
|
|
|
|
key: key,
|
|
|
|
textInfo: '',
|
|
|
|
noImageIcon: Icons.image_not_supported_outlined,
|
|
|
|
onTap: () {},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget buildInfoChip(String labelText) {
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.only(right: 10),
|
|
|
|
child: Chip(
|
|
|
|
backgroundColor: themeData.primaryColor,
|
|
|
|
label: Text(
|
|
|
|
labelText,
|
|
|
|
style: TextStyle(
|
2023-11-20 07:58:03 -07:00
|
|
|
fontSize: 11,
|
|
|
|
fontWeight: FontWeight.w500,
|
2023-10-22 08:05:10 -07:00
|
|
|
color: isDarkMode ? Colors.black : Colors.white,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
shape: const RoundedRectangleBorder(
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(25)),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget buildBottomInfo() {
|
|
|
|
return Row(
|
|
|
|
children: [
|
2023-11-28 21:17:29 -07:00
|
|
|
if (sharedLink.allowUpload)
|
|
|
|
buildInfoChip("shared_link_info_chip_upload".tr()),
|
|
|
|
if (sharedLink.allowDownload)
|
|
|
|
buildInfoChip("shared_link_info_chip_download".tr()),
|
|
|
|
if (sharedLink.showMetadata)
|
|
|
|
buildInfoChip("shared_link_info_chip_metadata".tr()),
|
2023-10-22 08:05:10 -07:00
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget buildSharedLinkActions() {
|
|
|
|
const actionIconSize = 20.0;
|
|
|
|
return Row(
|
|
|
|
children: [
|
|
|
|
IconButton(
|
|
|
|
splashRadius: 25,
|
|
|
|
constraints: const BoxConstraints(),
|
|
|
|
iconSize: actionIconSize,
|
|
|
|
icon: const Icon(Icons.delete_outline),
|
|
|
|
style: const ButtonStyle(
|
|
|
|
tapTargetSize:
|
|
|
|
MaterialTapTargetSize.shrinkWrap, // the '2023' part
|
|
|
|
),
|
|
|
|
onPressed: deleteShareLink,
|
|
|
|
),
|
|
|
|
IconButton(
|
|
|
|
splashRadius: 25,
|
|
|
|
constraints: const BoxConstraints(),
|
|
|
|
iconSize: actionIconSize,
|
|
|
|
icon: const Icon(Icons.edit_outlined),
|
|
|
|
style: const ButtonStyle(
|
|
|
|
tapTargetSize:
|
|
|
|
MaterialTapTargetSize.shrinkWrap, // the '2023' part
|
|
|
|
),
|
2024-01-04 22:20:55 -07:00
|
|
|
onPressed: () => context
|
|
|
|
.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)),
|
2023-10-22 08:05:10 -07:00
|
|
|
),
|
|
|
|
IconButton(
|
|
|
|
splashRadius: 25,
|
|
|
|
constraints: const BoxConstraints(),
|
|
|
|
iconSize: actionIconSize,
|
|
|
|
icon: const Icon(Icons.copy_outlined),
|
|
|
|
style: const ButtonStyle(
|
|
|
|
tapTargetSize:
|
|
|
|
MaterialTapTargetSize.shrinkWrap, // the '2023' part
|
|
|
|
),
|
|
|
|
onPressed: copyShareLinkToClipboard,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget buildSharedLinkDetails() {
|
|
|
|
return Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
children: [
|
|
|
|
getExpiryDuration(isDarkMode),
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(top: 5),
|
|
|
|
child: Tooltip(
|
|
|
|
verticalOffset: 0,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: themeData.primaryColor.withOpacity(0.9),
|
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
|
),
|
|
|
|
textStyle: TextStyle(
|
|
|
|
color: isDarkMode ? Colors.black : Colors.white,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
),
|
|
|
|
message: sharedLink.title,
|
|
|
|
preferBelow: false,
|
|
|
|
triggerMode: TooltipTriggerMode.tap,
|
|
|
|
child: Text(
|
|
|
|
sharedLink.title,
|
|
|
|
style: TextStyle(
|
|
|
|
color: themeData.primaryColor,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Row(
|
|
|
|
mainAxisSize: MainAxisSize.max,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: [
|
|
|
|
Expanded(
|
|
|
|
child: Tooltip(
|
|
|
|
verticalOffset: 0,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: themeData.primaryColor.withOpacity(0.9),
|
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
|
),
|
|
|
|
textStyle: TextStyle(
|
|
|
|
color: isDarkMode ? Colors.black : Colors.white,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
),
|
|
|
|
message: sharedLink.description ?? "",
|
|
|
|
preferBelow: false,
|
|
|
|
triggerMode: TooltipTriggerMode.tap,
|
|
|
|
child: Text(
|
|
|
|
sharedLink.description ?? "",
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(right: 15),
|
|
|
|
child: buildSharedLinkActions(),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
buildBottomInfo(),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Row(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(left: 15),
|
|
|
|
child: buildThumbnail(),
|
|
|
|
),
|
|
|
|
Expanded(
|
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.only(left: 15),
|
|
|
|
child: buildSharedLinkDetails(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
const Padding(
|
|
|
|
padding: EdgeInsets.all(20),
|
|
|
|
child: Divider(
|
|
|
|
height: 0,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|