From 0a26871bb1c93ca13a4004ed7f52b5b61d606ab6 Mon Sep 17 00:00:00 2001 From: rang <378694192@qq.com> Date: Thu, 25 Dec 2025 17:47:49 +0800 Subject: [PATCH] 123 --- lib/api/api_provider.g.dart | 6 +- .../view/library_item_actions.dart | 135 ++++++++++++++++++ lib/pages/library_page.dart | 51 ++++--- lib/shared/extensions/style.dart | 7 + lib/shared/widgets/shelves/book_shelf.dart | 8 +- shelfsdk | 2 +- 6 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 lib/shared/extensions/style.dart diff --git a/lib/api/api_provider.g.dart b/lib/api/api_provider.g.dart index 487ccad..b75ac74 100644 --- a/lib/api/api_provider.g.dart +++ b/lib/api/api_provider.g.dart @@ -6,7 +6,7 @@ part of 'api_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$audiobookshelfApiHash() => r'd7fbddf9ce2b463468c8d4db5a1bc4a53b7b7278'; +String _$audiobookshelfApiHash() => r'ba34f6a16394cdc849b1bd63cd1f3f2472f04f69'; /// Copied from Dart SDK class _SystemHash { @@ -170,7 +170,7 @@ class _AudiobookshelfApiProviderElement Uri? get baseUrl => (origin as AudiobookshelfApiProvider).baseUrl; } -String _$authenticatedApiHash() => r'13bba42fa712f173d3b72761ae9d544854df26d0'; +String _$authenticatedApiHash() => r'd672c261b2da7b5091d64e2f7efb0da356ca32a5'; /// get the api instance for the authenticated user /// @@ -191,7 +191,7 @@ final authenticatedApiProvider = Provider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef AuthenticatedApiRef = ProviderRef; -String _$isServerAliveHash() => r'3afd608ced03a23fa7300d4a59368d170406ecc8'; +String _$isServerAliveHash() => r'71f272f2102f43c1d2be212eff85faf2aadf916d'; /// ping the server to check if it is reachable /// diff --git a/lib/features/item_viewer/view/library_item_actions.dart b/lib/features/item_viewer/view/library_item_actions.dart index b695183..99d25fd 100644 --- a/lib/features/item_viewer/view/library_item_actions.dart +++ b/lib/features/item_viewer/view/library_item_actions.dart @@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shelfsdk/audiobookshelf_api.dart' as shelfsdk; import 'package:vaani/api/library_item_provider.dart'; import 'package:vaani/constants/hero_tag_conventions.dart'; +import 'package:vaani/constants/sizes.dart'; import 'package:vaani/features/downloads/providers/download_manager.dart' show downloadHistoryProvider, @@ -93,6 +94,7 @@ class LibraryItemActions extends HookConsumerWidget { }, icon: const Icon(Icons.share_rounded), ), + LibItemDownButton(item.id), // download button LibItemDownloadButton(item: item), @@ -202,6 +204,139 @@ class LibraryItemActions extends HookConsumerWidget { } } +class LibItemDownButton extends HookConsumerWidget { + const LibItemDownButton(this.libraryItemId, {super.key}); + final String libraryItemId; + @override + Widget build(BuildContext context, WidgetRef ref) { + return IconButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) { + return FractionallySizedBox( + heightFactor: 0.8, + child: LibItemDownSheet(libraryItemId), + ); + }, + ); + }, + icon: const Icon( + Icons.download_sharp, + ), + ); + } +} + +class LibItemDownSheet extends HookConsumerWidget { + const LibItemDownSheet(this.libraryItemId, {super.key}); + final String libraryItemId; + @override + Widget build(BuildContext context, WidgetRef ref) { + // final downloadHistory = + // ref.watch(downloadHistoryProvider(group: libraryItemId)); + final libraryItem = ref.watch(libraryItemProvider(libraryItemId)); + return libraryItem.when( + data: (item) { + final book = item.media.asBookExpanded; + final tracks = book.tracks; + return Padding( + padding: const EdgeInsets.all(AppElementSizes.paddingRegular), + child: Column( + children: [ + Row( + children: [ + Text('下载管理'), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + onPressed: () {}, + icon: Icon(Icons.delete_outlined), + ), + IconButton( + onPressed: () {}, + icon: Icon(Icons.download_outlined), + ), + ], + ), + ), + ], + ), + Expanded( + child: ListView.builder( + itemCount: tracks.length, + itemBuilder: (context, index) { + final track = tracks[index]; + return ListTile( + title: Text(track.title), + subtitle: Text( + // '${record.task.directory}/${record.task.baseDirectory}', + // track.metadata?.relPath ?? '', + item.relPath, + ), + trailing: const Icon( + Icons.open_in_new_rounded, + ), + onLongPress: () { + // show the delete dialog + // _showDialog(context, record.task); + }, + onTap: () async { + // open the file location + }, + ); + }, + ), + ), + ], + ), + ); + }, + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (error, stackTrace) => Center( + child: Text('Error: $error'), + ), + ); + } + + void _showDialog(context, task) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Delete'), + content: Text( + 'Are you sure you want to delete ${task.filename}?', + ), + actions: [ + TextButton( + onPressed: () { + // delete the file + FileDownloader().database.deleteRecordWithId( + task.taskId, + ); + Navigator.pop(context); + }, + child: const Text('Yes'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('No'), + ), + ], + ); + }, + ); + } +} + class LibItemDownloadButton extends HookConsumerWidget { const LibItemDownloadButton({ super.key, diff --git a/lib/pages/library_page.dart b/lib/pages/library_page.dart index 2e509db..6fc3394 100644 --- a/lib/pages/library_page.dart +++ b/lib/pages/library_page.dart @@ -14,6 +14,7 @@ import 'package:vaani/generated/l10n.dart'; import 'package:vaani/router/models/library_item_extras.dart'; import 'package:vaani/router/router.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; +import 'package:vaani/shared/extensions/style.dart'; import 'package:vaani/shared/icons/abs_icons.dart'; import 'package:vaani/shared/widgets/skeletons.dart'; @@ -101,6 +102,7 @@ class LibraryPage extends HookConsumerWidget { itemBuilder: (context, index) { return LibraryPageItem( item: items[index], + width: width, ); }, ); @@ -133,12 +135,20 @@ class LibraryPageItem extends HookConsumerWidget { const LibraryPageItem({ super.key, required this.item, + required this.width, }); final LibraryItem item; + final double width; @override Widget build(BuildContext context, WidgetRef ref) { final book = item.media.asBookMinified; final metadata = book.metadata.asBookMetadataMinified; + final bodyLarge = Theme.of(context).textTheme.bodyLarge; + final bodySmall = Theme.of(context).textTheme.bodySmall; + final height = width + + 15 + + (bodyLarge?.calculateHeight ?? 0) + + (bodySmall?.calculateHeight ?? 0); return InkWell( onTap: () => context.pushNamed( Routes.libraryItem.name, @@ -150,25 +160,28 @@ class LibraryPageItem extends HookConsumerWidget { ), ), borderRadius: BorderRadius.circular(10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BookCoverWidget(itemId: item.id), - const SizedBox(height: 3), - Text( - metadata.title ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(height: 2), - Text( - metadata.authorName ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ), - ], + child: SizedBox( + height: height, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: BookCoverWidget(itemId: item.id)), + const SizedBox(height: 3), + Text( + metadata.title ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: bodyLarge, + ), + const SizedBox(height: 2), + Text( + metadata.authorName ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: bodySmall, + ), + ], + ), ), ); } diff --git a/lib/shared/extensions/style.dart b/lib/shared/extensions/style.dart new file mode 100644 index 0000000..606f4dd --- /dev/null +++ b/lib/shared/extensions/style.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +extension TextStyleExtension on TextStyle { + double get calculateHeight { + return (height ?? 0) * (fontSize ?? 0); + } +} diff --git a/lib/shared/widgets/shelves/book_shelf.dart b/lib/shared/widgets/shelves/book_shelf.dart index 4cd7efc..e1ff7ee 100644 --- a/lib/shared/widgets/shelves/book_shelf.dart +++ b/lib/shared/widgets/shelves/book_shelf.dart @@ -213,10 +213,12 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final me = ref.watch(meProvider); final currentBook = ref.watch(currentBookProvider); - final playing = ref.watch(playerStateProvider.select((v) => v.playing)); + final playerStateNotifier = ref.watch(playerStateProvider.notifier); + final isLoading = playerStateNotifier.isLoading(libraryItemId); final isCurrentBookSetInPlayer = currentBook?.libraryItemId == libraryItemId; - final isPlayingThisBook = playing && isCurrentBookSetInPlayer; + final isPlayingThisBook = + playerStateNotifier.isPlaying() && isCurrentBookSetInPlayer; final userProgress = me.valueOrNull?.mediaProgress ?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId); @@ -298,7 +300,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { icon: Hero( tag: HeroTagPrefixes.libraryItemPlayButton + libraryItemId, child: DynamicItemPlayIcon( - // isLoading: isLoading, + isLoading: isLoading, isBookCompleted: isBookCompleted, isPlayingThisBook: isPlayingThisBook, isCurrentBookSetInPlayer: isCurrentBookSetInPlayer, diff --git a/shelfsdk b/shelfsdk index 875c4bf..c4d69ad 160000 --- a/shelfsdk +++ b/shelfsdk @@ -1 +1 @@ -Subproject commit 875c4bf90f5f08f24b90d3478322696ea29bd28f +Subproject commit c4d69ada95a8ad2db367bb3c5efd181668ceca6d