mirror of
https://github.com/immich-app/immich.git
synced 2026-02-02 18:18:10 -08:00
Compare commits
3 Commits
renovate/t
...
feat/custo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4977363e85 | ||
|
|
a5144b0821 | ||
|
|
c0dcef6cf0 |
@@ -2088,6 +2088,7 @@
|
||||
"shared_link_manage_links": "Manage Shared links",
|
||||
"shared_link_options": "Shared link options",
|
||||
"shared_link_password_description": "Require a password to access this shared link",
|
||||
"shared_link_update_error": "Error while updating shared link",
|
||||
"shared_links": "Shared links",
|
||||
"shared_links_description": "Share photos and videos with a link",
|
||||
"shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}",
|
||||
|
||||
@@ -13,6 +13,7 @@ class SharedLink {
|
||||
final DateTime? expiresAt;
|
||||
final String key;
|
||||
final bool showMetadata;
|
||||
final String? slug;
|
||||
final SharedLinkSource type;
|
||||
|
||||
const SharedLink({
|
||||
@@ -26,6 +27,7 @@ class SharedLink {
|
||||
required this.expiresAt,
|
||||
required this.key,
|
||||
required this.showMetadata,
|
||||
required this.slug,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
@@ -40,6 +42,7 @@ class SharedLink {
|
||||
DateTime? expiresAt,
|
||||
String? key,
|
||||
bool? showMetadata,
|
||||
String? slug,
|
||||
SharedLinkSource? type,
|
||||
}) {
|
||||
return SharedLink(
|
||||
@@ -53,6 +56,7 @@ class SharedLink {
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
key: key ?? this.key,
|
||||
showMetadata: showMetadata ?? this.showMetadata,
|
||||
slug: slug ?? this.slug,
|
||||
type: type ?? this.type,
|
||||
);
|
||||
}
|
||||
@@ -66,6 +70,7 @@ class SharedLink {
|
||||
expiresAt = dto.expiresAt,
|
||||
key = dto.key,
|
||||
showMetadata = dto.showMetadata,
|
||||
slug = dto.slug,
|
||||
type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual,
|
||||
title = dto.type == SharedLinkType.ALBUM
|
||||
? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE"
|
||||
@@ -78,7 +83,7 @@ class SharedLink {
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type)';
|
||||
'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, slug=$slug, type=$type)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
@@ -94,6 +99,7 @@ class SharedLink {
|
||||
other.expiresAt == expiresAt &&
|
||||
other.key == key &&
|
||||
other.showMetadata == showMetadata &&
|
||||
other.slug == slug &&
|
||||
other.type == type;
|
||||
|
||||
@override
|
||||
@@ -108,5 +114,6 @@ class SharedLink {
|
||||
expiresAt.hashCode ^
|
||||
key.hashCode ^
|
||||
showMetadata.hashCode ^
|
||||
slug.hashCode ^
|
||||
type.hashCode;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
@RoutePage()
|
||||
class SharedLinkEditPage extends HookConsumerWidget {
|
||||
@@ -26,6 +27,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
const padding = 20.0;
|
||||
final themeData = context.themeData;
|
||||
final colorScheme = context.colorScheme;
|
||||
final slugController = useTextEditingController(text: existingLink?.slug ?? "");
|
||||
final descriptionController = useTextEditingController(text: existingLink?.description ?? "");
|
||||
final descriptionFocusNode = useFocusNode();
|
||||
final passwordController = useTextEditingController(text: existingLink?.password ?? "");
|
||||
@@ -71,6 +73,46 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
return const Text("create_link_to_share_description", style: TextStyle(fontWeight: FontWeight.bold)).tr();
|
||||
}
|
||||
|
||||
Widget buildSlugField() {
|
||||
final isDarkMode = colorScheme.brightness == Brightness.dark;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: slugController,
|
||||
enabled: newShareLink.value.isEmpty,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'custom_url'.tr(),
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'shared_link_custom_url_description'.tr(),
|
||||
hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14),
|
||||
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))),
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: slugController,
|
||||
builder: (context, value, _) {
|
||||
if (value.text.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'/s/${value.text.trim()}',
|
||||
style: TextStyle(color: isDarkMode ? Colors.grey[400] : Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDescriptionField() {
|
||||
return TextField(
|
||||
controller: descriptionController,
|
||||
@@ -262,6 +304,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
description: descriptionController.text.isEmpty ? null : descriptionController.text,
|
||||
password: passwordController.text.isEmpty ? null : passwordController.text,
|
||||
expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(),
|
||||
slug: slugController.text.isEmpty ? null : slugController.text.trim(),
|
||||
);
|
||||
ref.invalidate(sharedLinksStateProvider);
|
||||
|
||||
@@ -274,7 +317,11 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
if (newLink != null && serverUrl != null) {
|
||||
newShareLink.value = "${serverUrl}share/${newLink.key}";
|
||||
if (newLink.slug != null && newLink.slug!.isNotEmpty) {
|
||||
newShareLink.value = "${serverUrl}s/${newLink.slug}";
|
||||
} else {
|
||||
newShareLink.value = "${serverUrl}share/${newLink.key}";
|
||||
}
|
||||
copyLinkToClipboard();
|
||||
} else if (newLink == null) {
|
||||
ImmichToast.show(
|
||||
@@ -320,20 +367,32 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
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);
|
||||
await context.maybePop();
|
||||
try {
|
||||
await ref
|
||||
.read(sharedLinkServiceProvider)
|
||||
.updateSharedLink(
|
||||
existingLink!.id,
|
||||
showMeta: meta,
|
||||
allowDownload: download,
|
||||
allowUpload: upload,
|
||||
description: desc,
|
||||
password: password,
|
||||
expiresAt: expiry,
|
||||
changeExpiry: changeExpiry,
|
||||
slug: slugController.text.isEmpty ? null : slugController.text.trim(),
|
||||
);
|
||||
|
||||
ref.invalidate(sharedLinksStateProvider);
|
||||
await context.maybePop();
|
||||
} on ApiException catch (_) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
msg: 'shared_link_update_error'.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
@@ -347,6 +406,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()),
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildSlugField()),
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()),
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()),
|
||||
Padding(
|
||||
|
||||
@@ -40,6 +40,7 @@ class SharedLinkService {
|
||||
String? albumId,
|
||||
List<String>? assetIds,
|
||||
DateTime? expiresAt,
|
||||
String? slug,
|
||||
}) async {
|
||||
try {
|
||||
final type = albumId != null ? SharedLinkType.ALBUM : SharedLinkType.INDIVIDUAL;
|
||||
@@ -54,6 +55,7 @@ class SharedLinkService {
|
||||
expiresAt: expiresAt,
|
||||
description: description,
|
||||
password: password,
|
||||
slug: slug,
|
||||
);
|
||||
} else if (assetIds != null) {
|
||||
dto = SharedLinkCreateDto(
|
||||
@@ -65,6 +67,7 @@ class SharedLinkService {
|
||||
description: description,
|
||||
password: password,
|
||||
assetIds: assetIds,
|
||||
slug: slug,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,6 +92,7 @@ class SharedLinkService {
|
||||
String? description,
|
||||
String? password,
|
||||
DateTime? expiresAt,
|
||||
String? slug,
|
||||
}) async {
|
||||
try {
|
||||
final responseDto = await _apiService.sharedLinksApi.updateSharedLink(
|
||||
@@ -101,6 +105,7 @@ class SharedLinkService {
|
||||
description: description,
|
||||
password: password,
|
||||
changeExpiryTime: changeExpiry,
|
||||
slug: slug,
|
||||
),
|
||||
);
|
||||
if (responseDto != null) {
|
||||
@@ -108,6 +113,7 @@ class SharedLinkService {
|
||||
}
|
||||
} catch (e) {
|
||||
_log.severe("Failed to update shared link id - $id", e);
|
||||
rethrow; // Handled at UI level
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,13 @@ class SharedLinkItem extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
Clipboard.setData(ClipboardData(text: "${serverUrl}share/${sharedLink.key}")).then((_) {
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: sharedLink.slug != null && sharedLink.slug!.isNotEmpty
|
||||
? "${serverUrl}s/${sharedLink.slug}"
|
||||
: "${serverUrl}share/${sharedLink.key}",
|
||||
),
|
||||
).then((_) {
|
||||
context.scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
|
||||
Reference in New Issue
Block a user