mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 15:02:42 +01:00
v2
This commit is contained in:
149
lib/app/hooks/use_download_actions.dart
Normal file
149
lib/app/hooks/use_download_actions.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../models/music.dart';
|
||||
import '../../models/support.dart';
|
||||
import '../../services/download_service.dart';
|
||||
import '../../state/music.dart';
|
||||
import '../../state/settings.dart';
|
||||
import '../dialogs.dart';
|
||||
|
||||
enum DownloadActionType {
|
||||
download,
|
||||
cancel,
|
||||
delete,
|
||||
}
|
||||
|
||||
class DownloadAction {
|
||||
final DownloadActionType type;
|
||||
final WidgetBuilder iconBuilder;
|
||||
final FutureOr<void> Function()? action;
|
||||
|
||||
const DownloadAction({
|
||||
required this.type,
|
||||
required this.iconBuilder,
|
||||
this.action,
|
||||
});
|
||||
}
|
||||
|
||||
List<DownloadAction> useAlbumDownloadActions({
|
||||
required BuildContext context,
|
||||
required WidgetRef ref,
|
||||
required Album album,
|
||||
}) {
|
||||
final status = ref.watch(albumDownloadStatusProvider(album.id)).valueOrNull;
|
||||
|
||||
return useListDownloadActions(
|
||||
context: context,
|
||||
ref: ref,
|
||||
list: album,
|
||||
status: status,
|
||||
onDownload: () =>
|
||||
ref.read(downloadServiceProvider.notifier).downloadAlbum(album),
|
||||
onDelete: () =>
|
||||
ref.read(downloadServiceProvider.notifier).deleteAlbum(album),
|
||||
onCancel: () =>
|
||||
ref.read(downloadServiceProvider.notifier).cancelAlbum(album),
|
||||
);
|
||||
}
|
||||
|
||||
List<DownloadAction> usePlaylistDownloadActions({
|
||||
required BuildContext context,
|
||||
required WidgetRef ref,
|
||||
required Playlist playlist,
|
||||
}) {
|
||||
final status =
|
||||
ref.watch(playlistDownloadStatusProvider(playlist.id)).valueOrNull;
|
||||
|
||||
return useListDownloadActions(
|
||||
context: context,
|
||||
ref: ref,
|
||||
list: playlist,
|
||||
status: status,
|
||||
onDownload: () =>
|
||||
ref.read(downloadServiceProvider.notifier).downloadPlaylist(playlist),
|
||||
onDelete: () =>
|
||||
ref.read(downloadServiceProvider.notifier).deletePlaylist(playlist),
|
||||
onCancel: () =>
|
||||
ref.read(downloadServiceProvider.notifier).cancelPlaylist(playlist),
|
||||
);
|
||||
}
|
||||
|
||||
List<DownloadAction> useListDownloadActions({
|
||||
required BuildContext context,
|
||||
required WidgetRef ref,
|
||||
required SourceIdentifiable list,
|
||||
required ListDownloadStatus? status,
|
||||
required FutureOr<void> Function() onDelete,
|
||||
required FutureOr<void> Function() onCancel,
|
||||
required FutureOr<void> Function() onDownload,
|
||||
}) {
|
||||
status ??= const ListDownloadStatus(total: 0, downloaded: 0, downloading: 0);
|
||||
|
||||
final sourceId = SourceId.from(list);
|
||||
final offline = ref.watch(offlineModeProvider);
|
||||
final listDownloadInProgress = ref.watch(downloadServiceProvider
|
||||
.select((value) => value.listDownloads.contains(sourceId)));
|
||||
final listDeleteInProgress = ref.watch(downloadServiceProvider
|
||||
.select((value) => value.deletes.contains(sourceId)));
|
||||
final listCancelInProgress = ref.watch(downloadServiceProvider
|
||||
.select((value) => value.listCancels.contains(sourceId)));
|
||||
|
||||
DownloadAction delete() {
|
||||
return DownloadAction(
|
||||
type: DownloadActionType.delete,
|
||||
iconBuilder: (context) => const Icon(Icons.delete_forever_rounded),
|
||||
action: listDeleteInProgress
|
||||
? null
|
||||
: () async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => const DeleteDialog(),
|
||||
);
|
||||
if (ok == true) {
|
||||
await onDelete();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
DownloadAction cancel() {
|
||||
return DownloadAction(
|
||||
type: DownloadActionType.cancel,
|
||||
iconBuilder: (context) => Stack(
|
||||
alignment: Alignment.center,
|
||||
children: const [
|
||||
Icon(Icons.cancel_rounded),
|
||||
SizedBox(
|
||||
height: 32,
|
||||
width: 32,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
action: listCancelInProgress ? null : onCancel,
|
||||
);
|
||||
}
|
||||
|
||||
DownloadAction download() {
|
||||
return DownloadAction(
|
||||
type: DownloadActionType.download,
|
||||
iconBuilder: (context) => const Icon(Icons.download_rounded),
|
||||
action: !offline ? onDownload : null,
|
||||
);
|
||||
}
|
||||
|
||||
if (status.total == status.downloaded) {
|
||||
return [delete()];
|
||||
} else if (status.downloading == 0 && status.downloaded > 0) {
|
||||
return [download(), delete()];
|
||||
} else if (listDownloadInProgress || status.downloading > 0) {
|
||||
return [cancel()];
|
||||
} else {
|
||||
return [download()];
|
||||
}
|
||||
}
|
||||
91
lib/app/hooks/use_list_query_paging_controller.dart
Normal file
91
lib/app/hooks/use_list_query_paging_controller.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
import '../../models/query.dart';
|
||||
import '../../services/sync_service.dart';
|
||||
import '../../state/settings.dart';
|
||||
import '../pages/library_page.dart';
|
||||
import 'use_paging_controller.dart';
|
||||
|
||||
PagingController<int, T> useLibraryPagingController<T>(
|
||||
WidgetRef ref, {
|
||||
required int libraryTabIndex,
|
||||
required FutureOr<List<T>> Function(ListQuery query) getItems,
|
||||
}) {
|
||||
final queryProvider = libraryListQueryProvider(libraryTabIndex).select(
|
||||
(value) => value.query,
|
||||
);
|
||||
final query = useState(ref.read(queryProvider));
|
||||
|
||||
final onPageRequest = useCallback(
|
||||
(int pageKey, PagingController<int, T> pagingController) =>
|
||||
_pageRequest(getItems, query.value, pageKey, pagingController),
|
||||
[query.value],
|
||||
);
|
||||
|
||||
final pagingController = usePagingController<int, T>(
|
||||
firstPageKey: query.value.page.offset,
|
||||
onPageRequest: onPageRequest,
|
||||
);
|
||||
|
||||
ref.listen(queryProvider, (_, next) {
|
||||
query.value = next;
|
||||
pagingController.refresh();
|
||||
});
|
||||
|
||||
ref.listen(syncServiceProvider, (_, __) => pagingController.refresh());
|
||||
ref.listen(sourceIdProvider, (_, __) => pagingController.refresh());
|
||||
ref.listen(offlineModeProvider, (_, __) => pagingController.refresh());
|
||||
|
||||
return pagingController;
|
||||
}
|
||||
|
||||
PagingController<int, T> useListQueryPagingController<T>(
|
||||
WidgetRef ref, {
|
||||
required ListQuery query,
|
||||
required FutureOr<List<T>> Function(ListQuery query) getItems,
|
||||
}) {
|
||||
final onPageRequest = useCallback(
|
||||
(int pageKey, PagingController<int, T> pagingController) =>
|
||||
_pageRequest(getItems, query, pageKey, pagingController),
|
||||
[query],
|
||||
);
|
||||
|
||||
final pagingController = usePagingController<int, T>(
|
||||
firstPageKey: query.page.offset,
|
||||
onPageRequest: onPageRequest,
|
||||
);
|
||||
|
||||
return pagingController;
|
||||
}
|
||||
|
||||
Future<void> _pageRequest<T>(
|
||||
FutureOr<List<T>> Function(ListQuery query) getItems,
|
||||
ListQuery query,
|
||||
int pageKey,
|
||||
PagingController<int, dynamic> pagingController,
|
||||
) async {
|
||||
try {
|
||||
final newItems = await getItems(query.copyWith.page(offset: pageKey));
|
||||
|
||||
final isFirstPage = newItems.isNotEmpty && pageKey == 0;
|
||||
final alreadyHasItems = pagingController.itemList != null &&
|
||||
pagingController.itemList!.isNotEmpty;
|
||||
if (isFirstPage && alreadyHasItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
final isLastPage = newItems.length < query.page.limit;
|
||||
if (isLastPage) {
|
||||
pagingController.appendLastPage(newItems);
|
||||
} else {
|
||||
final nextPageKey = pageKey + newItems.length;
|
||||
pagingController.appendPage(newItems, nextPageKey);
|
||||
}
|
||||
} catch (error) {
|
||||
pagingController.error = error;
|
||||
}
|
||||
}
|
||||
66
lib/app/hooks/use_paging_controller.dart
Normal file
66
lib/app/hooks/use_paging_controller.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
PagingController<PageKeyType, ItemType>
|
||||
usePagingController<PageKeyType, ItemType>({
|
||||
required final PageKeyType firstPageKey,
|
||||
final int? invisibleItemsThreshold,
|
||||
List<Object?>? keys,
|
||||
FutureOr<void> Function(PageKeyType pageKey,
|
||||
PagingController<PageKeyType, ItemType> pagingController)?
|
||||
onPageRequest,
|
||||
}) {
|
||||
final controller = use(
|
||||
_PagingControllerHook<PageKeyType, ItemType>(
|
||||
firstPageKey: firstPageKey,
|
||||
invisibleItemsThreshold: invisibleItemsThreshold,
|
||||
keys: keys,
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
listener(PageKeyType pageKey) => onPageRequest?.call(pageKey, controller);
|
||||
controller.addPageRequestListener(listener);
|
||||
return () => controller.removePageRequestListener(listener);
|
||||
}, [onPageRequest]);
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
class _PagingControllerHook<PageKeyType, ItemType>
|
||||
extends Hook<PagingController<PageKeyType, ItemType>> {
|
||||
const _PagingControllerHook({
|
||||
required this.firstPageKey,
|
||||
this.invisibleItemsThreshold,
|
||||
List<Object?>? keys,
|
||||
}) : super(keys: keys);
|
||||
|
||||
final PageKeyType firstPageKey;
|
||||
final int? invisibleItemsThreshold;
|
||||
|
||||
@override
|
||||
HookState<PagingController<PageKeyType, ItemType>,
|
||||
Hook<PagingController<PageKeyType, ItemType>>>
|
||||
createState() => _PagingControllerHookState<PageKeyType, ItemType>();
|
||||
}
|
||||
|
||||
class _PagingControllerHookState<PageKeyType, ItemType> extends HookState<
|
||||
PagingController<PageKeyType, ItemType>,
|
||||
_PagingControllerHook<PageKeyType, ItemType>> {
|
||||
late final controller = PagingController<PageKeyType, ItemType>(
|
||||
firstPageKey: hook.firstPageKey,
|
||||
invisibleItemsThreshold: hook.invisibleItemsThreshold);
|
||||
|
||||
@override
|
||||
PagingController<PageKeyType, ItemType> build(BuildContext context) =>
|
||||
controller;
|
||||
|
||||
@override
|
||||
void dispose() => controller.dispose();
|
||||
|
||||
@override
|
||||
String get debugLabel => 'usePagingController';
|
||||
}
|
||||
Reference in New Issue
Block a user