mirror of
https://github.com/immich-app/immich.git
synced 2025-09-29 15:31:13 -04:00
* bump flutter to 3.35.3 * migrate deprecated code * linting * disable custom_lint in ci * disable custom_lint
398 lines
15 KiB
Dart
398 lines
15 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/server_info.provider.dart';
|
|
import 'package:immich_mobile/providers/shared_link.provider.dart';
|
|
import 'package:immich_mobile/services/shared_link.service.dart';
|
|
import 'package:immich_mobile/utils/url_helper.dart';
|
|
import 'package:immich_mobile/widgets/common/immich_toast.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 colorScheme = context.colorScheme;
|
|
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('public_album', style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
|
const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)),
|
|
Text(
|
|
existingLink!.title,
|
|
style: TextStyle(color: colorScheme.primary, 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: colorScheme.primary, fontWeight: FontWeight.bold),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
return const Text("create_link_to_share_description", 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: 'description'.tr(),
|
|
labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
|
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.withValues(alpha: 0.5))),
|
|
),
|
|
onTapOutside: (_) => descriptionFocusNode.unfocus(),
|
|
);
|
|
}
|
|
|
|
Widget buildPasswordField() {
|
|
return TextField(
|
|
controller: passwordController,
|
|
enabled: newShareLink.value.isEmpty,
|
|
autofocus: false,
|
|
decoration: InputDecoration(
|
|
labelText: 'password'.tr(),
|
|
labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
|
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.withValues(alpha: 0.5))),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildShowMetaButton() {
|
|
return SwitchListTile.adaptive(
|
|
value: showMetadata.value,
|
|
onChanged: newShareLink.value.isEmpty ? (value) => showMetadata.value = value : null,
|
|
activeThumbColor: colorScheme.primary,
|
|
dense: true,
|
|
title: Text("show_metadata", 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,
|
|
activeThumbColor: colorScheme.primary,
|
|
dense: true,
|
|
title: Text(
|
|
"allow_public_user_to_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,
|
|
activeThumbColor: colorScheme.primary,
|
|
dense: true,
|
|
title: Text(
|
|
"allow_public_user_to_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,
|
|
activeThumbColor: colorScheme.primary,
|
|
dense: true,
|
|
title: Text(
|
|
"change_expiration_time",
|
|
style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold),
|
|
).tr(),
|
|
);
|
|
}
|
|
|
|
Widget buildExpiryAfterButton() {
|
|
return DropdownMenu(
|
|
label: Text(
|
|
"expire_after",
|
|
style: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
|
).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!;
|
|
},
|
|
dropdownMenuEntries: [
|
|
DropdownMenuEntry(value: 0, label: "never".tr()),
|
|
DropdownMenuEntry(
|
|
value: 30,
|
|
label: "shared_link_edit_expire_after_option_minutes".tr(namedArgs: {'count': "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(namedArgs: {'count': "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(namedArgs: {'count': "7"}),
|
|
),
|
|
DropdownMenuEntry(
|
|
value: 60 * 24 * 30,
|
|
label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "30"}),
|
|
),
|
|
DropdownMenuEntry(
|
|
value: 60 * 24 * 30 * 3,
|
|
label: "shared_link_edit_expire_after_option_months".tr(namedArgs: {'count': "3"}),
|
|
),
|
|
DropdownMenuEntry(
|
|
value: 60 * 24 * 30 * 12,
|
|
label: "shared_link_edit_expire_after_option_year".tr(namedArgs: {'count': "1"}),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void copyLinkToClipboard() {
|
|
Clipboard.setData(ClipboardData(text: newShareLink.value)).then((_) {
|
|
context.scaffoldMessenger.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.maybePop();
|
|
},
|
|
child: const Text("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);
|
|
|
|
await ref.read(serverInfoProvider.notifier).getServerConfig();
|
|
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.maybePop();
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(existingLink == null ? "create_link_to_share" : "edit_link").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" : "create_link",
|
|
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(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|