2025-12-20 11:54:14 +08:00
|
|
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
2025-04-19 19:17:31 +05:30
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart' show Ref;
|
|
|
|
|
import 'package:logging/logging.dart' show Logger;
|
|
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
2025-11-28 17:05:35 +08:00
|
|
|
import 'package:shelfsdk/audiobookshelf_api.dart'
|
2025-12-20 11:54:14 +08:00
|
|
|
show GetLibrarysItemsReqParams, Library, LibraryItem;
|
2025-04-19 19:17:31 +05:30
|
|
|
import 'package:vaani/api/api_provider.dart' show authenticatedApiProvider;
|
2025-11-28 17:05:35 +08:00
|
|
|
import 'package:vaani/features/settings/api_settings_provider.dart'
|
2025-04-19 19:17:31 +05:30
|
|
|
show apiSettingsProvider;
|
2025-11-28 17:05:35 +08:00
|
|
|
|
2025-04-19 19:17:31 +05:30
|
|
|
part 'library_provider.g.dart';
|
|
|
|
|
|
|
|
|
|
final _logger = Logger('LibraryProvider');
|
|
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
|
Future<Library?> library(Ref ref, String id) async {
|
|
|
|
|
final api = ref.watch(authenticatedApiProvider);
|
|
|
|
|
final library = await api.libraries.get(libraryId: id);
|
|
|
|
|
if (library == null) {
|
|
|
|
|
_logger.warning('No library found through id: $id');
|
|
|
|
|
// try to get the library from the list of libraries
|
|
|
|
|
final libraries = await ref.watch(librariesProvider.future);
|
|
|
|
|
for (final lib in libraries) {
|
|
|
|
|
if (lib.id == id) {
|
|
|
|
|
return lib;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_logger.warning('No library found in the list of libraries');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-04-23 00:29:02 +05:30
|
|
|
_logger.fine('Fetched library: $library');
|
2025-04-19 19:17:31 +05:30
|
|
|
return library.library;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
|
Future<Library?> currentLibrary(Ref ref) async {
|
|
|
|
|
final libraryId =
|
|
|
|
|
ref.watch(apiSettingsProvider.select((s) => s.activeLibraryId));
|
|
|
|
|
if (libraryId == null) {
|
|
|
|
|
_logger.warning('No active library id found');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return await ref.watch(libraryProvider(libraryId).future);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
|
class Libraries extends _$Libraries {
|
|
|
|
|
@override
|
|
|
|
|
FutureOr<List<Library>> build() async {
|
|
|
|
|
final api = ref.watch(authenticatedApiProvider);
|
|
|
|
|
final libraries = await api.libraries.getAll();
|
|
|
|
|
if (libraries == null) {
|
|
|
|
|
_logger.warning('Failed to fetch libraries');
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
_logger.fine('Fetched ${libraries.length} libraries');
|
|
|
|
|
ref.keepAlive();
|
|
|
|
|
return libraries;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-28 17:05:35 +08:00
|
|
|
|
2025-12-20 11:54:14 +08:00
|
|
|
class LibraryItemsState {
|
|
|
|
|
final List<LibraryItem> items;
|
|
|
|
|
final int limit;
|
|
|
|
|
final int page;
|
|
|
|
|
final String? sort;
|
|
|
|
|
final bool? desc;
|
|
|
|
|
final bool isLoading;
|
|
|
|
|
final bool isRefreshing;
|
|
|
|
|
final bool hasMore;
|
|
|
|
|
final bool hasError;
|
|
|
|
|
final String? errorMessage;
|
|
|
|
|
|
|
|
|
|
const LibraryItemsState({
|
|
|
|
|
this.items = const [],
|
|
|
|
|
this.limit = 18,
|
|
|
|
|
this.page = 0,
|
|
|
|
|
this.sort,
|
|
|
|
|
this.desc,
|
|
|
|
|
this.isLoading = false,
|
|
|
|
|
this.isRefreshing = false,
|
|
|
|
|
this.hasMore = false,
|
|
|
|
|
this.hasError = false,
|
|
|
|
|
this.errorMessage,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
LibraryItemsState copyWith({
|
|
|
|
|
List<LibraryItem>? items,
|
|
|
|
|
int? limit,
|
|
|
|
|
int? page,
|
|
|
|
|
String? sort,
|
|
|
|
|
bool? desc,
|
|
|
|
|
bool? isLoading,
|
|
|
|
|
bool? isRefreshing,
|
|
|
|
|
bool? hasMore,
|
|
|
|
|
bool? hasError,
|
|
|
|
|
String? errorMessage,
|
|
|
|
|
}) {
|
|
|
|
|
return LibraryItemsState(
|
|
|
|
|
items: items ?? this.items,
|
|
|
|
|
limit: limit ?? this.limit,
|
|
|
|
|
page: page ?? this.page,
|
|
|
|
|
sort: sort ?? this.sort,
|
|
|
|
|
desc: desc ?? this.desc,
|
|
|
|
|
isLoading: isLoading ?? this.isLoading,
|
|
|
|
|
isRefreshing: isRefreshing ?? this.isRefreshing,
|
|
|
|
|
hasMore: hasMore ?? this.hasMore,
|
|
|
|
|
hasError: hasError ?? this.hasError,
|
|
|
|
|
errorMessage: errorMessage ?? this.errorMessage,
|
2025-12-05 17:59:13 +08:00
|
|
|
);
|
|
|
|
|
}
|
2025-12-20 11:54:14 +08:00
|
|
|
|
|
|
|
|
factory LibraryItemsState.initial() => const LibraryItemsState();
|
2025-12-05 17:59:13 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 17:05:35 +08:00
|
|
|
@riverpod
|
2025-12-20 11:54:14 +08:00
|
|
|
class LibraryItems extends _$LibraryItems {
|
|
|
|
|
@override
|
|
|
|
|
LibraryItemsState build() {
|
|
|
|
|
// 初始加载数据
|
|
|
|
|
Future.microtask(_loadInitialData);
|
|
|
|
|
return LibraryItemsState.initial();
|
2025-11-28 17:05:35 +08:00
|
|
|
}
|
2025-12-20 11:54:14 +08:00
|
|
|
|
|
|
|
|
// 下拉刷新
|
|
|
|
|
Future<void> refresh() async {
|
|
|
|
|
if (state.isRefreshing) return;
|
|
|
|
|
|
|
|
|
|
// 开始刷新
|
|
|
|
|
state = state.copyWith(
|
2025-12-05 17:59:13 +08:00
|
|
|
page: 0,
|
2025-12-20 11:54:14 +08:00
|
|
|
isRefreshing: true,
|
|
|
|
|
hasError: false,
|
|
|
|
|
errorMessage: null,
|
|
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
final items = await _load();
|
|
|
|
|
state = state.copyWith(
|
|
|
|
|
items: [...items],
|
|
|
|
|
page: state.page + 1,
|
|
|
|
|
isRefreshing: false,
|
|
|
|
|
hasMore: items.length == state.limit,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
state = state.copyWith(
|
|
|
|
|
isRefreshing: false,
|
|
|
|
|
hasError: true,
|
|
|
|
|
errorMessage: e.toString(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始加载数据
|
|
|
|
|
Future<void> _loadInitialData() async {
|
|
|
|
|
// await _loadMore(skip: true);
|
|
|
|
|
await _loadMore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上拉加载更多
|
|
|
|
|
Future<void> loadMore() async {
|
|
|
|
|
if (state.isLoading || !state.hasMore) return;
|
|
|
|
|
await _loadMore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 内部加载方法
|
|
|
|
|
Future<void> _loadMore({bool skip = false}) async {
|
|
|
|
|
if (!skip) {
|
|
|
|
|
state = state.copyWith(
|
|
|
|
|
isLoading: true,
|
|
|
|
|
hasError: false,
|
|
|
|
|
errorMessage: null,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
final items = await _load();
|
|
|
|
|
state = state.copyWith(
|
|
|
|
|
items: [...state.items, ...items],
|
|
|
|
|
page: state.page + 1,
|
|
|
|
|
isLoading: false,
|
|
|
|
|
hasMore: items.length == state.limit,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
state = state.copyWith(
|
|
|
|
|
isLoading: false,
|
|
|
|
|
hasError: true,
|
|
|
|
|
errorMessage: e.toString(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载方法
|
|
|
|
|
Future<List<LibraryItem>> _load() async {
|
|
|
|
|
final api = ref.read(authenticatedApiProvider);
|
|
|
|
|
final libraryId =
|
|
|
|
|
ref.watch(apiSettingsProvider.select((s) => s.activeLibraryId));
|
|
|
|
|
if (libraryId != null) {
|
|
|
|
|
final newItems = await api.libraries.getItems(
|
|
|
|
|
libraryId: libraryId,
|
|
|
|
|
parameters: GetLibrarysItemsReqParams(
|
|
|
|
|
limit: state.limit,
|
|
|
|
|
page: state.page,
|
|
|
|
|
sort: state.sort,
|
|
|
|
|
desc: state.desc,
|
|
|
|
|
minified: true,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return newItems?.results ?? [];
|
|
|
|
|
}
|
2025-11-28 17:05:35 +08:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-20 11:54:14 +08:00
|
|
|
|
|
|
|
|
// 查询库下所有项目
|
|
|
|
|
// @riverpod
|
|
|
|
|
// Future<List<LibraryItem>> currentLibraryItems(Ref ref) async {
|
|
|
|
|
// final api = ref.watch(authenticatedApiProvider);
|
|
|
|
|
// final libraryId =
|
|
|
|
|
// ref.watch(apiSettingsProvider.select((s) => s.activeLibraryId));
|
|
|
|
|
// if (libraryId == null) {
|
|
|
|
|
// _logger.warning('No active library id found');
|
|
|
|
|
// return [];
|
|
|
|
|
// }
|
|
|
|
|
// final items = await api.libraries.getItems(
|
|
|
|
|
// libraryId: libraryId,
|
|
|
|
|
// parameters: const GetLibrarysItemsReqParams(
|
|
|
|
|
// limit: 18,
|
|
|
|
|
// page: 0,
|
|
|
|
|
// minified: true,
|
|
|
|
|
// ),
|
|
|
|
|
// );
|
|
|
|
|
// if (items == null) {
|
|
|
|
|
// return [];
|
|
|
|
|
// }
|
|
|
|
|
// return items.results;
|
|
|
|
|
// }
|