媒体库上拉刷新,下拉加载

This commit is contained in:
rang 2025-12-30 17:02:28 +08:00
parent bdd85efcd8
commit ebcbe1774a
20 changed files with 351 additions and 126 deletions

View file

@ -12,7 +12,7 @@ import 'package:vaani/shared/extensions/obfuscation.dart';
final _logger = Logger('AudiobookDownloadManager');
final tq = MemoryTaskQueue();
const downloadDirectory = BaseDirectory.applicationSupport;
// const downloadDirectory = BaseDirectory.applicationSupport;
class AudiobookDownloadManager {
// takes in the baseUrl and the token
@ -72,16 +72,12 @@ class AudiobookDownloadManager {
late StreamSubscription<TaskUpdate> _updatesSubscription;
Future<void> queueAudioBookDownload(
LibraryItemExpanded item, {
String prePath = '',
}) async {
LibraryItemExpanded item,
) async {
_logger.info('queuing download for item: ${item.id}');
// create a download task for each file in the item
for (final file in item.libraryFiles) {
//
if (![FileType.audio, FileType.video].contains(file.fileType)) {
continue;
}
// for (final file in item.libraryFiles) {
for (final file in item.media.asBookExpanded.audioFiles) {
// check if the file is already downloaded
if (isFileDownloaded(
constructFilePath(item, file),
@ -93,13 +89,13 @@ class AudiobookDownloadManager {
final task = DownloadTask(
taskId: file.ino,
url: file.url(baseUrl, item.id, token).toString(),
directory: prePath + item.relPath,
directory: getPath(item.relPath),
filename: file.metadata.filename,
requiresWiFi: requiresWiFi,
retries: retries,
allowPause: allowPause,
group: item.id,
baseDirectory: downloadDirectory,
baseDirectory: baseDirectory,
updates: Updates.statusAndProgress,
// metaData: token
);
@ -111,9 +107,9 @@ class AudiobookDownloadManager {
String constructFilePath(
LibraryItemExpanded item,
LibraryFile file,
AudioFile file,
) =>
'${appSupportDir.path}/${item.relPath}/${file.metadata.filename}';
'$basePath/${item.relPath}/${file.metadata.filename}';
void dispose() {
_updatesSubscription.cancel();
@ -129,11 +125,12 @@ class AudiobookDownloadManager {
return File(filePath).existsSync();
}
Future<List<LibraryFile>> getDownloadedFilesMetadata(
Future<List<AudioFile>> getDownloadedFilesMetadata(
LibraryItemExpanded item,
) async {
final downloadedFiles = <LibraryFile>[];
for (final file in item.libraryFiles) {
final downloadedFiles = <AudioFile>[];
// for (final file in item.libraryFiles) {
for (final file in item.media.asBookExpanded.audioFiles) {
final filePath = constructFilePath(item, file);
if (isFileDownloaded(filePath)) {
downloadedFiles.add(file);
@ -152,7 +149,8 @@ class AudiobookDownloadManager {
}
Future<bool> isItemDownloaded(LibraryItemExpanded item) async {
for (final file in item.libraryFiles) {
// for (final file in item.libraryFiles) {
for (final file in item.media.asBookExpanded.audioFiles) {
if (!isFileDownloaded(constructFilePath(item, file))) {
_logger.info('file not downloaded: ${file.metadata.filename}');
return false;
@ -164,7 +162,8 @@ class AudiobookDownloadManager {
Future<void> deleteDownloadedItem(LibraryItemExpanded item) async {
_logger.info('deleting downloaded item with id: ${item.id}');
for (final file in item.libraryFiles) {
// for (final file in item.libraryFiles) {
for (final file in item.media.asBookExpanded.audioFiles) {
final filePath = constructFilePath(item, file);
if (isFileDownloaded(filePath)) {
File(filePath).deleteSync();
@ -175,7 +174,8 @@ class AudiobookDownloadManager {
Future<List<Uri>> getDownloadedFilesUri(LibraryItemExpanded item) async {
final files = <Uri>[];
for (final file in item.libraryFiles) {
// for (final file in item.libraryFiles) {
for (final file in item.media.asBookExpanded.audioFiles) {
final filePath = constructFilePath(item, file);
if (isFileDownloaded(filePath)) {
files.add(Uri.file(filePath));
@ -208,4 +208,29 @@ class AudiobookDownloadManager {
_logger.warning('Error when listening to download manager updates: $e');
}
}
String getPath(String relPath) {
if (path.isNotEmpty) {
return '$path/$relPath';
}
return relPath;
}
String get basePath => switch (baseDirectory) {
BaseDirectory.applicationSupport => appSupportDir.path,
BaseDirectory.applicationDocuments => appDocumentsDir.path,
_ => path,
};
BaseDirectory get baseDirectory {
if (path.isNotEmpty) {
return BaseDirectory.root;
} else if (Platform.isIOS || Platform.isMacOS) {
return BaseDirectory.applicationDocuments;
}
// else if (Platform.isAndroid) {
// return BaseDirectory.temporary;
// }
return BaseDirectory.applicationSupport;
}
}

View file

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logging/logging.dart';
@ -71,24 +69,6 @@ class DownloadManager extends _$DownloadManager {
await state.deleteDownloadedItem(item);
ref.notifyListeners();
}
String _getDirectory(String path) {
if (Platform.isWindows) {
return path;
}
return path;
}
BaseDirectory _getBaseDirectory() {
if (Platform.isIOS) {
return BaseDirectory.applicationDocuments;
} else if (Platform.isAndroid) {
return BaseDirectory.temporary;
} else if (Platform.isWindows) {
return BaseDirectory.root;
}
return BaseDirectory.applicationSupport;
}
}
@riverpod

View file

@ -176,7 +176,7 @@ final simpleDownloadManagerProvider = NotifierProvider<SimpleDownloadManager,
);
typedef _$SimpleDownloadManager = Notifier<core.AudiobookDownloadManager>;
String _$downloadManagerHash() => r'92afe484d6735d5de53473011ea9ecbad107fc1c';
String _$downloadManagerHash() => r'852012e32e613f86445afc7f7e4e85bec808e982';
/// See also [DownloadManager].
@ProviderFor(DownloadManager)

View file

@ -22,6 +22,7 @@ import 'package:vaani/globals.dart';
import 'package:vaani/router/router.dart';
import 'package:vaani/shared/extensions/model_conversions.dart';
import 'package:vaani/shared/utils.dart';
import 'package:vaani/shared/utils/custom_dialog.dart';
class LibraryItemActions extends HookConsumerWidget {
const LibraryItemActions({
@ -236,6 +237,8 @@ class LibItemDownSheet extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// final downloadHistory =
// ref.watch(downloadHistoryProvider(group: libraryItemId));
// final downloadManager = ref.watch(downloadManagerProvider);
// downloadManager.
final libraryItem = ref.watch(libraryItemProvider(libraryItemId));
return libraryItem.when(
data: (item) {
@ -247,19 +250,24 @@ class LibItemDownSheet extends HookConsumerWidget {
children: [
Row(
children: [
Text('下载管理'),
Text(S.of(context).bookDownloads),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
appLogger.fine('Pressed delete download button');
ref
.read(downloadManagerProvider.notifier)
.deleteDownloadedItem(
item,
);
DialogUtils.deleteDialog(
context,
name: item.media.metadata.title,
onPressed: () {
ref
.read(downloadManagerProvider.notifier)
.deleteDownloadedItem(
item,
);
},
);
},
icon: Icon(Icons.delete_outlined),
),
@ -307,38 +315,6 @@ class LibItemDownSheet extends HookConsumerWidget {
),
);
}
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 {
@ -572,12 +548,12 @@ class _LibraryItemPlayButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final currentBook = ref.watch(currentBookProvider);
final book = item.media.asBookExpanded;
final playerStateNotifier = ref.watch(playerStateProvider.notifier);
final playing = ref.watch(playerStateProvider.select((v) => v.playing));
final playerStateNotifier = ref.read(playerStateProvider.notifier);
final isLoading = playerStateNotifier.isLoading(book.libraryItemId);
final isCurrentBookSetInPlayer =
currentBook?.libraryItemId == book.libraryItemId;
final isPlayingThisBook =
playerStateNotifier.isPlaying() && isCurrentBookSetInPlayer;
final isPlayingThisBook = playing && isCurrentBookSetInPlayer;
final userMediaProgress = item.userMediaProgress;
final isBookCompleted = userMediaProgress?.isFinished ?? false;

View file

@ -88,6 +88,8 @@ class AbsPlayer extends _$AbsPlayer {
final api = ref.read(authenticatedApiProvider);
final downloadManager = ref.read(simpleDownloadManagerProvider);
print(downloadManager.basePath);
final libItem =
await ref.read(libraryItemProvider(book.libraryItemId).future);
final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem);

View file

@ -243,7 +243,7 @@ final currentChaptersProvider =
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef CurrentChaptersRef = AutoDisposeProviderRef<List<api.BookChapter>>;
String _$absPlayerHash() => r'74a59dbf0f9396fef6bb60363fb186f5e4619a63';
String _$absPlayerHash() => r'e682fea03793a0370cb143602980d5c1e37396c7';
/// riverpod状态
///

View file

@ -90,32 +90,41 @@ class ChapterSelectionModal extends HookConsumerWidget {
final chapter = book.chapters[index];
final isCurrent = currentChapter.id == chapter.id;
final isPlayed = index < initialIndex;
return ListTile(
autofocus: isCurrent,
iconColor: isPlayed && !isCurrent ? theme.disabledColor : null,
title: Text(
chapter.title,
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
return Container(
// autofocus,autofocus出现在其他组件底层
decoration: isCurrent
? BoxDecoration(
color: Theme.of(context).focusColor, //
)
: null,
child: ListTile(
// autofocus: isCurrent,
iconColor:
isPlayed && !isCurrent ? theme.disabledColor : null,
title: Text(
chapter.title,
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
),
subtitle: Text(
'(${chapter.duration.smartBinaryFormat})',
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
),
trailing: isCurrent
? const PlayingIndicatorIcon()
: const Icon(Icons.play_arrow),
selected: isCurrent,
onTap: () {
if (back) {
Navigator.of(context).pop();
} else {
ref.read(absPlayerProvider).switchChapter(chapter.id);
}
},
),
subtitle: Text(
'(${chapter.duration.smartBinaryFormat})',
style: isPlayed && !isCurrent
? TextStyle(color: theme.disabledColor)
: null,
),
trailing: isCurrent
? const PlayingIndicatorIcon()
: const Icon(Icons.play_arrow),
selected: isCurrent,
onTap: () {
if (back) {
Navigator.of(context).pop();
} else {
ref.read(absPlayerProvider).switchChapter(chapter.id);
}
},
);
},
),