mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-15 13:59:34 +00:00
媒体库上拉刷新,下拉加载
This commit is contained in:
parent
bdd85efcd8
commit
ebcbe1774a
20 changed files with 351 additions and 126 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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状态
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -163,6 +163,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"delete": MessageLookupByLibrary.simpleMessage("Delete"),
|
||||
"deleteDialog": m2,
|
||||
"deleted": m3,
|
||||
"erArmedText": MessageLookupByLibrary.simpleMessage("Release ready"),
|
||||
"erDragText": MessageLookupByLibrary.simpleMessage("Pull to refresh"),
|
||||
"erDragTextUp": MessageLookupByLibrary.simpleMessage("Pull to refresh"),
|
||||
"erFailedText": MessageLookupByLibrary.simpleMessage("Failed"),
|
||||
"erMessageText":
|
||||
MessageLookupByLibrary.simpleMessage("Last updated at %T"),
|
||||
"erNoMoreText": MessageLookupByLibrary.simpleMessage("No more"),
|
||||
"erProcessedText": MessageLookupByLibrary.simpleMessage("Succeeded"),
|
||||
"erProcessingText":
|
||||
MessageLookupByLibrary.simpleMessage("Refreshing..."),
|
||||
"erReadyText": MessageLookupByLibrary.simpleMessage("Refreshing..."),
|
||||
"explore": MessageLookupByLibrary.simpleMessage("explore"),
|
||||
"exploreHint": MessageLookupByLibrary.simpleMessage(
|
||||
"Seek and you shall discover...",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
|
||||
static String m1(user) => "用户数: ${user}";
|
||||
|
||||
static String m2(item) => "确定要删除 ${item} 吗?";
|
||||
static String m2(item) => "是否要删除 ${item} ?";
|
||||
|
||||
static String m3(item) => "已删除 ${item}";
|
||||
|
||||
|
|
@ -129,6 +129,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||
"delete": MessageLookupByLibrary.simpleMessage("删除"),
|
||||
"deleteDialog": m2,
|
||||
"deleted": m3,
|
||||
"erArmedText": MessageLookupByLibrary.simpleMessage("准备就绪"),
|
||||
"erDragText": MessageLookupByLibrary.simpleMessage("下拉刷新"),
|
||||
"erDragTextUp": MessageLookupByLibrary.simpleMessage("上拉加载"),
|
||||
"erFailedText": MessageLookupByLibrary.simpleMessage("失败"),
|
||||
"erMessageText": MessageLookupByLibrary.simpleMessage("最后更新于 %T"),
|
||||
"erNoMoreText": MessageLookupByLibrary.simpleMessage("没有更多"),
|
||||
"erProcessedText": MessageLookupByLibrary.simpleMessage("成功"),
|
||||
"erProcessingText": MessageLookupByLibrary.simpleMessage("刷新..."),
|
||||
"erReadyText": MessageLookupByLibrary.simpleMessage("刷新..."),
|
||||
"explore": MessageLookupByLibrary.simpleMessage("探索"),
|
||||
"exploreHint": MessageLookupByLibrary.simpleMessage("搜索与探索..."),
|
||||
"exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"),
|
||||
|
|
|
|||
|
|
@ -1894,6 +1894,86 @@ class S {
|
|||
);
|
||||
}
|
||||
|
||||
/// `Pull to refresh`
|
||||
String get erDragText {
|
||||
return Intl.message(
|
||||
'Pull to refresh',
|
||||
name: 'erDragText',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Pull to refresh`
|
||||
String get erDragTextUp {
|
||||
return Intl.message(
|
||||
'Pull to refresh',
|
||||
name: 'erDragTextUp',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Release ready`
|
||||
String get erArmedText {
|
||||
return Intl.message(
|
||||
'Release ready',
|
||||
name: 'erArmedText',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Refreshing...`
|
||||
String get erReadyText {
|
||||
return Intl.message(
|
||||
'Refreshing...',
|
||||
name: 'erReadyText',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Refreshing...`
|
||||
String get erProcessingText {
|
||||
return Intl.message(
|
||||
'Refreshing...',
|
||||
name: 'erProcessingText',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Succeeded`
|
||||
String get erProcessedText {
|
||||
return Intl.message(
|
||||
'Succeeded',
|
||||
name: 'erProcessedText',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `No more`
|
||||
String get erNoMoreText {
|
||||
return Intl.message('No more', name: 'erNoMoreText', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Failed`
|
||||
String get erFailedText {
|
||||
return Intl.message('Failed', name: 'erFailedText', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Last updated at %T`
|
||||
String get erMessageText {
|
||||
return Intl.message(
|
||||
'Last updated at %T',
|
||||
name: 'erMessageText',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Logs`
|
||||
String get logs {
|
||||
return Intl.message('Logs', name: 'logs', desc: '', args: []);
|
||||
|
|
|
|||
|
|
@ -272,6 +272,16 @@
|
|||
"resetAppSettingsDescription": "Reset the app settings to the default values",
|
||||
"resetAppSettingsDialog": "Are you sure you want to reset the app settings?",
|
||||
|
||||
"erDragText": "Pull to refresh",
|
||||
"erDragTextUp": "Pull to refresh",
|
||||
"erArmedText": "Release ready",
|
||||
"erReadyText": "Refreshing...",
|
||||
"erProcessingText": "Refreshing...",
|
||||
"erProcessedText": "Succeeded",
|
||||
"erNoMoreText": "No more",
|
||||
"erFailedText": "Failed",
|
||||
"erMessageText": "Last updated at %T",
|
||||
|
||||
"logs": "Logs",
|
||||
"notImplemented": "Not implemented"
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"reset": "重置",
|
||||
"retry": "重试",
|
||||
"delete": "删除",
|
||||
"deleteDialog": "确定要删除 {item} 吗?",
|
||||
"deleteDialog": "是否要删除 {item} ?",
|
||||
"@deleteDialog": {
|
||||
"placeholders": {
|
||||
"item": {
|
||||
|
|
@ -272,6 +272,15 @@
|
|||
"resetAppSettingsDescription": "将应用程序设置重置为默认值",
|
||||
"resetAppSettingsDialog": "您确定要重置应用程序设置吗?",
|
||||
|
||||
"erDragText": "下拉刷新",
|
||||
"erDragTextUp": "上拉加载",
|
||||
"erArmedText": "准备就绪",
|
||||
"erReadyText": "刷新...",
|
||||
"erProcessingText": "刷新...",
|
||||
"erProcessedText": "成功",
|
||||
"erNoMoreText": "没有更多",
|
||||
"erFailedText": "失败",
|
||||
"erMessageText": "最后更新于 %T",
|
||||
"logs": "日志",
|
||||
"notImplemented": "未实现"
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
|
|
@ -16,6 +17,7 @@ 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/utils/components.dart';
|
||||
import 'package:vaani/shared/widgets/skeletons.dart';
|
||||
|
||||
// TODO: implement the library page
|
||||
|
|
@ -70,6 +72,11 @@ class LibraryPage extends HookConsumerWidget {
|
|||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.next_plan),
|
||||
tooltip: '加载下一页', // Helpful tooltip for users
|
||||
onPressed: () => ref.read(libraryItemsProvider.notifier).loadMore(),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
tooltip: '刷新', // Helpful tooltip for users
|
||||
|
|
@ -85,8 +92,12 @@ class LibraryPage extends HookConsumerWidget {
|
|||
],
|
||||
),
|
||||
// drawer: const MyDrawer(),
|
||||
body: RefreshIndicator(
|
||||
|
||||
body: EasyRefresh(
|
||||
header: Components.easyRefreshHeader(context),
|
||||
footer: Components.easyRefreshFooter(context),
|
||||
onRefresh: () => ref.read(libraryItemsProvider.notifier).refresh(),
|
||||
onLoad: () => ref.read(libraryItemsProvider.notifier).loadMore(),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final height = getDefaultHeight(context);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,20 @@ extension UserConversion on User {
|
|||
User get asUser => User.fromJson(toJson());
|
||||
}
|
||||
|
||||
extension ContentUrlExtension on AudioFile {
|
||||
Uri url(String baseUrl, String itemId, String token) {
|
||||
// /api/items/{itemId}/file/{ino}?{token}
|
||||
// return Uri.parse('$baseUrl/api/items/$itemId/file/$ino?token=$token');
|
||||
var baseUri = Uri.parse(baseUrl);
|
||||
return Uri(
|
||||
scheme: baseUri.scheme,
|
||||
host: baseUri.host,
|
||||
path: '/api/items/$itemId/file/$ino',
|
||||
queryParameters: {'token': token},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentUrl on LibraryFile {
|
||||
Uri url(String baseUrl, String itemId, String token) {
|
||||
// /api/items/{itemId}/file/{ino}?{token}
|
||||
|
|
|
|||
33
lib/shared/utils/components.dart
Normal file
33
lib/shared/utils/components.dart
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:vaani/generated/l10n.dart';
|
||||
|
||||
class Components {
|
||||
Components._();
|
||||
static ClassicHeader easyRefreshHeader(BuildContext context) {
|
||||
return ClassicHeader(
|
||||
dragText: S.of(context).erDragText,
|
||||
armedText: S.of(context).erArmedText,
|
||||
readyText: S.of(context).erReadyText,
|
||||
processingText: S.of(context).erProcessingText,
|
||||
processedText: S.of(context).erProcessedText,
|
||||
noMoreText: S.of(context).erNoMoreText,
|
||||
failedText: S.of(context).erFailedText,
|
||||
messageText: S.of(context).erMessageText,
|
||||
);
|
||||
}
|
||||
|
||||
static ClassicFooter easyRefreshFooter(BuildContext context) {
|
||||
return ClassicFooter(
|
||||
dragText: S.of(context).erDragTextUp,
|
||||
armedText: S.of(context).erArmedText,
|
||||
readyText: S.of(context).erReadyText,
|
||||
processingText: S.of(context).erProcessingText,
|
||||
processedText: S.of(context).erProcessedText,
|
||||
noMoreText: S.of(context).erNoMoreText,
|
||||
failedText: S.of(context).erFailedText,
|
||||
messageText: S.of(context).erMessageText,
|
||||
infiniteOffset: 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
38
lib/shared/utils/custom_dialog.dart
Normal file
38
lib/shared/utils/custom_dialog.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:vaani/generated/l10n.dart';
|
||||
|
||||
class DialogUtils {
|
||||
DialogUtils._();
|
||||
|
||||
// 自定义删除 dialog
|
||||
static deleteDialog(
|
||||
BuildContext context, {
|
||||
String? name,
|
||||
required Function() onPressed,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(S.of(context).delete),
|
||||
content: Text(S.of(context).deleteDialog(name ?? '')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onPressed();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(S.of(context).yes),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(S.of(context).no),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:vaani/api/image_provider.dart';
|
||||
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||
import 'package:vaani/shared/widgets/shelves/home_shelf.dart';
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ class AuthorOnShelf extends HookConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final author = item.asMinified;
|
||||
// final coverImage = ref.watch(coverImageProvider(item));
|
||||
final coverImage = ref.watch(coverImageProvider(item.id));
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 10, bottom: 10),
|
||||
|
|
@ -53,17 +54,17 @@ class AuthorOnShelf extends HookConsumerWidget {
|
|||
aspectRatio: 1,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 50),
|
||||
// child: coverImage.when(
|
||||
// data: (image) {
|
||||
// return Image.memory(image, fit: BoxFit.cover);
|
||||
// },
|
||||
// loading: () {
|
||||
// return const Center(child: CircularProgressIndicator());
|
||||
// },
|
||||
// error: (error, stack) {
|
||||
// return const Icon(Icons.error);
|
||||
// },
|
||||
// ),
|
||||
child: coverImage.when(
|
||||
data: (image) {
|
||||
return Image.memory(image, fit: BoxFit.cover);
|
||||
},
|
||||
loading: () {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
error: (error, stack) {
|
||||
return const Icon(Icons.error);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -213,12 +213,12 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final me = ref.watch(meProvider);
|
||||
final currentBook = ref.watch(currentBookProvider);
|
||||
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(libraryItemId);
|
||||
final isCurrentBookSetInPlayer =
|
||||
currentBook?.libraryItemId == libraryItemId;
|
||||
final isPlayingThisBook =
|
||||
playerStateNotifier.isPlaying() && isCurrentBookSetInPlayer;
|
||||
final isPlayingThisBook = playing && isCurrentBookSetInPlayer;
|
||||
|
||||
final userProgress = me.valueOrNull?.mediaProgress
|
||||
?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId);
|
||||
|
|
|
|||
16
pubspec.lock
16
pubspec.lock
|
|
@ -398,6 +398,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
easy_refresh:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_refresh
|
||||
sha256: "486e30abfcaae66c0f2c2798a10de2298eb9dc5e0bb7e1dba9328308968cae0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -954,6 +962,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_drawing
|
||||
sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ dependencies:
|
|||
# flutter_platform_widgets: ^9.0.0
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
super_sliver_list: ^0.4.1
|
||||
easy_refresh: ^3.4.0
|
||||
duration_picker: ^1.2.0
|
||||
dynamic_color: ^1.7.0
|
||||
# easy_stepper: ^0.8.4
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue