This commit is contained in:
rang 2025-12-25 17:47:49 +08:00
parent ead8850b2e
commit 0a26871bb1
6 changed files with 183 additions and 26 deletions

View file

@ -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<AudiobookshelfApi>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef AuthenticatedApiRef = ProviderRef<AudiobookshelfApi>;
String _$isServerAliveHash() => r'3afd608ced03a23fa7300d4a59368d170406ecc8';
String _$isServerAliveHash() => r'71f272f2102f43c1d2be212eff85faf2aadf916d';
/// ping the server to check if it is reachable
///

View file

@ -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,

View file

@ -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,
),
],
),
),
);
}

View file

@ -0,0 +1,7 @@
import 'package:flutter/material.dart';
extension TextStyleExtension on TextStyle {
double get calculateHeight {
return (height ?? 0) * (fontSize ?? 0);
}
}

View file

@ -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,

@ -1 +1 @@
Subproject commit 875c4bf90f5f08f24b90d3478322696ea29bd28f
Subproject commit c4d69ada95a8ad2db367bb3c5efd181668ceca6d