mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:39:03 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			539 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			539 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:auto_route/auto_route.dart';
 | |
| import 'package:easy_localization/easy_localization.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter/services.dart';
 | |
| import 'package:flutter_hooks/flutter_hooks.dart';
 | |
| import 'package:fluttertoast/fluttertoast.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | |
| import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
 | |
| import 'package:immich_mobile/providers/shared_link.provider.dart';
 | |
| import 'package:immich_mobile/services/shared_link.service.dart';
 | |
| import 'package:immich_mobile/providers/server_info.provider.dart';
 | |
| import 'package:immich_mobile/widgets/common/immich_toast.dart';
 | |
| import 'package:immich_mobile/utils/url_helper.dart';
 | |
| 
 | |
| @RoutePage()
 | |
| class SharedLinkEditPage extends HookConsumerWidget {
 | |
|   final SharedLink? existingLink;
 | |
|   final List<String>? assetsList;
 | |
|   final String? albumId;
 | |
| 
 | |
|   const SharedLinkEditPage({
 | |
|     super.key,
 | |
|     this.existingLink,
 | |
|     this.assetsList,
 | |
|     this.albumId,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context, WidgetRef ref) {
 | |
|     const padding = 20.0;
 | |
|     final themeData = context.themeData;
 | |
|     final descriptionController =
 | |
|         useTextEditingController(text: existingLink?.description ?? "");
 | |
|     final descriptionFocusNode = useFocusNode();
 | |
|     final passwordController =
 | |
|         useTextEditingController(text: existingLink?.password ?? "");
 | |
|     final showMetadata = useState(existingLink?.showMetadata ?? true);
 | |
|     final allowDownload = useState(existingLink?.allowDownload ?? true);
 | |
|     final allowUpload = useState(existingLink?.allowUpload ?? false);
 | |
|     final editExpiry = useState(false);
 | |
|     final expiryAfter = useState(0);
 | |
|     final newShareLink = useState("");
 | |
| 
 | |
|     Widget buildLinkTitle() {
 | |
|       if (existingLink != null) {
 | |
|         if (existingLink!.type == SharedLinkSource.album) {
 | |
|           return Row(
 | |
|             children: [
 | |
|               const Text(
 | |
|                 'shared_link_public_album',
 | |
|                 style: TextStyle(fontWeight: FontWeight.bold),
 | |
|               ).tr(),
 | |
|               const Text(
 | |
|                 " | ",
 | |
|                 style: TextStyle(fontWeight: FontWeight.bold),
 | |
|               ),
 | |
|               Text(
 | |
|                 existingLink!.title,
 | |
|                 style: TextStyle(
 | |
|                   color: themeData.primaryColor,
 | |
|                   fontWeight: FontWeight.bold,
 | |
|                 ),
 | |
|               ),
 | |
|             ],
 | |
|           );
 | |
|         }
 | |
| 
 | |
|         if (existingLink!.type == SharedLinkSource.individual) {
 | |
|           return Row(
 | |
|             children: [
 | |
|               const Text(
 | |
|                 'shared_link_individual_shared',
 | |
|                 style: TextStyle(fontWeight: FontWeight.bold),
 | |
|               ).tr(),
 | |
|               const Text(
 | |
|                 " | ",
 | |
|                 style: TextStyle(fontWeight: FontWeight.bold),
 | |
|               ),
 | |
|               Expanded(
 | |
|                 child: Text(
 | |
|                   existingLink!.description ?? "--",
 | |
|                   style: TextStyle(
 | |
|                     color: themeData.primaryColor,
 | |
|                     fontWeight: FontWeight.bold,
 | |
|                   ),
 | |
|                   overflow: TextOverflow.ellipsis,
 | |
|                 ),
 | |
|               ),
 | |
|             ],
 | |
|           );
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       return const Text(
 | |
|         "shared_link_create_info",
 | |
|         style: TextStyle(fontWeight: FontWeight.bold),
 | |
|       ).tr();
 | |
|     }
 | |
| 
 | |
|     Widget buildDescriptionField() {
 | |
|       return TextField(
 | |
|         controller: descriptionController,
 | |
|         enabled: newShareLink.value.isEmpty,
 | |
|         focusNode: descriptionFocusNode,
 | |
|         textInputAction: TextInputAction.done,
 | |
|         autofocus: false,
 | |
|         decoration: InputDecoration(
 | |
|           labelText: 'shared_link_edit_description'.tr(),
 | |
|           labelStyle: TextStyle(
 | |
|             fontWeight: FontWeight.bold,
 | |
|             color: themeData.primaryColor,
 | |
|           ),
 | |
|           floatingLabelBehavior: FloatingLabelBehavior.always,
 | |
|           border: const OutlineInputBorder(),
 | |
|           hintText: 'shared_link_edit_description_hint'.tr(),
 | |
|           hintStyle: const TextStyle(
 | |
|             fontWeight: FontWeight.normal,
 | |
|             fontSize: 14,
 | |
|           ),
 | |
|           disabledBorder: OutlineInputBorder(
 | |
|             borderSide: BorderSide(color: Colors.grey.withOpacity(0.5)),
 | |
|           ),
 | |
|         ),
 | |
|         onTapOutside: (_) => descriptionFocusNode.unfocus(),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildPasswordField() {
 | |
|       return TextField(
 | |
|         controller: passwordController,
 | |
|         enabled: newShareLink.value.isEmpty,
 | |
|         autofocus: false,
 | |
|         decoration: InputDecoration(
 | |
|           labelText: 'shared_link_edit_password'.tr(),
 | |
|           labelStyle: TextStyle(
 | |
|             fontWeight: FontWeight.bold,
 | |
|             color: themeData.primaryColor,
 | |
|           ),
 | |
|           floatingLabelBehavior: FloatingLabelBehavior.always,
 | |
|           border: const OutlineInputBorder(),
 | |
|           hintText: 'shared_link_edit_password_hint'.tr(),
 | |
|           hintStyle: const TextStyle(
 | |
|             fontWeight: FontWeight.normal,
 | |
|             fontSize: 14,
 | |
|           ),
 | |
|           disabledBorder: OutlineInputBorder(
 | |
|             borderSide: BorderSide(color: Colors.grey.withOpacity(0.5)),
 | |
|           ),
 | |
|         ),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildShowMetaButton() {
 | |
|       return SwitchListTile.adaptive(
 | |
|         value: showMetadata.value,
 | |
|         onChanged: newShareLink.value.isEmpty
 | |
|             ? (value) => showMetadata.value = value
 | |
|             : null,
 | |
|         activeColor: themeData.primaryColor,
 | |
|         dense: true,
 | |
|         title: Text(
 | |
|           "shared_link_edit_show_meta",
 | |
|           style: themeData.textTheme.labelLarge
 | |
|               ?.copyWith(fontWeight: FontWeight.bold),
 | |
|         ).tr(),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildAllowDownloadButton() {
 | |
|       return SwitchListTile.adaptive(
 | |
|         value: allowDownload.value,
 | |
|         onChanged: newShareLink.value.isEmpty
 | |
|             ? (value) => allowDownload.value = value
 | |
|             : null,
 | |
|         activeColor: themeData.primaryColor,
 | |
|         dense: true,
 | |
|         title: Text(
 | |
|           "shared_link_edit_allow_download",
 | |
|           style: themeData.textTheme.labelLarge
 | |
|               ?.copyWith(fontWeight: FontWeight.bold),
 | |
|         ).tr(),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildAllowUploadButton() {
 | |
|       return SwitchListTile.adaptive(
 | |
|         value: allowUpload.value,
 | |
|         onChanged: newShareLink.value.isEmpty
 | |
|             ? (value) => allowUpload.value = value
 | |
|             : null,
 | |
|         activeColor: themeData.primaryColor,
 | |
|         dense: true,
 | |
|         title: Text(
 | |
|           "shared_link_edit_allow_upload",
 | |
|           style: themeData.textTheme.labelLarge
 | |
|               ?.copyWith(fontWeight: FontWeight.bold),
 | |
|         ).tr(),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildEditExpiryButton() {
 | |
|       return SwitchListTile.adaptive(
 | |
|         value: editExpiry.value,
 | |
|         onChanged: newShareLink.value.isEmpty
 | |
|             ? (value) => editExpiry.value = value
 | |
|             : null,
 | |
|         activeColor: themeData.primaryColor,
 | |
|         dense: true,
 | |
|         title: Text(
 | |
|           "shared_link_edit_change_expiry",
 | |
|           style: themeData.textTheme.labelLarge
 | |
|               ?.copyWith(fontWeight: FontWeight.bold),
 | |
|         ).tr(),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildExpiryAfterButton() {
 | |
|       return DropdownMenu(
 | |
|         label: Text(
 | |
|           "shared_link_edit_expire_after",
 | |
|           style: TextStyle(
 | |
|             fontWeight: FontWeight.bold,
 | |
|             color: themeData.primaryColor,
 | |
|           ),
 | |
|         ).tr(),
 | |
|         enableSearch: false,
 | |
|         enableFilter: false,
 | |
|         width: context.width - 40,
 | |
|         initialSelection: expiryAfter.value,
 | |
|         enabled: newShareLink.value.isEmpty &&
 | |
|             (existingLink == null || editExpiry.value),
 | |
|         onSelected: (value) {
 | |
|           expiryAfter.value = value!;
 | |
|         },
 | |
|         inputDecorationTheme: themeData.inputDecorationTheme.copyWith(
 | |
|           disabledBorder: OutlineInputBorder(
 | |
|             borderSide: BorderSide(color: Colors.grey.withOpacity(0.5)),
 | |
|           ),
 | |
|           enabledBorder: const OutlineInputBorder(
 | |
|             borderSide: BorderSide(color: Colors.grey),
 | |
|           ),
 | |
|         ),
 | |
|         dropdownMenuEntries: [
 | |
|           DropdownMenuEntry(
 | |
|             value: 0,
 | |
|             label: "shared_link_edit_expire_after_option_never".tr(),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 30,
 | |
|             label:
 | |
|                 "shared_link_edit_expire_after_option_minutes".tr(args: ["30"]),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60,
 | |
|             label: "shared_link_edit_expire_after_option_hour".tr(),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60 * 6,
 | |
|             label: "shared_link_edit_expire_after_option_hours".tr(args: ["6"]),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60 * 24,
 | |
|             label: "shared_link_edit_expire_after_option_day".tr(),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60 * 24 * 7,
 | |
|             label: "shared_link_edit_expire_after_option_days".tr(args: ["7"]),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60 * 24 * 30,
 | |
|             label: "shared_link_edit_expire_after_option_days".tr(args: ["30"]),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60 * 24 * 30 * 3,
 | |
|             label:
 | |
|                 "shared_link_edit_expire_after_option_months".tr(args: ["3"]),
 | |
|           ),
 | |
|           DropdownMenuEntry(
 | |
|             value: 60 * 24 * 30 * 12,
 | |
|             label: "shared_link_edit_expire_after_option_year".tr(args: ["1"]),
 | |
|           ),
 | |
|         ],
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     void copyLinkToClipboard() {
 | |
|       Clipboard.setData(ClipboardData(text: newShareLink.value)).then((_) {
 | |
|         ScaffoldMessenger.of(context).showSnackBar(
 | |
|           SnackBar(
 | |
|             content: Text(
 | |
|               "shared_link_clipboard_copied_massage",
 | |
|               style: context.textTheme.bodyLarge?.copyWith(
 | |
|                 color: context.primaryColor,
 | |
|               ),
 | |
|             ).tr(),
 | |
|             duration: const Duration(seconds: 2),
 | |
|           ),
 | |
|         );
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     Widget buildNewLinkField() {
 | |
|       return Column(
 | |
|         children: [
 | |
|           const Padding(
 | |
|             padding: EdgeInsets.only(
 | |
|               top: 20,
 | |
|               bottom: 20,
 | |
|             ),
 | |
|             child: Divider(),
 | |
|           ),
 | |
|           TextFormField(
 | |
|             readOnly: true,
 | |
|             initialValue: newShareLink.value,
 | |
|             decoration: InputDecoration(
 | |
|               border: const OutlineInputBorder(),
 | |
|               enabledBorder: themeData.inputDecorationTheme.focusedBorder,
 | |
|               suffixIcon: IconButton(
 | |
|                 onPressed: copyLinkToClipboard,
 | |
|                 icon: const Icon(Icons.copy),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|           Padding(
 | |
|             padding: const EdgeInsets.only(top: 16.0),
 | |
|             child: Align(
 | |
|               alignment: Alignment.bottomRight,
 | |
|               child: ElevatedButton(
 | |
|                 onPressed: () {
 | |
|                   context.popRoute();
 | |
|                 },
 | |
|                 child: const Text(
 | |
|                   "share_done",
 | |
|                   style: TextStyle(
 | |
|                     fontSize: 14,
 | |
|                     fontWeight: FontWeight.bold,
 | |
|                   ),
 | |
|                 ).tr(),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|         ],
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     DateTime calculateExpiry() {
 | |
|       return DateTime.now().add(Duration(minutes: expiryAfter.value));
 | |
|     }
 | |
| 
 | |
|     Future<void> handleNewLink() async {
 | |
|       final newLink =
 | |
|           await ref.read(sharedLinkServiceProvider).createSharedLink(
 | |
|                 albumId: albumId,
 | |
|                 assetIds: assetsList,
 | |
|                 showMeta: showMetadata.value,
 | |
|                 allowDownload: allowDownload.value,
 | |
|                 allowUpload: allowUpload.value,
 | |
|                 description: descriptionController.text.isEmpty
 | |
|                     ? null
 | |
|                     : descriptionController.text,
 | |
|                 password: passwordController.text.isEmpty
 | |
|                     ? null
 | |
|                     : passwordController.text,
 | |
|                 expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(),
 | |
|               );
 | |
|       ref.invalidate(sharedLinksStateProvider);
 | |
|       final externalDomain = ref.read(
 | |
|         serverInfoProvider.select((s) => s.serverConfig.externalDomain),
 | |
|       );
 | |
|       var serverUrl =
 | |
|           externalDomain.isNotEmpty ? externalDomain : getServerUrl();
 | |
|       if (serverUrl != null && !serverUrl.endsWith('/')) {
 | |
|         serverUrl += '/';
 | |
|       }
 | |
|       if (newLink != null && serverUrl != null) {
 | |
|         newShareLink.value = "${serverUrl}share/${newLink.key}";
 | |
|         copyLinkToClipboard();
 | |
|       } else if (newLink == null) {
 | |
|         ImmichToast.show(
 | |
|           context: context,
 | |
|           gravity: ToastGravity.BOTTOM,
 | |
|           toastType: ToastType.error,
 | |
|           msg: 'shared_link_create_error'.tr(),
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     Future<void> handleEditLink() async {
 | |
|       bool? download;
 | |
|       bool? upload;
 | |
|       bool? meta;
 | |
|       String? desc;
 | |
|       String? password;
 | |
|       DateTime? expiry;
 | |
|       bool? changeExpiry;
 | |
| 
 | |
|       if (allowDownload.value != existingLink!.allowDownload) {
 | |
|         download = allowDownload.value;
 | |
|       }
 | |
| 
 | |
|       if (allowUpload.value != existingLink!.allowUpload) {
 | |
|         upload = allowUpload.value;
 | |
|       }
 | |
| 
 | |
|       if (showMetadata.value != existingLink!.showMetadata) {
 | |
|         meta = showMetadata.value;
 | |
|       }
 | |
| 
 | |
|       if (descriptionController.text != existingLink!.description) {
 | |
|         desc = descriptionController.text;
 | |
|       }
 | |
| 
 | |
|       if (passwordController.text != existingLink!.password) {
 | |
|         password = passwordController.text;
 | |
|       }
 | |
| 
 | |
|       if (editExpiry.value) {
 | |
|         expiry = expiryAfter.value == 0 ? null : calculateExpiry();
 | |
|         changeExpiry = true;
 | |
|       }
 | |
| 
 | |
|       await ref.read(sharedLinkServiceProvider).updateSharedLink(
 | |
|             existingLink!.id,
 | |
|             showMeta: meta,
 | |
|             allowDownload: download,
 | |
|             allowUpload: upload,
 | |
|             description: desc,
 | |
|             password: password,
 | |
|             expiresAt: expiry,
 | |
|             changeExpiry: changeExpiry,
 | |
|           );
 | |
|       ref.invalidate(sharedLinksStateProvider);
 | |
|       context.popRoute();
 | |
|     }
 | |
| 
 | |
|     return Scaffold(
 | |
|       appBar: AppBar(
 | |
|         title: Text(
 | |
|           existingLink == null
 | |
|               ? "shared_link_create_app_bar_title"
 | |
|               : "shared_link_edit_app_bar_title",
 | |
|         ).tr(),
 | |
|         elevation: 0,
 | |
|         leading: const CloseButton(),
 | |
|         centerTitle: false,
 | |
|       ),
 | |
|       body: SafeArea(
 | |
|         child: ListView(
 | |
|           children: [
 | |
|             Padding(
 | |
|               padding: const EdgeInsets.all(padding),
 | |
|               child: buildLinkTitle(),
 | |
|             ),
 | |
|             Padding(
 | |
|               padding: const EdgeInsets.all(padding),
 | |
|               child: buildDescriptionField(),
 | |
|             ),
 | |
|             Padding(
 | |
|               padding: const EdgeInsets.all(padding),
 | |
|               child: buildPasswordField(),
 | |
|             ),
 | |
|             Padding(
 | |
|               padding: const EdgeInsets.only(
 | |
|                 left: padding,
 | |
|                 right: padding,
 | |
|                 bottom: padding,
 | |
|               ),
 | |
|               child: buildShowMetaButton(),
 | |
|             ),
 | |
|             Padding(
 | |
|               padding: const EdgeInsets.only(
 | |
|                 left: padding,
 | |
|                 right: padding,
 | |
|                 bottom: padding,
 | |
|               ),
 | |
|               child: buildAllowDownloadButton(),
 | |
|             ),
 | |
|             Padding(
 | |
|               padding:
 | |
|                   const EdgeInsets.only(left: padding, right: 20, bottom: 20),
 | |
|               child: buildAllowUploadButton(),
 | |
|             ),
 | |
|             if (existingLink != null)
 | |
|               Padding(
 | |
|                 padding: const EdgeInsets.only(
 | |
|                   left: padding,
 | |
|                   right: padding,
 | |
|                   bottom: padding,
 | |
|                 ),
 | |
|                 child: buildEditExpiryButton(),
 | |
|               ),
 | |
|             Padding(
 | |
|               padding: const EdgeInsets.only(
 | |
|                 left: padding,
 | |
|                 right: padding,
 | |
|                 bottom: padding,
 | |
|               ),
 | |
|               child: buildExpiryAfterButton(),
 | |
|             ),
 | |
|             if (newShareLink.value.isEmpty)
 | |
|               Align(
 | |
|                 alignment: Alignment.bottomRight,
 | |
|                 child: Padding(
 | |
|                   padding: const EdgeInsets.only(
 | |
|                     right: padding + 10,
 | |
|                     bottom: padding,
 | |
|                   ),
 | |
|                   child: ElevatedButton(
 | |
|                     onPressed:
 | |
|                         existingLink != null ? handleEditLink : handleNewLink,
 | |
|                     child: Text(
 | |
|                       existingLink != null
 | |
|                           ? "shared_link_edit_submit_button"
 | |
|                           : "shared_link_create_submit_button",
 | |
|                       style: const TextStyle(
 | |
|                         fontSize: 14,
 | |
|                         fontWeight: FontWeight.bold,
 | |
|                       ),
 | |
|                     ).tr(),
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|             if (newShareLink.value.isNotEmpty)
 | |
|               Padding(
 | |
|                 padding: const EdgeInsets.only(
 | |
|                   left: padding,
 | |
|                   right: padding,
 | |
|                   bottom: padding,
 | |
|                 ),
 | |
|                 child: buildNewLinkField(),
 | |
|               ),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |