diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0ffe3ef..b93f0e4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1149,5 +1149,14 @@ "mediaSegmentOutro": "Outro", "mediaSegmentIntro": "Intro", "errorLogs": "Error logs", - "external": "External" + "external": "External", + "downloadFile": "Download {type}", + "@downloadFile": { + "placeholders": { + "type": { + "type": "String" + } + } + }, + "copyStreamUrl": "Copy stream url" } \ No newline at end of file diff --git a/lib/models/account_model.dart b/lib/models/account_model.dart index 80de4b7..b0cb85d 100644 --- a/lib/models/account_model.dart +++ b/lib/models/account_model.dart @@ -38,13 +38,9 @@ class AccountModel with _$AccountModel { factory AccountModel.fromJson(Map json) => _$AccountModelFromJson(json); - String get server { - return credentials.server; - } + String get server => credentials.server; - bool get canDownload { - return (policy?.enableContentDownloading ?? false) && !kIsWeb; - } + bool get canDownload => (policy?.enableContentDownloading ?? false); //Check if it's the same account on the same server bool sameIdentity(AccountModel other) { diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index 2735e37..3e7f7d8 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -5,6 +5,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:fladder/jellyfin/enum_models.dart'; import 'package:fladder/models/account_model.dart'; +import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/library_filters_model.dart'; import 'package:fladder/providers/api_provider.dart'; @@ -172,4 +173,7 @@ class User extends _$User { } void deleteAllFilters() => state = state?.copyWith(savedFilters: []); + + String? createDownloadUrl(ItemBaseModel item) => + Uri.encodeFull("${state?.server}/Items/${item.id}/Download?api_key=${state?.credentials.token}"); } diff --git a/lib/util/file_downloader.dart b/lib/util/file_downloader.dart new file mode 100644 index 0000000..d9dcaac --- /dev/null +++ b/lib/util/file_downloader.dart @@ -0,0 +1,11 @@ +import 'package:universal_html/html.dart' as html; + +Future downloadFile(String url) async { + try { + html.AnchorElement anchorElement = html.AnchorElement(href: url); + anchorElement.download = url; + anchorElement.click(); + } catch (e) { + print('Download error: $e'); + } +} diff --git a/lib/util/item_base_model/item_base_model_extensions.dart b/lib/util/item_base_model/item_base_model_extensions.dart index 4345a43..0c29c52 100644 --- a/lib/util/item_base_model/item_base_model_extensions.dart +++ b/lib/util/item_base_model/item_base_model_extensions.dart @@ -1,4 +1,6 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:ficonsax/ficonsax.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -19,6 +21,7 @@ import 'package:fladder/screens/playlists/add_to_playlists.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/syncing/sync_button.dart'; import 'package:fladder/screens/syncing/sync_item_details.dart'; +import 'package:fladder/util/file_downloader.dart'; import 'package:fladder/util/item_base_model/play_item_helpers.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/refresh_state.dart'; @@ -76,6 +79,7 @@ extension ItemBaseModelExtensions on ItemBaseModel { syncAble && (canDownload ?? false); final syncedItem = ref.read(syncProvider.notifier).getSyncedItem(this); + final downloadUrl = ref.read(userProvider.notifier).createDownloadUrl(this); return [ if (!exclude.contains(ItemActions.play)) if (playAble) @@ -197,19 +201,38 @@ extension ItemBaseModelExtensions on ItemBaseModel { }, label: Text(context.localized.refreshMetadata), ), - if (!exclude.contains(ItemActions.download) && downloadEnabled) - if (syncedItem == null) + if (!exclude.contains(ItemActions.download) && downloadEnabled) ...[ + if (!kIsWeb) + if (syncedItem == null) + ItemActionButton( + icon: const Icon(IconsaxOutline.arrow_down_2), + label: Text(context.localized.sync), + action: () => ref.read(syncProvider.notifier).addSyncItem(context, this), + ) + else + ItemActionButton( + icon: IgnorePointer(child: SyncButton(item: this, syncedItem: syncedItem)), + action: () => showSyncItemDetails(context, syncedItem, ref), + label: Text(context.localized.syncDetails), + ) + else if (downloadUrl != null) ...[ ItemActionButton( - icon: const Icon(IconsaxOutline.arrow_down_2), - label: Text(context.localized.sync), - action: () => ref.read(syncProvider.notifier).addSyncItem(context, this), - ) - else - ItemActionButton( - icon: IgnorePointer(child: SyncButton(item: this, syncedItem: syncedItem)), - action: () => showSyncItemDetails(context, syncedItem, ref), - label: Text(context.localized.syncDetails), + icon: const Icon(IconsaxOutline.document_download), + action: () => downloadFile(downloadUrl), + label: Text(context.localized.downloadFile(type.label(context).toLowerCase())), ), + ItemActionButton( + icon: const Icon(IconsaxOutline.link_21), + action: () async { + await Clipboard.setData(ClipboardData(text: downloadUrl)); + if (context.mounted) { + fladderSnackbar(context, title: "Copied URL to clipboard"); + } + }, + label: Text(context.localized.copyStreamUrl), + ) + ], + ], if (canDelete == true) ItemActionButton( icon: Container(