mirror of
https://github.com/gabehf/Fladder.git
synced 2026-04-26 05:51:51 -07:00
Init repo
This commit is contained in:
commit
764b6034e3
566 changed files with 212335 additions and 0 deletions
149
lib/providers/items/book_details_provider.dart
Normal file
149
lib/providers/items/book_details_provider.dart
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/book_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
|
||||
class BookProviderModel {
|
||||
final List<BookModel> chapters;
|
||||
final ItemBaseModel? parentModel;
|
||||
BookProviderModel({
|
||||
this.chapters = const [],
|
||||
this.parentModel,
|
||||
});
|
||||
|
||||
BookModel? get book => chapters.firstOrNull;
|
||||
|
||||
ImagesData? get cover => parentModel?.getPosters ?? book?.getPosters;
|
||||
|
||||
List<BookModel> get allBooks {
|
||||
if (chapters.isEmpty) return [book].whereNotNull().toList();
|
||||
return chapters;
|
||||
}
|
||||
|
||||
bool get collectionPlayed {
|
||||
if (chapters.isEmpty) return book?.userData.played ?? false;
|
||||
for (var i = 0; i < chapters.length; i++) {
|
||||
if (!chapters[i].userData.played) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BookModel? get nextUp {
|
||||
if (chapters.isEmpty) return book;
|
||||
return chapters.lastWhereOrNull((element) => element.currentPage != 0) ??
|
||||
chapters.firstWhereOrNull((element) => !element.userData.played) ??
|
||||
chapters.first;
|
||||
}
|
||||
|
||||
BookModel? nextChapter(BookModel? currentBook) {
|
||||
if (currentBook != null && chapters.isEmpty) return null;
|
||||
|
||||
final currentChapter = chapters.indexOf(currentBook!);
|
||||
|
||||
// Check if the current chapter is the last one
|
||||
if (currentChapter == chapters.length - 1) return null;
|
||||
|
||||
// Return the next chapter
|
||||
return chapters[currentChapter + 1];
|
||||
}
|
||||
|
||||
BookModel? previousChapter(BookModel? currentBook) {
|
||||
if (currentBook != null && chapters.isEmpty) return null;
|
||||
|
||||
final currentChapter = chapters.indexOf(currentBook!);
|
||||
|
||||
// Check if the current chapter is the first one
|
||||
if (currentChapter == 0) return null;
|
||||
|
||||
// Return the previous chapter
|
||||
return chapters[currentChapter - 1];
|
||||
}
|
||||
|
||||
BookProviderModel copyWith({
|
||||
List<BookModel>? chapters,
|
||||
ValueGetter<ItemBaseModel?>? parentModel,
|
||||
}) {
|
||||
return BookProviderModel(
|
||||
chapters: chapters ?? this.chapters,
|
||||
parentModel: parentModel != null ? parentModel.call() : this.parentModel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final bookDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<BookDetailsProviderNotifier, BookProviderModel, String>((ref, id) {
|
||||
return BookDetailsProviderNotifier(ref);
|
||||
});
|
||||
|
||||
class BookDetailsProviderNotifier extends StateNotifier<BookProviderModel> {
|
||||
BookDetailsProviderNotifier(this.ref) : super(BookProviderModel());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late Directory savedDirectory;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchDetails(BookModel book) async {
|
||||
state = state.copyWith(
|
||||
parentModel: () => book,
|
||||
);
|
||||
String bookId = state.book?.id ?? book.id;
|
||||
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: bookId);
|
||||
final parentResponse = await api.usersUserIdItemsItemIdGet(itemId: response.body?.parentId);
|
||||
|
||||
final parentModel = parentResponse.bodyOrThrow;
|
||||
final getViews = await api.usersUserIdViewsGet();
|
||||
|
||||
//Hacky solution more false positives so good enough for now.
|
||||
final parentIsView =
|
||||
getViews.body?.items?.firstWhereOrNull((element) => element.name == parentResponse.body?.name) != null;
|
||||
|
||||
Response<ServerQueryResult>? siblingsResponse;
|
||||
if (!parentIsView) {
|
||||
siblingsResponse = await api.itemsGet(
|
||||
parentId: parentModel.id,
|
||||
recursive: true,
|
||||
sortBy: SortingOptions.name.toSortBy,
|
||||
fields: [
|
||||
ItemFields.genres,
|
||||
ItemFields.parentid,
|
||||
ItemFields.tags,
|
||||
ItemFields.datecreated,
|
||||
ItemFields.datelastmediaadded,
|
||||
ItemFields.parentid,
|
||||
ItemFields.overview,
|
||||
ItemFields.originaltitle,
|
||||
ItemFields.primaryimageaspectratio,
|
||||
],
|
||||
includeItemTypes: [
|
||||
BaseItemKind.book,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
siblingsResponse = null;
|
||||
}
|
||||
|
||||
final openedBook = response.bodyOrThrow;
|
||||
|
||||
state = state.copyWith(
|
||||
parentModel: !parentIsView ? () => parentResponse.bodyOrThrow : null,
|
||||
chapters: (siblingsResponse?.body?.items ?? [openedBook]).whereType<BookModel>().whereNotNull().toList(),
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
106
lib/providers/items/episode_details_provider.dart
Normal file
106
lib/providers/items/episode_details_provider.dart
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
|
||||
class EpisodeDetailModel {
|
||||
final SeriesModel? series;
|
||||
final List<EpisodeModel> episodes;
|
||||
final EpisodeModel? episode;
|
||||
EpisodeDetailModel({
|
||||
this.series,
|
||||
this.episodes = const [],
|
||||
this.episode,
|
||||
});
|
||||
|
||||
EpisodeDetailModel copyWith({
|
||||
SeriesModel? series,
|
||||
List<EpisodeModel>? episodes,
|
||||
EpisodeModel? episode,
|
||||
}) {
|
||||
return EpisodeDetailModel(
|
||||
series: series ?? this.series,
|
||||
episodes: episodes ?? this.episodes,
|
||||
episode: episode ?? this.episode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final episodeDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<EpisodeDetailsProvider, EpisodeDetailModel, String>((ref, id) {
|
||||
return EpisodeDetailsProvider(ref);
|
||||
});
|
||||
|
||||
class EpisodeDetailsProvider extends StateNotifier<EpisodeDetailModel> {
|
||||
EpisodeDetailsProvider(this.ref) : super(EpisodeDetailModel());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchDetails(ItemBaseModel item) async {
|
||||
try {
|
||||
final seriesResponse = await api.usersUserIdItemsItemIdGet(itemId: item.parentBaseModel.id);
|
||||
if (seriesResponse.body == null) return null;
|
||||
final episodes = await api.showsSeriesIdEpisodesGet(seriesId: item.parentBaseModel.id);
|
||||
|
||||
if (episodes.body == null) return null;
|
||||
|
||||
final episode = (await api.usersUserIdItemsItemIdGet(itemId: item.id)).bodyOrThrow as EpisodeModel;
|
||||
|
||||
state = state.copyWith(
|
||||
series: seriesResponse.bodyOrThrow as SeriesModel,
|
||||
episodes: EpisodeModel.episodesFromDto(episodes.bodyOrThrow.items, ref),
|
||||
episode: episode,
|
||||
);
|
||||
|
||||
return seriesResponse;
|
||||
} catch (e) {
|
||||
_tryToCreateOfflineState(item);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _tryToCreateOfflineState(ItemBaseModel item) {
|
||||
final syncNotifier = ref.read(syncProvider.notifier);
|
||||
final syncedItem = syncNotifier.getParentItem(item.id);
|
||||
if (syncedItem == null) return;
|
||||
final seriesModel = syncedItem.createItemModel(ref) as SeriesModel;
|
||||
final episodes = ref
|
||||
.read(syncProvider.notifier)
|
||||
.getChildren(syncedItem)
|
||||
.map(
|
||||
(e) => e.createItemModel(ref) as EpisodeModel,
|
||||
)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
state = state.copyWith(
|
||||
series: seriesModel,
|
||||
episode: episodes.firstWhereOrNull((element) => element.id == item.id),
|
||||
episodes: episodes,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
void setSubIndex(int index) {
|
||||
state = state.copyWith(
|
||||
episode: state.episode?.copyWith(
|
||||
mediaStreams: state.episode?.mediaStreams.copyWith(
|
||||
defaultSubStreamIndex: index,
|
||||
)));
|
||||
}
|
||||
|
||||
void setAudioIndex(int index) {
|
||||
state = state.copyWith(
|
||||
episode: state.episode?.copyWith(
|
||||
mediaStreams: state.episode?.mediaStreams.copyWith(
|
||||
defaultAudioStreamIndex: index,
|
||||
)));
|
||||
}
|
||||
}
|
||||
42
lib/providers/items/folder_details_provider.dart
Normal file
42
lib/providers/items/folder_details_provider.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/folder_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final folderDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<FolderDetailsNotifier, FolderModel?, String>((ref, id) {
|
||||
return FolderDetailsNotifier(ref);
|
||||
});
|
||||
|
||||
class FolderDetailsNotifier extends StateNotifier<FolderModel?> {
|
||||
FolderDetailsNotifier(this.ref) : super(null);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchDetails(String id) async {
|
||||
if (state == null) {
|
||||
final folderItem = await api.usersUserIdItemsItemIdGet(itemId: id);
|
||||
|
||||
if (folderItem.body != null) {
|
||||
state = folderItem.bodyOrThrow as FolderModel;
|
||||
}
|
||||
}
|
||||
|
||||
final response = await api.itemsGet(
|
||||
parentId: id,
|
||||
sortBy: [ItemSortBy.sortname, ItemSortBy.name],
|
||||
sortOrder: [SortOrder.ascending],
|
||||
fields: [
|
||||
ItemFields.primaryimageaspectratio,
|
||||
ItemFields.childcount,
|
||||
],
|
||||
);
|
||||
|
||||
state = state?.copyWith(items: response.body?.items.where((element) => element.childCount != 0).toList());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
132
lib/providers/items/identify_provider.dart
Normal file
132
lib/providers/items/identify_provider.dart
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
|
||||
class IdentifyModel {
|
||||
final ItemBaseModel? item;
|
||||
final String searchString;
|
||||
final List<ExternalIdInfo> externalIds;
|
||||
final Map<String, String> keys;
|
||||
final List<RemoteSearchResult> results;
|
||||
final int? year;
|
||||
final bool replaceAllImages;
|
||||
final bool processing;
|
||||
IdentifyModel({
|
||||
this.item,
|
||||
this.searchString = "",
|
||||
this.externalIds = const [],
|
||||
this.keys = const {},
|
||||
this.results = const [],
|
||||
this.year,
|
||||
this.replaceAllImages = true,
|
||||
this.processing = false,
|
||||
});
|
||||
|
||||
Map<String, dynamic> get body => {
|
||||
"SearchInfo": {
|
||||
"ProviderIds": keys,
|
||||
"Name": searchString,
|
||||
"Year": year,
|
||||
},
|
||||
"ItemId": item?.id,
|
||||
}..removeWhere((key, value) => value == null);
|
||||
|
||||
IdentifyModel copyWith({
|
||||
ValueGetter<ItemBaseModel?>? item,
|
||||
String? searchString,
|
||||
List<ExternalIdInfo>? externalIds,
|
||||
Map<String, String>? keys,
|
||||
List<RemoteSearchResult>? results,
|
||||
ValueGetter<int?>? year,
|
||||
bool? replaceAllImages,
|
||||
bool? processing,
|
||||
}) {
|
||||
return IdentifyModel(
|
||||
item: item != null ? item() : this.item,
|
||||
searchString: searchString ?? this.searchString,
|
||||
externalIds: externalIds ?? this.externalIds,
|
||||
keys: keys ?? this.keys,
|
||||
results: results ?? this.results,
|
||||
year: year != null ? year() : this.year,
|
||||
replaceAllImages: replaceAllImages ?? this.replaceAllImages,
|
||||
processing: processing ?? this.processing,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final simpleProviderProvider = StateProvider<String>((ref) {
|
||||
return "";
|
||||
});
|
||||
|
||||
final identifyProvider = StateNotifierProvider.autoDispose.family<IdentifyNotifier, IdentifyModel, String>((ref, id) {
|
||||
return IdentifyNotifier(ref, id);
|
||||
});
|
||||
|
||||
class IdentifyNotifier extends StateNotifier<IdentifyModel> {
|
||||
IdentifyNotifier(this.ref, this.id) : super(IdentifyModel());
|
||||
|
||||
final String id;
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<void> fetchInformation() async {
|
||||
state = state.copyWith(processing: true);
|
||||
final item = await api.usersUserIdItemsItemIdGet(itemId: id);
|
||||
final itemModel = item.bodyOrThrow;
|
||||
final response = await api.itemsItemIdExternalIdInfosGet(itemId: id);
|
||||
state = state.copyWith(
|
||||
item: () => itemModel,
|
||||
externalIds: response.body,
|
||||
searchString: itemModel.name,
|
||||
year: () => itemModel.overview.yearAired,
|
||||
keys: {for (var element in response.body ?? []) (element as ExternalIdInfo).key ?? "": ""},
|
||||
);
|
||||
state = state.copyWith(processing: false);
|
||||
}
|
||||
|
||||
IdentifyModel update(IdentifyModel Function(IdentifyModel state) cb) => state = cb(state);
|
||||
|
||||
void clearFields() {
|
||||
state = state.copyWith(
|
||||
searchString: "",
|
||||
year: () => null,
|
||||
keys: state.keys..updateAll((key, value) => ""),
|
||||
);
|
||||
}
|
||||
|
||||
void updateKey(MapEntry<String, String> map) {
|
||||
state = state.copyWith(keys: state.keys..update(map.key, (value) => map.value));
|
||||
}
|
||||
|
||||
Future<Response<List<RemoteSearchResult>>?> remoteSearch() async {
|
||||
if (state.item == null) return null;
|
||||
state = state.copyWith(processing: true);
|
||||
late Response<List<RemoteSearchResult>> response;
|
||||
switch (state.item) {
|
||||
case SeriesModel _:
|
||||
response = await api.itemsRemoteSearchSeriesPost(body: SeriesInfoRemoteSearchQuery.fromJson(state.body));
|
||||
case MovieModel _:
|
||||
default:
|
||||
response = await api.itemsRemoteSearchMoviePost(body: MovieInfoRemoteSearchQuery.fromJson(state.body));
|
||||
}
|
||||
state = state.copyWith(results: response.body, processing: false);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response<dynamic>?> setIdentity(RemoteSearchResult result) async {
|
||||
if (state.item == null) return null;
|
||||
state = state.copyWith(processing: true);
|
||||
final response = await api.itemsRemoteSearchApplyItemIdPost(
|
||||
itemId: state.item?.id ?? "", body: RemoteSearchResult.fromJson(result.toJson()));
|
||||
state = state.copyWith(processing: false);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
47
lib/providers/items/information_provider.dart
Normal file
47
lib/providers/items/information_provider.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/information_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
|
||||
class InformationProviderModel {
|
||||
final InformationModel? model;
|
||||
final bool loading;
|
||||
InformationProviderModel({
|
||||
this.model,
|
||||
this.loading = false,
|
||||
});
|
||||
|
||||
InformationProviderModel copyWith({
|
||||
InformationModel? model,
|
||||
bool? loading,
|
||||
}) {
|
||||
return InformationProviderModel(
|
||||
model: model ?? this.model,
|
||||
loading: loading ?? this.loading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final informationProvider =
|
||||
StateNotifierProvider.autoDispose.family<InformationNotifier, InformationProviderModel, String>((ref, id) {
|
||||
return InformationNotifier(ref);
|
||||
});
|
||||
|
||||
class InformationNotifier extends StateNotifier<InformationProviderModel> {
|
||||
InformationNotifier(this.ref) : super(InformationProviderModel());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response> getItemInformation(ItemBaseModel item) async {
|
||||
state = state.copyWith(loading: true);
|
||||
final response = await api.usersUserIdItemsItemIdGetBaseItem(itemId: item.id);
|
||||
await Future.delayed(const Duration(milliseconds: 250));
|
||||
state = state.copyWith(loading: false, model: InformationModel.fromResponse(response.body));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
22
lib/providers/items/item_details_provider.dart
Normal file
22
lib/providers/items/item_details_provider.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final itemDetailsProvider = StateNotifierProvider.autoDispose<ItemDetailsNotifier, ItemBaseModel?>((ref) {
|
||||
return ItemDetailsNotifier(ref);
|
||||
});
|
||||
|
||||
class ItemDetailsNotifier extends StateNotifier<ItemBaseModel?> {
|
||||
ItemDetailsNotifier(this.ref) : super(null);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<ItemBaseModel?> fetchDetails(String itemId) async {
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: itemId);
|
||||
if (response.body == null) return null;
|
||||
return response.bodyOrThrow;
|
||||
}
|
||||
}
|
||||
51
lib/providers/items/movies_details_provider.dart
Normal file
51
lib/providers/items/movies_details_provider.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/related_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'movies_details_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class MovieDetails extends _$MovieDetails {
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
@override
|
||||
MovieModel? build(String arg) => null;
|
||||
|
||||
Future<Response?> fetchDetails(ItemBaseModel item) async {
|
||||
try {
|
||||
if (item is MovieModel && state == null) {
|
||||
state = item;
|
||||
}
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: item.id);
|
||||
if (response.body == null) return null;
|
||||
state = response.bodyOrThrow as MovieModel;
|
||||
final related = await ref.read(relatedUtilityProvider).relatedContent(item.id);
|
||||
state = state?.copyWith(related: related.body);
|
||||
return null;
|
||||
} catch (e) {
|
||||
_tryToCreateOfflineState(item);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _tryToCreateOfflineState(ItemBaseModel item) {
|
||||
final syncNotifier = ref.read(syncProvider.notifier);
|
||||
final syncedItem = syncNotifier.getParentItem(item.id);
|
||||
if (syncedItem == null) return;
|
||||
final movieModel = syncedItem.createItemModel(ref) as MovieModel;
|
||||
state = movieModel;
|
||||
}
|
||||
|
||||
void setSubIndex(int index) {
|
||||
state = state?.copyWith(mediaStreams: state?.mediaStreams.copyWith(defaultSubStreamIndex: index));
|
||||
}
|
||||
|
||||
void setAudioIndex(int index) {
|
||||
state = state?.copyWith(mediaStreams: state?.mediaStreams.copyWith(defaultAudioStreamIndex: index));
|
||||
}
|
||||
}
|
||||
174
lib/providers/items/movies_details_provider.g.dart
Normal file
174
lib/providers/items/movies_details_provider.g.dart
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'movies_details_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$movieDetailsHash() => r'e5ab0af7fab9eb7a8ea50a873e8875bb572bd240';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$MovieDetails
|
||||
extends BuildlessAutoDisposeNotifier<MovieModel?> {
|
||||
late final String arg;
|
||||
|
||||
MovieModel? build(
|
||||
String arg,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [MovieDetails].
|
||||
@ProviderFor(MovieDetails)
|
||||
const movieDetailsProvider = MovieDetailsFamily();
|
||||
|
||||
/// See also [MovieDetails].
|
||||
class MovieDetailsFamily extends Family<MovieModel?> {
|
||||
/// See also [MovieDetails].
|
||||
const MovieDetailsFamily();
|
||||
|
||||
/// See also [MovieDetails].
|
||||
MovieDetailsProvider call(
|
||||
String arg,
|
||||
) {
|
||||
return MovieDetailsProvider(
|
||||
arg,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
MovieDetailsProvider getProviderOverride(
|
||||
covariant MovieDetailsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.arg,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'movieDetailsProvider';
|
||||
}
|
||||
|
||||
/// See also [MovieDetails].
|
||||
class MovieDetailsProvider
|
||||
extends AutoDisposeNotifierProviderImpl<MovieDetails, MovieModel?> {
|
||||
/// See also [MovieDetails].
|
||||
MovieDetailsProvider(
|
||||
String arg,
|
||||
) : this._internal(
|
||||
() => MovieDetails()..arg = arg,
|
||||
from: movieDetailsProvider,
|
||||
name: r'movieDetailsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$movieDetailsHash,
|
||||
dependencies: MovieDetailsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
MovieDetailsFamily._allTransitiveDependencies,
|
||||
arg: arg,
|
||||
);
|
||||
|
||||
MovieDetailsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.arg,
|
||||
}) : super.internal();
|
||||
|
||||
final String arg;
|
||||
|
||||
@override
|
||||
MovieModel? runNotifierBuild(
|
||||
covariant MovieDetails notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
arg,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(MovieDetails Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: MovieDetailsProvider._internal(
|
||||
() => create()..arg = arg,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
arg: arg,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<MovieDetails, MovieModel?>
|
||||
createElement() {
|
||||
return _MovieDetailsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is MovieDetailsProvider && other.arg == arg;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, arg.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin MovieDetailsRef on AutoDisposeNotifierProviderRef<MovieModel?> {
|
||||
/// The parameter `arg` of this provider.
|
||||
String get arg;
|
||||
}
|
||||
|
||||
class _MovieDetailsProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<MovieDetails, MovieModel?>
|
||||
with MovieDetailsRef {
|
||||
_MovieDetailsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get arg => (origin as MovieDetailsProvider).arg;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
69
lib/providers/items/person_details_provider.dart
Normal file
69
lib/providers/items/person_details_provider.dart
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/person_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final personDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<PersonDetailsNotifier, PersonModel?, String>((ref, id) {
|
||||
return PersonDetailsNotifier(ref);
|
||||
});
|
||||
|
||||
class PersonDetailsNotifier extends StateNotifier<PersonModel?> {
|
||||
PersonDetailsNotifier(this.ref) : super(null);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchPerson(Person person) async {
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: person.id);
|
||||
|
||||
if (response.isSuccessful && response.body != null) {
|
||||
state = response.bodyOrThrow as PersonModel;
|
||||
fetchMovies();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response?> fetchMovies() async {
|
||||
final movies = await api.itemsGet(
|
||||
personIds: [state?.id ?? ""],
|
||||
limit: 25,
|
||||
sortBy: [ItemSortBy.premieredate, ItemSortBy.communityrating, ItemSortBy.sortname, ItemSortBy.productionyear],
|
||||
sortOrder: [SortOrder.descending],
|
||||
recursive: true,
|
||||
fields: [
|
||||
ItemFields.primaryimageaspectratio,
|
||||
],
|
||||
includeItemTypes: [
|
||||
BaseItemKind.movie,
|
||||
],
|
||||
);
|
||||
|
||||
final series = await api.itemsGet(
|
||||
personIds: [state?.id ?? ""],
|
||||
limit: 25,
|
||||
sortBy: [ItemSortBy.premieredate, ItemSortBy.communityrating, ItemSortBy.sortname, ItemSortBy.productionyear],
|
||||
sortOrder: [SortOrder.descending],
|
||||
recursive: true,
|
||||
fields: [
|
||||
ItemFields.primaryimageaspectratio,
|
||||
],
|
||||
includeItemTypes: [
|
||||
BaseItemKind.series,
|
||||
],
|
||||
);
|
||||
|
||||
state = state?.copyWith(
|
||||
movies: movies.body?.items.whereType<MovieModel>().toList(),
|
||||
series: series.body?.items.whereType<SeriesModel>().toList(),
|
||||
);
|
||||
return movies;
|
||||
}
|
||||
}
|
||||
49
lib/providers/items/photo_details_provider.dart
Normal file
49
lib/providers/items/photo_details_provider.dart
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final photoDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<PhotoDetailsNotifier, PhotoAlbumModel?, String>((ref, id) {
|
||||
return PhotoDetailsNotifier(ref);
|
||||
});
|
||||
|
||||
class PhotoDetailsNotifier extends StateNotifier<PhotoAlbumModel?> {
|
||||
PhotoDetailsNotifier(this.ref) : super(null);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchDetails(ItemBaseModel item) async {
|
||||
String? albumId;
|
||||
if (item is PhotoModel) {
|
||||
albumId = item.albumId;
|
||||
} else if (item is PhotoAlbumModel) {
|
||||
albumId = item.id;
|
||||
}
|
||||
|
||||
final albumData = await api.usersUserIdItemsItemIdGet(itemId: albumId);
|
||||
if (albumData.body == null) return albumData;
|
||||
PhotoAlbumModel newState = albumData.bodyOrThrow as PhotoAlbumModel;
|
||||
final response = await api.itemsGet(
|
||||
parentId: albumId,
|
||||
fields: [ItemFields.primaryimageaspectratio],
|
||||
sortBy: [ItemSortBy.sortname],
|
||||
includeItemTypes: [
|
||||
BaseItemKind.folder,
|
||||
BaseItemKind.photoalbum,
|
||||
BaseItemKind.photo,
|
||||
BaseItemKind.video,
|
||||
],
|
||||
sortOrder: [SortOrder.ascending],
|
||||
);
|
||||
if (response.body == null) return null;
|
||||
newState = newState.copyWith(photos: response.body?.items.toList());
|
||||
state = newState;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
33
lib/providers/items/season_details_provider.dart
Normal file
33
lib/providers/items/season_details_provider.dart
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final seasonDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<SeasonDetailsNotifier, SeasonModel?, String>((ref, id) {
|
||||
return SeasonDetailsNotifier(ref);
|
||||
});
|
||||
|
||||
class SeasonDetailsNotifier extends StateNotifier<SeasonModel?> {
|
||||
SeasonDetailsNotifier(this.ref) : super(null);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchDetails(String seasonId) async {
|
||||
SeasonModel? newState;
|
||||
|
||||
final season = await api.usersUserIdItemsItemIdGet(itemId: seasonId);
|
||||
if (season.body != null) newState = season.bodyOrThrow as SeasonModel;
|
||||
|
||||
final episodes = await api.showsSeriesIdEpisodesGet(
|
||||
seriesId: newState?.seriesId ?? "", seasonId: seasonId, fields: [ItemFields.overview]);
|
||||
newState = newState?.copyWith(episodes: EpisodeModel.episodesFromDto(episodes.body?.items, ref));
|
||||
state = newState;
|
||||
return season;
|
||||
}
|
||||
}
|
||||
95
lib/providers/items/series_details_provider.dart
Normal file
95
lib/providers/items/series_details_provider.dart
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/related_provider.dart';
|
||||
|
||||
final seriesDetailsProvider =
|
||||
StateNotifierProvider.autoDispose.family<SeriesDetailViewNotifier, SeriesModel?, String>((ref, id) {
|
||||
return SeriesDetailViewNotifier(ref);
|
||||
});
|
||||
|
||||
class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
||||
SeriesDetailViewNotifier(this.ref) : super(null);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response?> fetchDetails(ItemBaseModel seriesModel) async {
|
||||
try {
|
||||
if (seriesModel is SeriesModel) {
|
||||
state = seriesModel;
|
||||
}
|
||||
SeriesModel? newState;
|
||||
final response = await api.usersUserIdItemsItemIdGet(itemId: seriesModel.id);
|
||||
if (response.body == null) {
|
||||
state = seriesModel as SeriesModel;
|
||||
return null;
|
||||
}
|
||||
newState = response.bodyOrThrow as SeriesModel;
|
||||
|
||||
final seasons = await api.showsSeriesIdSeasonsGet(seriesId: seriesModel.id);
|
||||
newState = newState.copyWith(seasons: SeasonModel.seasonsFromDto(seasons.body?.items, ref));
|
||||
|
||||
final episodes = await api.showsSeriesIdEpisodesGet(seriesId: seriesModel.id, fields: [
|
||||
ItemFields.mediastreams,
|
||||
ItemFields.mediasources,
|
||||
ItemFields.overview,
|
||||
]);
|
||||
|
||||
newState = newState.copyWith(
|
||||
availableEpisodes: EpisodeModel.episodesFromDto(
|
||||
episodes.body?.items,
|
||||
ref,
|
||||
),
|
||||
);
|
||||
|
||||
final related = await ref.read(relatedUtilityProvider).relatedContent(seriesModel.id);
|
||||
state = newState.copyWith(related: related.body);
|
||||
return response;
|
||||
} catch (e) {
|
||||
_tryToCreateOfflineState(seriesModel);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _tryToCreateOfflineState(ItemBaseModel series) async {
|
||||
final syncNotifier = ref.read(syncProvider.notifier);
|
||||
final syncedItem = syncNotifier.getSyncedItem(series);
|
||||
if (syncedItem == null) return;
|
||||
final seriesModel = syncedItem.createItemModel(ref) as SeriesModel;
|
||||
final allChildren = syncedItem
|
||||
.getNestedChildren(ref)
|
||||
.map(
|
||||
(e) => e.createItemModel(ref),
|
||||
)
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
state = seriesModel.copyWith(
|
||||
availableEpisodes: allChildren.whereType<EpisodeModel>().toList(),
|
||||
seasons: allChildren.whereType<SeasonModel>().toList(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
void updateEpisodeInfo(EpisodeModel episode) {
|
||||
final index = state?.availableEpisodes?.indexOf(episode);
|
||||
|
||||
if (index != null) {
|
||||
state = state?.copyWith(
|
||||
availableEpisodes: state?.availableEpisodes
|
||||
?..remove(episode)
|
||||
..insert(index, episode),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue