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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -90,32 +90,41 @@ class ChapterSelectionModal extends HookConsumerWidget {
final chapter = book.chapters[index]; final chapter = book.chapters[index];
final isCurrent = currentChapter.id == chapter.id; final isCurrent = currentChapter.id == chapter.id;
final isPlayed = index < initialIndex; final isPlayed = index < initialIndex;
return ListTile( return Container(
autofocus: isCurrent, // autofocus,autofocus出现在其他组件底层
iconColor: isPlayed && !isCurrent ? theme.disabledColor : null, decoration: isCurrent
title: Text( ? BoxDecoration(
chapter.title, color: Theme.of(context).focusColor, //
style: isPlayed && !isCurrent )
? TextStyle(color: theme.disabledColor) : null,
: 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);
}
},
); );
}, },
), ),

View file

@ -163,6 +163,17 @@ class MessageLookup extends MessageLookupByLibrary {
"delete": MessageLookupByLibrary.simpleMessage("Delete"), "delete": MessageLookupByLibrary.simpleMessage("Delete"),
"deleteDialog": m2, "deleteDialog": m2,
"deleted": m3, "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"), "explore": MessageLookupByLibrary.simpleMessage("explore"),
"exploreHint": MessageLookupByLibrary.simpleMessage( "exploreHint": MessageLookupByLibrary.simpleMessage(
"Seek and you shall discover...", "Seek and you shall discover...",

View file

@ -24,7 +24,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(user) => "用户数: ${user}"; static String m1(user) => "用户数: ${user}";
static String m2(item) => "确定要删除 ${item} "; static String m2(item) => "是否要删除 ${item} ";
static String m3(item) => "已删除 ${item}"; static String m3(item) => "已删除 ${item}";
@ -129,6 +129,15 @@ class MessageLookup extends MessageLookupByLibrary {
"delete": MessageLookupByLibrary.simpleMessage("删除"), "delete": MessageLookupByLibrary.simpleMessage("删除"),
"deleteDialog": m2, "deleteDialog": m2,
"deleted": m3, "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("探索"), "explore": MessageLookupByLibrary.simpleMessage("探索"),
"exploreHint": MessageLookupByLibrary.simpleMessage("搜索与探索..."), "exploreHint": MessageLookupByLibrary.simpleMessage("搜索与探索..."),
"exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"), "exploreTooltip": MessageLookupByLibrary.simpleMessage("搜索和探索"),

View file

@ -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` /// `Logs`
String get logs { String get logs {
return Intl.message('Logs', name: 'logs', desc: '', args: []); return Intl.message('Logs', name: 'logs', desc: '', args: []);

View file

@ -272,6 +272,16 @@
"resetAppSettingsDescription": "Reset the app settings to the default values", "resetAppSettingsDescription": "Reset the app settings to the default values",
"resetAppSettingsDialog": "Are you sure you want to reset the app settings?", "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", "logs": "Logs",
"notImplemented": "Not implemented" "notImplemented": "Not implemented"
} }

View file

@ -9,7 +9,7 @@
"reset": "重置", "reset": "重置",
"retry": "重试", "retry": "重试",
"delete": "删除", "delete": "删除",
"deleteDialog": "确定要删除 {item} 吗", "deleteDialog": "是否要删除 {item} ",
"@deleteDialog": { "@deleteDialog": {
"placeholders": { "placeholders": {
"item": { "item": {
@ -272,6 +272,15 @@
"resetAppSettingsDescription": "将应用程序设置重置为默认值", "resetAppSettingsDescription": "将应用程序设置重置为默认值",
"resetAppSettingsDialog": "您确定要重置应用程序设置吗?", "resetAppSettingsDialog": "您确定要重置应用程序设置吗?",
"erDragText": "下拉刷新",
"erDragTextUp": "上拉加载",
"erArmedText": "准备就绪",
"erReadyText": "刷新...",
"erProcessingText": "刷新...",
"erProcessedText": "成功",
"erNoMoreText": "没有更多",
"erFailedText": "失败",
"erMessageText": "最后更新于 %T",
"logs": "日志", "logs": "日志",
"notImplemented": "未实现" "notImplemented": "未实现"
} }

View file

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.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/model_conversions.dart';
import 'package:vaani/shared/extensions/style.dart'; import 'package:vaani/shared/extensions/style.dart';
import 'package:vaani/shared/icons/abs_icons.dart'; import 'package:vaani/shared/icons/abs_icons.dart';
import 'package:vaani/shared/utils/components.dart';
import 'package:vaani/shared/widgets/skeletons.dart'; import 'package:vaani/shared/widgets/skeletons.dart';
// TODO: implement the library page // TODO: implement the library page
@ -70,6 +72,11 @@ class LibraryPage extends HookConsumerWidget {
}, },
), ),
actions: [ actions: [
IconButton(
icon: Icon(Icons.next_plan),
tooltip: '加载下一页', // Helpful tooltip for users
onPressed: () => ref.read(libraryItemsProvider.notifier).loadMore(),
),
IconButton( IconButton(
icon: Icon(Icons.refresh), icon: Icon(Icons.refresh),
tooltip: '刷新', // Helpful tooltip for users tooltip: '刷新', // Helpful tooltip for users
@ -85,8 +92,12 @@ class LibraryPage extends HookConsumerWidget {
], ],
), ),
// drawer: const MyDrawer(), // drawer: const MyDrawer(),
body: RefreshIndicator(
body: EasyRefresh(
header: Components.easyRefreshHeader(context),
footer: Components.easyRefreshFooter(context),
onRefresh: () => ref.read(libraryItemsProvider.notifier).refresh(), onRefresh: () => ref.read(libraryItemsProvider.notifier).refresh(),
onLoad: () => ref.read(libraryItemsProvider.notifier).loadMore(),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final height = getDefaultHeight(context); final height = getDefaultHeight(context);

View file

@ -52,6 +52,20 @@ extension UserConversion on User {
User get asUser => User.fromJson(toJson()); 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 { extension ContentUrl on LibraryFile {
Uri url(String baseUrl, String itemId, String token) { Uri url(String baseUrl, String itemId, String token) {
// /api/items/{itemId}/file/{ino}?{token} // /api/items/{itemId}/file/{ino}?{token}

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

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

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelfsdk/audiobookshelf_api.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/extensions/model_conversions.dart';
import 'package:vaani/shared/widgets/shelves/home_shelf.dart'; import 'package:vaani/shared/widgets/shelves/home_shelf.dart';
@ -40,7 +41,7 @@ class AuthorOnShelf extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final author = item.asMinified; final author = item.asMinified;
// final coverImage = ref.watch(coverImageProvider(item)); final coverImage = ref.watch(coverImageProvider(item.id));
return Container( return Container(
margin: const EdgeInsets.only(right: 10, bottom: 10), margin: const EdgeInsets.only(right: 10, bottom: 10),
@ -53,17 +54,17 @@ class AuthorOnShelf extends HookConsumerWidget {
aspectRatio: 1, aspectRatio: 1,
child: Container( child: Container(
constraints: const BoxConstraints(maxWidth: 50), constraints: const BoxConstraints(maxWidth: 50),
// child: coverImage.when( child: coverImage.when(
// data: (image) { data: (image) {
// return Image.memory(image, fit: BoxFit.cover); return Image.memory(image, fit: BoxFit.cover);
// }, },
// loading: () { loading: () {
// return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
// }, },
// error: (error, stack) { error: (error, stack) {
// return const Icon(Icons.error); return const Icon(Icons.error);
// }, },
// ), ),
), ),
), ),
), ),

View file

@ -213,12 +213,12 @@ class _BookOnShelfPlayButton extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final me = ref.watch(meProvider); final me = ref.watch(meProvider);
final currentBook = ref.watch(currentBookProvider); 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 isLoading = playerStateNotifier.isLoading(libraryItemId);
final isCurrentBookSetInPlayer = final isCurrentBookSetInPlayer =
currentBook?.libraryItemId == libraryItemId; currentBook?.libraryItemId == libraryItemId;
final isPlayingThisBook = final isPlayingThisBook = playing && isCurrentBookSetInPlayer;
playerStateNotifier.isPlaying() && isCurrentBookSetInPlayer;
final userProgress = me.valueOrNull?.mediaProgress final userProgress = me.valueOrNull?.mediaProgress
?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId); ?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId);

View file

@ -398,6 +398,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.1" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -954,6 +962,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" 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: path_parsing:
dependency: transitive dependency: transitive
description: description:

View file

@ -49,6 +49,7 @@ dependencies:
# flutter_platform_widgets: ^9.0.0 # flutter_platform_widgets: ^9.0.0
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
super_sliver_list: ^0.4.1 super_sliver_list: ^0.4.1
easy_refresh: ^3.4.0
duration_picker: ^1.2.0 duration_picker: ^1.2.0
dynamic_color: ^1.7.0 dynamic_color: ^1.7.0
# easy_stepper: ^0.8.4 # easy_stepper: ^0.8.4