From c5e71bef7abc4a65872ee16060b61b5cd4b6f556 Mon Sep 17 00:00:00 2001 From: Jonathan <57756132+jdk-21@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:46:25 +0200 Subject: [PATCH] chore: genre banner caching --- lib/components/genre_banner.dart | 234 +++++----- lib/models/baseitemdto_adapter.dart | 24 + lib/providers/pagestorage_provider.dart | 4 + lib/screens/home_screen.dart | 564 ++++++++++++------------ lib/services/database_service.dart | 3 + macos/Runner/AppDelegate.swift | 3 + 6 files changed, 458 insertions(+), 374 deletions(-) create mode 100644 lib/models/baseitemdto_adapter.dart create mode 100644 lib/providers/pagestorage_provider.dart diff --git a/lib/components/genre_banner.dart b/lib/components/genre_banner.dart index 354ae09..b3ababc 100644 --- a/lib/components/genre_banner.dart +++ b/lib/components/genre_banner.dart @@ -9,127 +9,159 @@ import 'package:jellyflix/components/jfx_text_theme.dart'; import 'package:jellyflix/models/gradients.dart'; import 'package:jellyflix/models/screen_paths.dart'; import 'package:jellyflix/providers/api_provider.dart'; +import 'package:jellyflix/providers/database_provider.dart'; import 'package:tentacle/tentacle.dart'; -class GenreBanner extends HookConsumerWidget { - const GenreBanner({ +class CachedGenreBanner extends HookConsumerWidget { + const CachedGenreBanner({ super.key, }); @override Widget build(BuildContext context, WidgetRef ref) { final pageController = usePageController(viewportFraction: 0.95); + final List? queryData = + ref.read(databaseProvider("queryCache")).get("genres"); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: FutureBuilder( + // ref.read(apiProvider).getGenres(includeItemTypes: [ + // BaseItemKind.movie, + // BaseItemKind.series, + // BaseItemKind.boxSet + // ]), + if (queryData == null) { + return FutureBuilder( future: ref.read(apiProvider).getGenres(includeItemTypes: [ BaseItemKind.movie, BaseItemKind.series, BaseItemKind.boxSet ]), builder: (context, snapshot) { - if (!snapshot.hasData) { + if (snapshot.hasData && snapshot.data != null) { + snapshot.data!.shuffle(); + ref + .read(databaseProvider("queryCache")) + .put("genres", snapshot.data); + return GenreBanner( + pageController: pageController, queryData: snapshot.data!); + } else { return const SizedBox.shrink(); } - snapshot.data!.shuffle(); - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 10, horizontal: 10), - child: ItemCarouselLabel( - title: AppLocalizations.of(context)!.genres, - scrollController: pageController, - offsetWidth: MediaQuery.of(context).size.width * 0.9)), - SizedBox( - height: MediaQuery.of(context).size.width >= 640 ? 250 : 150, - child: PageView.builder( - controller: pageController, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () { - context.push(Uri( - path: ScreenPaths.library, - queryParameters: { - "genreFilter": snapshot.data![index].id, - }).toString()); - }, - child: Stack( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - ), - child: FutureBuilder( - future: ref - .read(apiProvider) - .getFilterItems( - genreIds: [snapshot.data![index]], - limit: 1), - builder: (context, itemData) { - if (!itemData.hasData || - itemData.data!.isEmpty) { - return const SizedBox.shrink(); - } - var imageType = ImageType.backdrop; - if ((itemData.data![0].imageTags - ?.containsKey("Backdrop") ?? - false) == - false) { - imageType = ImageType.primary; - } + }); + } else { + ref.read(apiProvider).getGenres(includeItemTypes: [ + BaseItemKind.movie, + BaseItemKind.series, + BaseItemKind.boxSet + ]).then((value) { + value.shuffle(); + ref.read(databaseProvider("queryCache")).put("genres", value); + }); + return GenreBanner(pageController: pageController, queryData: queryData); + } + } +} - return JellyfinImage( - id: itemData.data![0].id!, - type: imageType, - blurHash: itemData - .data![0] - .imageBlurHashes - ?.backdrop - ?.values - .first); - }, - )), - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: Gradients.getGradient(index), - stops: const [0, 0.5, 0.9], - begin: Alignment.bottomRight, - end: Alignment.topLeft, - ), - borderRadius: BorderRadius.circular(10.0), - ), - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - color: Colors.black.withOpacity(0.3), - ), - ), - Center( - child: Text(snapshot.data![index].name!, - style: JfxTextTheme.scalingTheme(context) - .headlineSmall! - .copyWith( - fontWeight: FontWeight.bold, - )), - ) - ], +class GenreBanner extends HookConsumerWidget { + const GenreBanner({ + super.key, + required this.pageController, + required this.queryData, + }); + + final PageController pageController; + final List queryData; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), + child: ItemCarouselLabel( + title: AppLocalizations.of(context)!.genres, + scrollController: pageController, + offsetWidth: MediaQuery.of(context).size.width * 0.9)), + SizedBox( + height: MediaQuery.of(context).size.width >= 640 ? 250 : 150, + child: PageView.builder( + controller: pageController, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () { + context.push( + Uri(path: ScreenPaths.library, queryParameters: { + "genreFilter": queryData[index].id, + }).toString()); + }, + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + ), + child: FutureBuilder( + future: ref.read(apiProvider).getFilterItems( + genreIds: [queryData[index]], limit: 1), + builder: (context, itemData) { + if (!itemData.hasData || + itemData.data!.isEmpty) { + return const SizedBox.shrink(); + } + var imageType = ImageType.backdrop; + if ((itemData.data![0].imageTags + ?.containsKey("Backdrop") ?? + false) == + false) { + imageType = ImageType.primary; + } + + return JellyfinImage( + id: itemData.data![0].id!, + type: imageType, + blurHash: itemData.data![0].imageBlurHashes + ?.backdrop?.values.first); + }, + )), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: Gradients.getGradient(index), + stops: const [0, 0.5, 0.9], + begin: Alignment.bottomRight, + end: Alignment.topLeft, + ), + borderRadius: BorderRadius.circular(10.0), ), ), - ); - }, - itemCount: snapshot.data?.length ?? 0, - scrollDirection: Axis.horizontal, + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: Colors.black.withOpacity(0.3), + ), + ), + Center( + child: Text(queryData[index].name!, + style: JfxTextTheme.scalingTheme(context) + .headlineSmall! + .copyWith( + fontWeight: FontWeight.bold, + )), + ) + ], + ), ), - ), - ], - ); - }, - )); + ); + }, + itemCount: queryData.length, + scrollDirection: Axis.horizontal, + ), + ), + ], + ), + ); } } diff --git a/lib/models/baseitemdto_adapter.dart b/lib/models/baseitemdto_adapter.dart new file mode 100644 index 0000000..54ef9dd --- /dev/null +++ b/lib/models/baseitemdto_adapter.dart @@ -0,0 +1,24 @@ +import 'package:hive/hive.dart'; +import 'package:tentacle/tentacle.dart'; + +class BaseItemDtoAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + BaseItemDto read(BinaryReader reader) { + return $BaseItemDto((p0) { + p0.id = reader.read(); + p0.name = reader.read(); + p0.productionYear = reader.read(); + }); + } + + @override + void write(BinaryWriter writer, BaseItemDto obj) { + writer + ..write(obj.id) + ..write(obj.name) + ..write(obj.productionYear); + } +} diff --git a/lib/providers/pagestorage_provider.dart b/lib/providers/pagestorage_provider.dart new file mode 100644 index 0000000..fae11d1 --- /dev/null +++ b/lib/providers/pagestorage_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final pageStorageProvider = Provider((ref) => PageStorageBucket()); diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 70bee8c..1ea5ed7 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -11,6 +11,7 @@ import 'package:jellyflix/models/skeleton_item.dart'; import 'package:jellyflix/providers/api_provider.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:jellyflix/providers/pagestorage_provider.dart'; import 'package:tentacle/tentacle.dart'; import 'package:jellyflix/providers/database_provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -19,147 +20,211 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class HomeScreen extends HookConsumerWidget { const HomeScreen({super.key}); + final PageStorageKey myWidgetKey = const PageStorageKey('MyUniqueWidgetKey'); + @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( - body: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FutureBuilder( - future: ref.read(apiProvider).getHeaderRecommendation(), - builder: (context, AsyncSnapshot> snapshot) { - List items = [ - SkeletonItem.baseItemDto, - SkeletonItem.baseItemDto, - SkeletonItem.baseItemDto - ]; - if (snapshot.hasData) { - if (snapshot.data!.isEmpty) { - return const SizedBox.shrink(); - } - // filter where backdrop image is not null - items = snapshot.data!; - items.shuffle(); - if (items.length > 7) { - items = items.sublist(0, 7); + body: PageStorage( + bucket: ref.read(pageStorageProvider), + child: SingleChildScrollView( + key: myWidgetKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FutureBuilder( + future: ref.read(apiProvider).getHeaderRecommendation(), + builder: + (context, AsyncSnapshot> snapshot) { + List items = [ + SkeletonItem.baseItemDto, + SkeletonItem.baseItemDto, + SkeletonItem.baseItemDto + ]; + if (snapshot.hasData) { + if (snapshot.data!.isEmpty) { + return const SizedBox.shrink(); + } + // filter where backdrop image is not null + items = snapshot.data!; + items.shuffle(); + if (items.length > 7) { + items = items.sublist(0, 7); + } } - } - return Skeletonizer( - enabled: !snapshot.hasData, - child: ImageBanner( - items: items, - ), - ); - }), - const SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: PaginatedItemCarousel( - future: (startIndex, limit) => - ref.read(apiProvider).continueWatchingAndNextUp(), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, - imageMapping: (BaseItemDto e) { - if (e.type == BaseItemKind.episode && - ref - .read(databaseProvider("settings")) - .get("showPrimaryForEpisodes") != - true) { - return e.seriesId!; - } else { - return e.id!; - } - }, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => - e.productionYear == null ? "" : e.productionYear.toString(), - title: AppLocalizations.of(context)!.continueWatching, - overlay: (int index, BaseItemDto element) => - PlaybackProgressOverlay( - progress: element.userData?.playedPercentage != null - ? element.userData!.playedPercentage! / 100 - : null, + return Skeletonizer( + enabled: !snapshot.hasData, + child: ImageBanner( + items: items, + ), + ); + }), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: PaginatedItemCarousel( + future: (startIndex, limit) => + ref.read(apiProvider).continueWatchingAndNextUp(), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + imageMapping: (BaseItemDto e) { + if (e.type == BaseItemKind.episode && + ref + .read(databaseProvider("settings")) + .get("showPrimaryForEpisodes") != + true) { + return e.seriesId!; + } else { + return e.id!; + } + }, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear == null + ? "" + : e.productionYear.toString(), + title: AppLocalizations.of(context)!.continueWatching, + overlay: (int index, BaseItemDto element) => + PlaybackProgressOverlay( + progress: element.userData?.playedPercentage != null + ? element.userData!.playedPercentage! / 100 + : null, + ), ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: PaginatedItemCarousel( - pageSize: 20, - future: (startIndex, limit) => ref - .read(apiProvider) - .getFilterItems( - sortBy: [ItemSortBy.dateCreated], - sortOrder: [SortOrder.descending], - includeItemTypes: [BaseItemKind.movie], - startIndex: startIndex, - limit: limit), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, - title: AppLocalizations.of(context)!.recentlyAddedMovies, - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - posterType: PosterType.vertical, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: PaginatedItemCarousel( + pageSize: 20, + future: (startIndex, limit) => ref + .read(apiProvider) + .getFilterItems( + sortBy: [ItemSortBy.dateCreated], + sortOrder: [SortOrder.descending], + includeItemTypes: [BaseItemKind.movie], + startIndex: startIndex, + limit: limit), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + title: AppLocalizations.of(context)!.recentlyAddedMovies, + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + posterType: PosterType.vertical, + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: PaginatedItemCarousel( - pageSize: 20, - future: (startIndex, limit) => ref - .read(apiProvider) - .getFilterItems( - sortBy: [ItemSortBy.dateLastContentAdded], - sortOrder: [SortOrder.descending], - includeItemTypes: [BaseItemKind.series], - startIndex: startIndex, - limit: limit), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, - title: AppLocalizations.of(context)!.recentlyAddedShows, - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - posterType: PosterType.vertical, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: PaginatedItemCarousel( + pageSize: 20, + future: (startIndex, limit) => ref + .read(apiProvider) + .getFilterItems( + sortBy: [ItemSortBy.dateLastContentAdded], + sortOrder: [SortOrder.descending], + includeItemTypes: [BaseItemKind.series], + startIndex: startIndex, + limit: limit), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + title: AppLocalizations.of(context)!.recentlyAddedShows, + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + posterType: PosterType.vertical, + ), + ), + const CachedGenreBanner(), + if (ref + .read(databaseProvider("settings")) + .get("disableWatchlist") != + true) + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: PaginatedItemCarousel( + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + future: (startIndex, limit) => + ref.read(apiProvider).getWatchlist(), + title: AppLocalizations.of(context)!.yourWatchlist, + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: FutureItemCarousel( + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + titleMapping: (e) => e.name!, + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + future: ref.read(apiProvider).getTopTenPopular(), + subtitleMapping: (e) => e.productionYear.toString(), + title: AppLocalizations.of(context)!.top10inYourLibrary, + overlay: (index, element) => Positioned( + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Container( + height: 25, + width: 60, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: Theme.of(context) + .buttonTheme + .colorScheme! + .onPrimary, + ), + child: Center( + child: Text(AppLocalizations.of(context)! + .top10(index + 1)))), + )), + ), ), - ), - const GenreBanner(), - if (ref - .read(databaseProvider("settings")) - .get("disableWatchlist") != - true) Padding( padding: const EdgeInsets.symmetric(vertical: 10.0), child: PaginatedItemCarousel( + title: AppLocalizations.of(context)!.similarToWatchHistory, titleMapping: (e) => e.name!, subtitleMapping: (e) => e.productionYear.toString(), imageMapping: (e) => e.id!, blurHashMapping: (e) => e.imageBlurHashes?.primary?.values.first, future: (startIndex, limit) => - ref.read(apiProvider).getWatchlist(), - title: AppLocalizations.of(context)!.yourWatchlist, + ref.read(apiProvider).similarItemsByLastWatched(), onTap: (index, id) { context .push(Uri(path: ScreenPaths.detail, queryParameters: { @@ -168,161 +233,114 @@ class HomeScreen extends HookConsumerWidget { }, ), ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: FutureItemCarousel( - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, - titleMapping: (e) => e.name!, - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - future: ref.read(apiProvider).getTopTenPopular(), - subtitleMapping: (e) => e.productionYear.toString(), - title: AppLocalizations.of(context)!.top10inYourLibrary, - overlay: (index, element) => Positioned( - child: Padding( - padding: const EdgeInsets.all(5.0), - child: Container( - height: 25, - width: 60, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - color: Theme.of(context) - .buttonTheme - .colorScheme! - .onPrimary, - ), - child: Center( - child: Text( - AppLocalizations.of(context)!.top10(index + 1)))), - )), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: PaginatedItemCarousel( - title: AppLocalizations.of(context)!.similarToWatchHistory, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - future: (startIndex, limit) => - ref.read(apiProvider).similarItemsByLastWatched(), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: PaginatedItemCarousel( + title: AppLocalizations.of(context)!.highesRatedMovies, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + future: (startIndex, limit) => + ref.read(apiProvider).getFilterItems( + sortBy: [ItemSortBy.random], + minCommunityRating: 7.5, + includeItemTypes: [BaseItemKind.movie], + //filters: [ItemFilter.isUnplayed], + startIndex: startIndex, + limit: limit), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: PaginatedItemCarousel( - title: AppLocalizations.of(context)!.highesRatedMovies, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - future: (startIndex, limit) => - ref.read(apiProvider).getFilterItems( - sortBy: [ItemSortBy.random], - minCommunityRating: 7.5, - includeItemTypes: [BaseItemKind.movie], - //filters: [ItemFilter.isUnplayed], - startIndex: startIndex, - limit: limit), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: PaginatedItemCarousel( + title: AppLocalizations.of(context)!.highestRatedShows, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + future: (startIndex, limit) => ref + .read(apiProvider) + .getFilterItems( + sortBy: [ItemSortBy.random], + minCommunityRating: 7.5, + includeItemTypes: [BaseItemKind.series], + startIndex: startIndex, + limit: limit), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: PaginatedItemCarousel( - title: AppLocalizations.of(context)!.highestRatedShows, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - future: (startIndex, limit) => ref - .read(apiProvider) - .getFilterItems( - sortBy: [ItemSortBy.random], - minCommunityRating: 7.5, - includeItemTypes: [BaseItemKind.series], - startIndex: startIndex, - limit: limit), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: PaginatedItemCarousel( + title: AppLocalizations.of(context)!.moviesMaybeMissed, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + future: (startIndex, limit) => ref + .read(apiProvider) + .getFilterItems( + sortBy: [ItemSortBy.random], + sortOrder: [SortOrder.descending], + includeItemTypes: [BaseItemKind.movie], + filters: [ItemFilter.isUnplayed], + startIndex: startIndex, + limit: limit), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: PaginatedItemCarousel( - title: AppLocalizations.of(context)!.moviesMaybeMissed, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - future: (startIndex, limit) => ref - .read(apiProvider) - .getFilterItems( - sortBy: [ItemSortBy.random], - sortOrder: [SortOrder.descending], - includeItemTypes: [BaseItemKind.movie], - filters: [ItemFilter.isUnplayed], - startIndex: startIndex, - limit: limit), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: PaginatedItemCarousel( + title: AppLocalizations.of(context)!.showsMaybeMissed, + titleMapping: (e) => e.name!, + subtitleMapping: (e) => e.productionYear.toString(), + imageMapping: (e) => e.id!, + blurHashMapping: (e) => + e.imageBlurHashes?.primary?.values.first, + future: (startIndex, limit) => ref + .read(apiProvider) + .getFilterItems( + sortBy: [ItemSortBy.random], + sortOrder: [SortOrder.descending], + includeItemTypes: [BaseItemKind.series], + filters: [ItemFilter.isUnplayed], + startIndex: startIndex, + limit: limit), + onTap: (index, id) { + context + .push(Uri(path: ScreenPaths.detail, queryParameters: { + "id": id, + }).toString()); + }, + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: PaginatedItemCarousel( - title: AppLocalizations.of(context)!.showsMaybeMissed, - titleMapping: (e) => e.name!, - subtitleMapping: (e) => e.productionYear.toString(), - imageMapping: (e) => e.id!, - blurHashMapping: (e) => - e.imageBlurHashes?.primary?.values.first, - future: (startIndex, limit) => ref - .read(apiProvider) - .getFilterItems( - sortBy: [ItemSortBy.random], - sortOrder: [SortOrder.descending], - includeItemTypes: [BaseItemKind.series], - filters: [ItemFilter.isUnplayed], - startIndex: startIndex, - limit: limit), - onTap: (index, id) { - context.push(Uri(path: ScreenPaths.detail, queryParameters: { - "id": id, - }).toString()); - }, + const RecommendationCarousels(), + const SizedBox( + height: 20, ), - ), - const RecommendationCarousels(), - const SizedBox( - height: 20, - ), - ], + ], + ), ), ), ); diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart index 71793f0..abf32e9 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:hive_flutter/hive_flutter.dart'; +import 'package:jellyflix/models/baseitemdto_adapter.dart'; import 'package:jellyflix/models/user.dart'; import 'package:jellyflix/services/secure_storage_service.dart'; @@ -24,8 +25,10 @@ class DatabaseService { static Future initialize() async { await Hive.initFlutter("jellyflix"); Hive.registerAdapter(UserAdapter()); + Hive.registerAdapter(BaseItemDtoAdapter()); await DatabaseService('auth', SecureStorageService()).openBox(); await DatabaseService('settings', SecureStorageService()).openBox(); + await DatabaseService('queryCache', SecureStorageService()).openBox(); } Future openBox() async { diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef64..a826e0f 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -6,4 +6,7 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } }