mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
Init repo
This commit is contained in:
commit
764b6034e3
566 changed files with 212335 additions and 0 deletions
53
lib/android_tv/main.dart
Normal file
53
lib/android_tv/main.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/util/application_info.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() async {
|
||||
_setupLogging();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
MediaKit.ensureInitialized();
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
|
||||
applicationInfoProvider.overrideWith((ref) => ApplicationInfo(
|
||||
name: packageInfo.appName, version: packageInfo.version, os: defaultTargetPlatform.name.capitalize())),
|
||||
],
|
||||
child: const Main(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _setupLogging() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((rec) {
|
||||
if (kDebugMode) {
|
||||
print('${rec.level.name}: ${rec.time}: ${rec.message}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Main extends ConsumerWidget {
|
||||
const Main({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(child: Text("AndroidTV")),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
1
lib/jellyfin/client_index.dart
Normal file
1
lib/jellyfin/client_index.dart
Normal file
|
|
@ -0,0 +1 @@
|
|||
export 'jellyfin_open_api.swagger.dart' show JellyfinOpenApi;
|
||||
1
lib/jellyfin/client_mapping.dart
Normal file
1
lib/jellyfin/client_mapping.dart
Normal file
|
|
@ -0,0 +1 @@
|
|||
final Map<Type, Object Function(Map<String, dynamic>)> generatedMapping = {};
|
||||
47
lib/jellyfin/enum_models.dart
Normal file
47
lib/jellyfin/enum_models.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
enum MetadataRefresh {
|
||||
defaultRefresh(MetadataRefreshMode.$default),
|
||||
validation(MetadataRefreshMode.validationonly),
|
||||
fullRefresh(MetadataRefreshMode.fullrefresh);
|
||||
|
||||
const MetadataRefresh(this.api);
|
||||
|
||||
final MetadataRefreshMode api;
|
||||
String label(BuildContext context) {
|
||||
return switch (this) {
|
||||
defaultRefresh => context.localized.metadataRefreshDefault,
|
||||
validation => context.localized.metadataRefreshValidation,
|
||||
fullRefresh => context.localized.metadataRefreshFull,
|
||||
};
|
||||
}
|
||||
|
||||
ItemsItemIdRefreshPostMetadataRefreshMode? get metadataRefreshMode => switch (this) {
|
||||
MetadataRefresh.fullRefresh => ItemsItemIdRefreshPostMetadataRefreshMode.fullrefresh,
|
||||
MetadataRefresh.validation => ItemsItemIdRefreshPostMetadataRefreshMode.validationonly,
|
||||
_ => ItemsItemIdRefreshPostMetadataRefreshMode.$default
|
||||
};
|
||||
|
||||
ItemsItemIdRefreshPostImageRefreshMode? get imageRefreshMode => switch (this) {
|
||||
MetadataRefresh.fullRefresh => ItemsItemIdRefreshPostImageRefreshMode.fullrefresh,
|
||||
MetadataRefresh.validation => ItemsItemIdRefreshPostImageRefreshMode.validationonly,
|
||||
_ => ItemsItemIdRefreshPostImageRefreshMode.$default
|
||||
};
|
||||
}
|
||||
|
||||
enum ItemLocation {
|
||||
filesystem('FileSystem'),
|
||||
remote('Remote'),
|
||||
virtual('Virtual'),
|
||||
offline('Offline');
|
||||
|
||||
final String? value;
|
||||
|
||||
factory ItemLocation.fromDto(LocationType? type) =>
|
||||
ItemLocation.values.firstWhereOrNull((element) => type?.value == element.value) ?? ItemLocation.filesystem;
|
||||
|
||||
const ItemLocation(this.value);
|
||||
}
|
||||
5318
lib/jellyfin/jellyfin_open_api.enums.swagger.dart
Normal file
5318
lib/jellyfin/jellyfin_open_api.enums.swagger.dart
Normal file
File diff suppressed because it is too large
Load diff
10335
lib/jellyfin/jellyfin_open_api.swagger.chopper.dart
Normal file
10335
lib/jellyfin/jellyfin_open_api.swagger.chopper.dart
Normal file
File diff suppressed because it is too large
Load diff
65767
lib/jellyfin/jellyfin_open_api.swagger.dart
Normal file
65767
lib/jellyfin/jellyfin_open_api.swagger.dart
Normal file
File diff suppressed because it is too large
Load diff
9317
lib/jellyfin/jellyfin_open_api.swagger.g.dart
Normal file
9317
lib/jellyfin/jellyfin_open_api.swagger.g.dart
Normal file
File diff suppressed because it is too large
Load diff
669
lib/l10n/app_en.arb
Normal file
669
lib/l10n/app_en.arb
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"switchUser": "Switch user",
|
||||
"userName": "Username",
|
||||
"password": "Password",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"cancel": "Cancel",
|
||||
"accept": "Accept",
|
||||
"code": "Code",
|
||||
"error": "Error",
|
||||
"clear": "Clear",
|
||||
"days": "Days",
|
||||
"search": "Search",
|
||||
"loggedIn": "Logged-in",
|
||||
"change": "Change",
|
||||
"other": "Other",
|
||||
"dynamicText": "Dynamic",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"dashboard": "Dashboard",
|
||||
"advanced": "Advanced",
|
||||
"refresh": "Refresh",
|
||||
"delete": "Delete",
|
||||
"goTo": "Go To",
|
||||
"loop": "Loop",
|
||||
"empty": "Empty",
|
||||
"noRating": "No rating",
|
||||
"backgroundBlur": "Background blur",
|
||||
"autoPlay": "Auto-play",
|
||||
"resume": "Resume {item}",
|
||||
"@resume": {
|
||||
"description": "resume",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"play": "Play {item}",
|
||||
"@play": {
|
||||
"description": "Play with",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"read": "Read {item}",
|
||||
"@read": {
|
||||
"description": "read",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readFromStart": "Read {item} from start",
|
||||
"@readFromStart": {
|
||||
"description": "Read book from start",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFrom": "Play from {name}",
|
||||
"@playFrom": {
|
||||
"description": "playFrom",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFromStart": "Play {name} from the start",
|
||||
"@playFromStart": {
|
||||
"description": "speel vanaf het begin",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreFrom": "More from {info}",
|
||||
"@moreFrom": {
|
||||
"description": "More from",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedWith": "Selected {info}",
|
||||
"@selectedWith": {
|
||||
"description": "selected",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected": "Selected",
|
||||
"restart": "Restart",
|
||||
"reWatch": "Rewatch",
|
||||
"options": "Options",
|
||||
"list": "List",
|
||||
"grid": "Grid",
|
||||
"masonry": "Masonry",
|
||||
"start": "Start",
|
||||
"none": "None",
|
||||
"chapter": "{count, plural, other{Chapters} one{Chapter}}",
|
||||
"@chapter": {
|
||||
"description": "chapter",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"watchOn": "Watch on",
|
||||
"sync": "Sync",
|
||||
"moreOptions": "More options",
|
||||
"continuePage": "Continue - page {page}",
|
||||
"@continuePage": {
|
||||
"description": "Continue - page 1",
|
||||
"placeholders": {
|
||||
"page": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openShow": "Open show",
|
||||
"showDetails": "Show details",
|
||||
"showAlbum": "Show album",
|
||||
"season": "{count, plural, other{Seasons} one{Season} }",
|
||||
"@season": {
|
||||
"description": "season",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{Episodes} one{Episode} }",
|
||||
"@episode": {
|
||||
"description": "episode",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToCollection": "Add to collection",
|
||||
"addToPlaylist": "Add to playlist",
|
||||
"removeFromCollection": "Remove to collection",
|
||||
"removeFromPlaylist": "Remove to playlist",
|
||||
"markAsWatched": "Mark as watched",
|
||||
"markAsUnwatched": "Mark as unwatched",
|
||||
"removeAsFavorite": "Remove as favorite",
|
||||
"addAsFavorite": "Add as favorite",
|
||||
"editMetadata": "Edit metadata",
|
||||
"refreshMetadata": "Refresh metadata",
|
||||
"syncDetails": "Sync details",
|
||||
"identify": "Identify",
|
||||
"info": "Info",
|
||||
"clearAllSettings": "Clear all settings",
|
||||
"clearAllSettingsQuestion": "Clear all settings?",
|
||||
"unableToReverseAction": "This action can not be reversed, it will remove all settings.",
|
||||
"navigationDashboard": "Dashboard",
|
||||
"navigationFavorites": "Favorites",
|
||||
"navigationSync": "Synced",
|
||||
"navigation": "Navigation",
|
||||
"library": "{count, plural, other{Libraries} one{Library}}",
|
||||
"@library": {
|
||||
"description": "Plural",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanLibrary": "Scan library",
|
||||
"dashboardContinue": "Continue",
|
||||
"dashboardContinueWatching": "Continue Watching",
|
||||
"dashboardContinueReading": "Continue Reading",
|
||||
"dashboardContinueListening": "Continue Listening",
|
||||
"dashboardNextUp": "Next-up",
|
||||
"dashboardRecentlyAdded": "Recently added in {name}",
|
||||
"@dashboardRecentlyAdded": {
|
||||
"description": "Recently added on home screen",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": "Settings",
|
||||
"settingsClientTitle": "Fladder",
|
||||
"settingsClientDesc": "General, Time-out, Layout, Theme",
|
||||
"settingsQuickConnectTitle": "Quick connect",
|
||||
"settingsProfileTitle": "Profile",
|
||||
"settingsProfileDesc": "Lockscreen",
|
||||
"settingsPlayerTitle": "Player",
|
||||
"settingsPlayerDesc": "Aspect-ratio, Advanced",
|
||||
"logoutUserPopupTitle": "Log-out user {userName}?",
|
||||
"@logoutUserPopupTitle": {
|
||||
"description": "Pop-up for loging out the user",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logoutUserPopupContent": "This will log-out {userName} and delete te user from the app.\nYou will have to log back in to {serverName}.",
|
||||
"@logoutUserPopupContent": {
|
||||
"description": "Pop-up for loging out the user description",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
},
|
||||
"serverName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quickConnectTitle": "Quick-connect",
|
||||
"quickConnectAction": "Enter quick connect code for",
|
||||
"quickConnectInputACode": "Input a code",
|
||||
"quickConnectWrongCode": "Wrong code",
|
||||
"downloadsTitle": "Downloads",
|
||||
"downloadsPath": "Path",
|
||||
"pathEditTitle": "Change location",
|
||||
"pathEditSelect": "Select downloads destination",
|
||||
"pathClearTitle": "Clear downloads path",
|
||||
"pathEditDesc": "This location is set for all users, any synced data will no longer be accessible.\nIt will remain on your storage.",
|
||||
"downloadsSyncedData": "Synced data",
|
||||
"downloadsClearTitle": "Clear synced data",
|
||||
"downloadsClearDesc": "Are you sure you want to remove all synced data?\nThis will clear all data for every synced user!",
|
||||
"lockscreen": "Lockscreen",
|
||||
"timeOut": "Time-out",
|
||||
"home": "Home",
|
||||
"settingsHomeCarouselTitle": "Dashboard carousel",
|
||||
"settingsHomeCarouselDesc": "Shows a carousel on the dashboard screen",
|
||||
"settingsHomeNextUpTitle": "Next-up posters",
|
||||
"settingsHomeNextUpDesc": "Type of posters shown in the dashboard screen",
|
||||
"settingsVisual": "Visual",
|
||||
"settingsBlurredPlaceholderTitle": "Blurred placeholder",
|
||||
"settingsBlurredPlaceholderDesc": "Show blurred background when loading posters",
|
||||
"settingsBlurEpisodesTitle": "Blur next-up episodes",
|
||||
"settingsBlurEpisodesDesc": "Blur all upcoming episodes",
|
||||
"settingsEnableOsMediaControls": "Enable OS media controls",
|
||||
"settingsNextUpCutoffDays": "Next-up cutoff days",
|
||||
"settingsShowScaleSlider": "Show poster size slide",
|
||||
"settingsPosterSize": "Poster size",
|
||||
"settingsPosterSlider": "Show scale slider",
|
||||
"settingsPosterPinch": "Pinch-zoom to scale posters",
|
||||
"theme": "Theme",
|
||||
"mode": "Mode",
|
||||
"themeModeSystem": "System",
|
||||
"themeModeLight": "Light",
|
||||
"themeModeDark": "Dark",
|
||||
"themeColor": "Theme color",
|
||||
"color": "Color",
|
||||
"amoledBlack": "Amoled black",
|
||||
"hide": "Hide",
|
||||
"nextUp": "Next Up",
|
||||
"settingsContinue": "Continue",
|
||||
"separate": "Separate",
|
||||
"combined": "Combined",
|
||||
"settingsSecurity": "Security",
|
||||
"settingSecurityApplockTitle": "App lock",
|
||||
"appLockTitle": "Set the log-in method for {userName}",
|
||||
"@appLockTitle": {
|
||||
"description": "Pop-up to pick a login method",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"biometricsFailedCheckAgain": "Biometrics failed check settings and try again",
|
||||
"appLockAutoLogin": "Auto login",
|
||||
"appLockPasscode": "Passcode",
|
||||
"appLockBiometrics": "Biometrics",
|
||||
"settingsPlayerVideoHWAccelTitle": "Hardware acceleration",
|
||||
"settingsPlayerVideoHWAccelDesc": "Use the gpu to render video (recommended)",
|
||||
"settingsPlayerNativeLibassAccelTitle": "Native libass subtitles",
|
||||
"settingsPlayerNativeLibassAccelDesc": "Use video player libass subtitle renderer",
|
||||
"settingsPlayerMobileWarning": "Enabling Hardware acceleration and native libass subtitles on Android might cause some subtitles to not render.",
|
||||
"settingsPlayerCustomSubtitlesTitle": "Customize subtitles",
|
||||
"settingsPlayerCustomSubtitlesDesc": "Customize Size, Color, Position, Outline",
|
||||
"videoScalingFillScreenTitle": "Fill screen",
|
||||
"videoScalingFillScreenDesc": "Fill the navigation and statusbar",
|
||||
"videoScalingFillScreenNotif": "Fill-screen overwrites video fit, in horizontal rotation",
|
||||
"videoScaling": "Video scaling",
|
||||
"videoScalingFill": "Fill",
|
||||
"videoScalingContain": "Contain",
|
||||
"videoScalingCover": "Cover",
|
||||
"videoScalingFitWidth": "Fit Width",
|
||||
"videoScalingFitHeight": "Fit Height",
|
||||
"videoScalingScaleDown": "ScaleDown",
|
||||
"subtitleConfiguratorPlaceHolder": "This is placeholder text, \n nothing to see here.",
|
||||
"subtitleConfigurator": "Subtitle configurator",
|
||||
"refreshPopup": "Refresh - {name}",
|
||||
"@refreshPopup": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanningName": "Scanning - {name}",
|
||||
"@scanningName": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshPopupContentMetadata": "Metadata is refreshed based on settings and internet services that are enabled in the Dashboard.",
|
||||
"replaceExistingImages": "Replace existing images",
|
||||
"metadataRefreshDefault": "Scan for new and updated files",
|
||||
"metadataRefreshValidation": "Search for missing metadata",
|
||||
"metadataRefreshFull": "Replace all metadata",
|
||||
"syncedItems": "Synced items",
|
||||
"noItemsSynced": "No items synced",
|
||||
"syncDeletePopupPermanent": "This action is permanent and will remove all localy synced files",
|
||||
"totalSize": "Total size: {size}",
|
||||
"@totalSize": {
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mediaTypeBase": "Base Type",
|
||||
"mediaTypeMovie": "Movie",
|
||||
"mediaTypeSeries": "Series",
|
||||
"mediaTypeSeason": "Season",
|
||||
"mediaTypeEpisode": "Episode",
|
||||
"mediaTypePhoto": "Photo",
|
||||
"mediaTypePerson": "Person",
|
||||
"mediaTypePhotoAlbum": "Photo Album",
|
||||
"mediaTypeFolder": "Folder",
|
||||
"mediaTypeBoxset": "Boxset",
|
||||
"mediaTypePlaylist": "Playlist",
|
||||
"mediaTypeBook": "Book",
|
||||
"actor": "{count, plural, other{Actors} one{Actor}}",
|
||||
"@actor": {
|
||||
"description": "actor",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"writer": "{count, plural, other{Writer} two{Writers}}",
|
||||
"@writer": {
|
||||
"description": "writer",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": "{count, plural, other{Director} two{Directors}}",
|
||||
"@director": {
|
||||
"description": "director",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": "Video",
|
||||
"audio": "Audio",
|
||||
"subtitles": "Subtitles",
|
||||
"related": "Related",
|
||||
"all": "All",
|
||||
"overview": "Overview",
|
||||
"selectViewType": "Select view type",
|
||||
"noItemsToShow": "No items to show",
|
||||
"sortBy": "Sort by",
|
||||
"groupBy": "Group by",
|
||||
"scrollToTop": "Scroll to top",
|
||||
"disableFilters": "Disable filters",
|
||||
"selectAll": "Select all",
|
||||
"clearSelection": "Clear selection",
|
||||
"shuffleVideos": "Shuffle videos",
|
||||
"shuffleGallery": "Shuffle gallery",
|
||||
"unknown": "Unknown",
|
||||
"favorites": "Favorites",
|
||||
"recursive": "Recursive",
|
||||
"genre": "{count, plural, other{Genres} one{Genre}}",
|
||||
"@genre": {
|
||||
"description": "genre",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"studio": "{count, plural, other{Studios} one{Studio}}",
|
||||
"@studio": {
|
||||
"description": "studio",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "{count, plural, other{Labels} one{Label}}",
|
||||
"@label": {
|
||||
"description": "label",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": "Group",
|
||||
"type": "{count, plural, other{Types} one{Type}}",
|
||||
"@type": {
|
||||
"description": "type",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "{count, plural, other{Filters} one{Filter}}",
|
||||
"@filter": {
|
||||
"description": "filter",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showEmpty": "Show empty",
|
||||
"hideEmpty": "Hide empty",
|
||||
"rating": "{count, plural, other{Ratings} one{Rating}}",
|
||||
"@rating": {
|
||||
"description": "rating",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"year": "{count, plural, other{Years} one{Year}}",
|
||||
"@year": {
|
||||
"description": "year",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playVideos": "Play videos",
|
||||
"playLabel": "Play",
|
||||
"forceRefresh": "Force refresh",
|
||||
"itemCount": "Item count: {count}",
|
||||
"@itemCount": {
|
||||
"description": "Item count",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "Invalid url",
|
||||
"invalidUrlDesc": "Url needs to start with http(s)://",
|
||||
"incorrectPinTryAgain": "Incorrect pin try again",
|
||||
"somethingWentWrongPasswordCheck": "Something went wrong, check your password",
|
||||
"unableToConnectHost": "Unable to connect to host",
|
||||
"server": "Server",
|
||||
"retrievePublicListOfUsers": "Retrieve public list of users",
|
||||
"displayLanguage": "Display language",
|
||||
"deleteItem": "Delete {item}?",
|
||||
"@deleteItem": {
|
||||
"description": "deleteItem",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncDeleteItemTitle": "Delete synced item",
|
||||
"syncDeleteItemDesc": "Delete all synced data for?\n{item}",
|
||||
"@syncDeleteItemDesc": {
|
||||
"description": "Sync delete item pop-up window",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncOpenParent": "Open parent",
|
||||
"syncRemoveDataTitle": "Remove synced data?",
|
||||
"syncRemoveDataDesc": "Delete synced video data? This is permanent and you will need to re-sync the files",
|
||||
"collectionFolder": "Collection folder",
|
||||
"musicAlbum": "Album",
|
||||
"active": "Active",
|
||||
"name": "Name",
|
||||
"result": "Result",
|
||||
"close": "Close",
|
||||
"replaceAllImages": "Replace all images",
|
||||
"noResults": "No results",
|
||||
"openWebLink": "Open web link",
|
||||
"setIdentityTo": "Set identity to {name}",
|
||||
"@setIdentityTo": {
|
||||
"description": "setIdentityTo",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"clearChanges": "Clear changes",
|
||||
"useDefaults": "Use defaults",
|
||||
"light": "Light",
|
||||
"normal": "Normal",
|
||||
"bold": "Bold",
|
||||
"fontSize": "Font size",
|
||||
"heightOffset": "Height offset",
|
||||
"fontColor": "Font color",
|
||||
"outlineColor": "Outline color",
|
||||
"outlineSize": "Outline size",
|
||||
"backgroundOpacity": "Background opacity",
|
||||
"shadow": "Shadow",
|
||||
"played": "Played",
|
||||
"unPlayed": "Unplayed",
|
||||
"resumable": "Resumable",
|
||||
"sortOrder": "Sort order",
|
||||
"sortName": "Name",
|
||||
"communityRating": "Community Rating",
|
||||
"parentalRating": "Parental Rating",
|
||||
"dateAdded": "Date added",
|
||||
"dateLastContentAdded": "Date last content added",
|
||||
"favorite": "Favorite",
|
||||
"datePlayed": "Date played",
|
||||
"folders": "Folders",
|
||||
"playCount": "Play count",
|
||||
"releaseDate": "Release date",
|
||||
"runTime": "Run time",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"minutes": "{count, plural, other{Minutes} one{Minute} }",
|
||||
"@minutes": {
|
||||
"description": "minute",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds": "{count, plural, other{Seconds} one{Second}}",
|
||||
"@seconds": {
|
||||
"description": "second",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": "Page {index}",
|
||||
"@page": {
|
||||
"description": "page",
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set": "Set",
|
||||
"@set": {
|
||||
"description": "Use for setting a certain value",
|
||||
"context": "Set 'time'"
|
||||
},
|
||||
"never": "Never",
|
||||
"selectTime": "Select time",
|
||||
"immediately": "Immediately",
|
||||
"timeAndAnnotation": "{minutes} and {seconds}",
|
||||
"@timeAndAnnotation": {
|
||||
"description": "timeAndAnnotation",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "String"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanYourFingerprintToAuthenticate": "Scan your fingerprint to authenticate {user}",
|
||||
"@scanYourFingerprintToAuthenticate": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanBiometricHint": "Verify identity",
|
||||
"deleteFileFromSystem": "Deleting this item {item} will delete it from both the file system and your media library.\nAre you sure you wish to continue?",
|
||||
"@deleteFileFromSystem": {
|
||||
"description": "Delete file from system",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notPartOfAlbum": "Not part of a album",
|
||||
"retry": "Retry",
|
||||
"failedToLoadImage": "Failed to load image",
|
||||
"save": "Save",
|
||||
"metaDataSavedFor": "Metadata saved for {item}",
|
||||
"@metaDataSavedFor": {
|
||||
"description": "metaDataSavedFor",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraryPageSizeTitle": "Library page size",
|
||||
"libraryPageSizeDesc": "Set the amount to load at a time. 0 disables paging",
|
||||
"fetchingLibrary": "Fetching library items",
|
||||
"libraryFetchNoItemsFound": "No items found, try different settings.",
|
||||
"noSuggestionsFound": "No suggestions found",
|
||||
"viewPhotos": "View photos",
|
||||
"random": "Random",
|
||||
"tag": "{count, plural, one{Tag} other{Tags}}",
|
||||
"@tag": {
|
||||
"description": "tag",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": "Saved",
|
||||
"discovered": "Discovered",
|
||||
"noServersFound": "No new servers found",
|
||||
"about": "About",
|
||||
"openParent": "Open parent",
|
||||
"mouseDragSupport": "Drag using mouse",
|
||||
"controls": "Controls"
|
||||
}
|
||||
669
lib/l10n/app_es.arb
Normal file
669
lib/l10n/app_es.arb
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"switchUser": "Cambiar usuario",
|
||||
"userName": "Nombre de usuario",
|
||||
"password": "Contraseña",
|
||||
"login": "Iniciar sesión",
|
||||
"logout": "Cerrar sesión",
|
||||
"cancel": "Cancelar",
|
||||
"accept": "Aceptar",
|
||||
"code": "Código",
|
||||
"error": "Error",
|
||||
"clear": "Borrar",
|
||||
"days": "Días",
|
||||
"search": "Buscar",
|
||||
"loggedIn": "Conectado",
|
||||
"change": "Cambiar",
|
||||
"other": "Otro",
|
||||
"dynamicText": "Dinámico",
|
||||
"enabled": "Habilitado",
|
||||
"disabled": "Deshabilitado",
|
||||
"dashboard": "Tablero",
|
||||
"advanced": "Avanzado",
|
||||
"refresh": "Actualizar",
|
||||
"delete": "Eliminar",
|
||||
"goTo": "Ir a",
|
||||
"loop": "Bucle",
|
||||
"empty": "Vacío",
|
||||
"noRating": "Sin calificación",
|
||||
"backgroundBlur": "Desenfoque de fondo",
|
||||
"autoPlay": "Reproducción automática",
|
||||
"resume": "Reanudar {item}",
|
||||
"@resume": {
|
||||
"description": "reanudar",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"play": "Reproducir {item}",
|
||||
"@play": {
|
||||
"description": "Reproducir con",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"read": "Leer {item}",
|
||||
"@read": {
|
||||
"description": "leer",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readFromStart": "Leer {item} desde el principio",
|
||||
"@readFromStart": {
|
||||
"description": "Leer libro desde el principio",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFrom": "Reproducir desde {name}",
|
||||
"@playFrom": {
|
||||
"description": "reproducir desde",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreFrom": "Más de {info}",
|
||||
"@moreFrom": {
|
||||
"description": "Más de",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedWith": "Seleccionado {info}",
|
||||
"@selectedWith": {
|
||||
"description": "seleccionado",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected": "Seleccionado",
|
||||
"restart": "Reiniciar",
|
||||
"reWatch": "Volver a ver",
|
||||
"options": "Opciones",
|
||||
"list": "Lista",
|
||||
"grid": "Cuadrícula",
|
||||
"masonry": "Mampostería",
|
||||
"start": "Iniciar",
|
||||
"none": "Ninguno",
|
||||
"chapter": "{count, plural, other{Capítulos} one{Capítulo}}",
|
||||
"@chapter": {
|
||||
"description": "capítulo",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"watchOn": "Ver en",
|
||||
"sync": "Sincronizar",
|
||||
"moreOptions": "Más opciones",
|
||||
"continuePage": "Continuar - página {page}",
|
||||
"@continuePage": {
|
||||
"description": "Continuar - página 1",
|
||||
"placeholders": {
|
||||
"page": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openShow": "Abrir espectáculo",
|
||||
"showDetails": "Mostrar detalles",
|
||||
"showAlbum": "Mostrar álbum",
|
||||
"season": "{count, plural, other{Temporadas} one{Temporada}}",
|
||||
"@season": {
|
||||
"description": "temporada",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{Episodios} one{Episodio}}",
|
||||
"@episode": {
|
||||
"description": "episodio",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToCollection": "Agregar a la colección",
|
||||
"addToPlaylist": "Agregar a la lista de reproducción",
|
||||
"removeFromCollection": "Eliminar de la colección",
|
||||
"removeFromPlaylist": "Eliminar de la lista de reproducción",
|
||||
"markAsWatched": "Marcar como visto",
|
||||
"markAsUnwatched": "Marcar como no visto",
|
||||
"removeAsFavorite": "Eliminar como favorito",
|
||||
"addAsFavorite": "Agregar como favorito",
|
||||
"editMetadata": "Editar metadatos",
|
||||
"refreshMetadata": "Actualizar metadatos",
|
||||
"syncDetails": "Sincronizar detalles",
|
||||
"identify": "Identificar",
|
||||
"info": "Información",
|
||||
"clearAllSettings": "Borrar todos los ajustes",
|
||||
"clearAllSettingsQuestion": "¿Borrar todos los ajustes?",
|
||||
"unableToReverseAction": "Esta acción no se puede deshacer, eliminará todos los ajustes.",
|
||||
"navigationDashboard": "Tablero",
|
||||
"navigationFavorites": "Favoritos",
|
||||
"navigationSync": "Sincronizado",
|
||||
"navigation": "Navegación",
|
||||
"library": "{count, plural, other{Bibliotecas} one{Biblioteca}}",
|
||||
"@library": {
|
||||
"description": "plural",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanLibrary": "Escanear biblioteca",
|
||||
"dashboardContinue": "Continuar",
|
||||
"dashboardContinueWatching": "Continuar viendo",
|
||||
"dashboardContinueReading": "Continuar leyendo",
|
||||
"dashboardContinueListening": "Continuar escuchando",
|
||||
"dashboardNextUp": "Próximo",
|
||||
"dashboardRecentlyAdded": "Recientemente añadido en {name}",
|
||||
"@dashboardRecentlyAdded": {
|
||||
"description": "Recientemente añadido en la pantalla de inicio",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": "Ajustes",
|
||||
"settingsClientTitle": "Fladder",
|
||||
"settingsClientDesc": "General, Tiempo de espera, Diseño, Tema",
|
||||
"settingsQuickConnectTitle": "Conexión rápida",
|
||||
"settingsProfileTitle": "Perfil",
|
||||
"settingsProfileDesc": "Pantalla de bloqueo",
|
||||
"settingsPlayerTitle": "Reproductor",
|
||||
"settingsPlayerDesc": "Relación de aspecto, Avanzado",
|
||||
"logoutUserPopupTitle": "¿Cerrar sesión del usuario {userName}?",
|
||||
"@logoutUserPopupTitle": {
|
||||
"description": "Pop-up para cerrar sesión del usuario",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logoutUserPopupContent": "Esto cerrará la sesión de {userName} y eliminará al usuario de la aplicación.\nDeberá volver a iniciar sesión en {serverName}.",
|
||||
"@logoutUserPopupContent": {
|
||||
"description": "Pop-up para cerrar sesión del usuario descripción",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
},
|
||||
"serverName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quickConnectTitle": "Conexión rápida",
|
||||
"quickConnectAction": "Ingrese el código de conexión rápida para",
|
||||
"quickConnectInputACode": "Ingrese un código",
|
||||
"quickConnectWrongCode": "Código incorrecto",
|
||||
"downloadsTitle": "Descargas",
|
||||
"downloadsPath": "Ruta",
|
||||
"pathEditTitle": "Cambiar ubicación",
|
||||
"pathEditSelect": "Seleccionar destino de descargas",
|
||||
"pathClearTitle": "Borrar ruta de descargas",
|
||||
"pathEditDesc": "Esta ubicación se establece para todos los usuarios, los datos sincronizados ya no serán accesibles.\nPermanecerán en su almacenamiento.",
|
||||
"downloadsSyncedData": "Datos sincronizados",
|
||||
"downloadsClearTitle": "Borrar datos sincronizados",
|
||||
"downloadsClearDesc": "¿Está seguro de que desea eliminar todos los datos sincronizados?\n¡Esto eliminará todos los datos de cada usuario sincronizado!",
|
||||
"lockscreen": "Pantalla de bloqueo",
|
||||
"timeOut": "Tiempo de espera",
|
||||
"home": "Inicio",
|
||||
"settingsHomeCarouselTitle": "Carrusel del tablero",
|
||||
"settingsHomeCarouselDesc": "Muestra un carrusel en la pantalla del tablero",
|
||||
"settingsHomeNextUpTitle": "Próximos pósteres",
|
||||
"settingsHomeNextUpDesc": "Tipo de pósteres mostrados en la pantalla del tablero",
|
||||
"settingsVisual": "Visual",
|
||||
"settingsBlurredPlaceholderTitle": "Marcador de posición difuminado",
|
||||
"settingsBlurredPlaceholderDesc": "Mostrar fondo difuminado al cargar pósteres",
|
||||
"settingsBlurEpisodesTitle": "Difuminar próximos episodios",
|
||||
"settingsBlurEpisodesDesc": "Difuminar todos los próximos episodios",
|
||||
"settingsEnableOsMediaControls": "Habilitar controles multimedia del sistema operativo",
|
||||
"settingsNextUpCutoffDays": "Días límite de próximos",
|
||||
"settingsShowScaleSlider": "Mostrar control deslizante de tamaño de póster",
|
||||
"settingsPosterSize": "Tamaño del póster",
|
||||
"settingsPosterSlider": "Mostrar control deslizante de escala",
|
||||
"settingsPosterPinch": "Pellizcar para escalar pósteres",
|
||||
"theme": "Tema",
|
||||
"mode": "Modo",
|
||||
"themeModeSystem": "Sistema",
|
||||
"themeModeLight": "Claro",
|
||||
"themeModeDark": "Oscuro",
|
||||
"themeColor": "Color del tema",
|
||||
"color": "Color",
|
||||
"amoledBlack": "Negro amoled",
|
||||
"hide": "Ocultar",
|
||||
"nextUp": "Próximos",
|
||||
"settingsContinue": "Continuar",
|
||||
"separate": "Separado",
|
||||
"combined": "Combinado",
|
||||
"settingsSecurity": "Seguridad",
|
||||
"settingSecurityApplockTitle": "Bloqueo de aplicación",
|
||||
"appLockTitle": "Establecer el método de inicio de sesión para {userName}",
|
||||
"@appLockTitle": {
|
||||
"description": "Pop-up para elegir un método de inicio de sesión",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"biometricsFailedCheckAgain": "Falló la biometría, verifique la configuración y vuelva a intentarlo",
|
||||
"appLockAutoLogin": "Inicio de sesión automático",
|
||||
"appLockPasscode": "Código de acceso",
|
||||
"appLockBiometrics": "Biometría",
|
||||
"settingsPlayerVideoHWAccelTitle": "Aceleración de hardware",
|
||||
"settingsPlayerVideoHWAccelDesc": "Usar la gpu para renderizar video (recomendado)",
|
||||
"settingsPlayerNativeLibassAccelTitle": "Subtítulos nativos libass",
|
||||
"settingsPlayerNativeLibassAccelDesc": "Usar el renderizador de subtítulos libass del reproductor de video",
|
||||
"settingsPlayerMobileWarning": "Habilitar la aceleración de hardware y los subtítulos libass nativos en Android puede hacer que algunos subtítulos no se rendericen.",
|
||||
"settingsPlayerCustomSubtitlesTitle": "Personalizar subtítulos",
|
||||
"settingsPlayerCustomSubtitlesDesc": "Personalizar tamaño, color, posición, contorno",
|
||||
"videoScalingFillScreenTitle": "Llenar pantalla",
|
||||
"videoScalingFillScreenDesc": "Llenar la barra de navegación y la barra de estado",
|
||||
"videoScalingFillScreenNotif": "Llenar pantalla sobrescribe ajuste de video, en rotación horizontal",
|
||||
"videoScaling": "Escalado de video",
|
||||
"videoScalingFill": "Llenar",
|
||||
"videoScalingContain": "Contener",
|
||||
"videoScalingCover": "Cubrir",
|
||||
"videoScalingFitWidth": "Ajustar ancho",
|
||||
"videoScalingFitHeight": "Ajustar altura",
|
||||
"videoScalingScaleDown": "Reducir escala",
|
||||
"subtitleConfiguratorPlaceHolder": "Este es un texto de marcador de posición,\nnada que ver aquí.",
|
||||
"subtitleConfigurator": "Configurador de subtítulos",
|
||||
"refreshPopup": "Actualizar - {name}",
|
||||
"@refreshPopup": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanningName": "Escaneando - {name}",
|
||||
"@scanningName": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshPopupContentMetadata": "Los metadatos se actualizan según la configuración y los servicios de Internet que están habilitados en el Tablero.",
|
||||
"replaceExistingImages": "Reemplazar imágenes existentes",
|
||||
"metadataRefreshDefault": "Buscar archivos nuevos y actualizados",
|
||||
"metadataRefreshValidation": "Buscar metadatos faltantes",
|
||||
"metadataRefreshFull": "Reemplazar todos los metadatos",
|
||||
"syncedItems": "Elementos sincronizados",
|
||||
"noItemsSynced": "No hay elementos sincronizados",
|
||||
"syncDeletePopupPermanent": "Esta acción es permanente y eliminará todos los archivos sincronizados localmente",
|
||||
"totalSize": "Tamaño total: {size}",
|
||||
"@totalSize": {
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mediaTypeBase": "Tipo base",
|
||||
"mediaTypeMovie": "Película",
|
||||
"mediaTypeSeries": "Serie",
|
||||
"mediaTypeSeason": "Temporada",
|
||||
"mediaTypeEpisode": "Episodio",
|
||||
"mediaTypePhoto": "Foto",
|
||||
"mediaTypePerson": "Persona",
|
||||
"mediaTypePhotoAlbum": "Álbum de fotos",
|
||||
"mediaTypeFolder": "Carpeta",
|
||||
"mediaTypeBoxset": "Conjunto de cajas",
|
||||
"mediaTypePlaylist": "Lista de reproducción",
|
||||
"mediaTypeBook": "Libro",
|
||||
"actor": "{count, plural, other{Actores} one{Actor}}",
|
||||
"@actor": {
|
||||
"description": "actor",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"writer": "{count, plural, other{Escritores} one{Escritor}}",
|
||||
"@writer": {
|
||||
"description": "escritor",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": "{count, plural, other{Directores} one{Director}}",
|
||||
"@director": {
|
||||
"description": "director",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": "Video",
|
||||
"audio": "Audio",
|
||||
"subtitles": "Subtítulos",
|
||||
"related": "Relacionado",
|
||||
"all": "Todo",
|
||||
"overview": "Visión general",
|
||||
"selectViewType": "Seleccionar tipo de vista",
|
||||
"noItemsToShow": "No hay elementos para mostrar",
|
||||
"sortBy": "Ordenar por",
|
||||
"groupBy": "Agrupar por",
|
||||
"scrollToTop": "Desplazarse hacia arriba",
|
||||
"disableFilters": "Deshabilitar filtros",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"clearSelection": "Borrar selección",
|
||||
"shuffleVideos": "Videos aleatorios",
|
||||
"shuffleGallery": "Galería aleatoria",
|
||||
"unknown": "Desconocido",
|
||||
"favorites": "Favoritos",
|
||||
"recursive": "Recursivo",
|
||||
"genre": "{count, plural, other{Géneros} one{Género}}",
|
||||
"@genre": {
|
||||
"description": "género",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"studio": "{count, plural, other{Estudios} one{Estudio}}",
|
||||
"@studio": {
|
||||
"description": "estudio",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "{count, plural, other{Etiquetas} one{Etiqueta}}",
|
||||
"@label": {
|
||||
"description": "etiqueta",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": "Grupo",
|
||||
"type": "{count, plural, other{Tipos} one{Tipo}}",
|
||||
"@type": {
|
||||
"description": "tipo",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "{count, plural, other{Filtros} one{Filtro}}",
|
||||
"@filter": {
|
||||
"description": "filtro",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showEmpty": "Mostrar vacío",
|
||||
"hideEmpty": "Ocultar vacío",
|
||||
"rating": "{count, plural, other{Calificaciones} one{Calificación}}",
|
||||
"@rating": {
|
||||
"description": "calificación",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"year": "{count, plural, other{Años} one{Año}}",
|
||||
"@year": {
|
||||
"description": "año",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playVideos": "Reproducir videos",
|
||||
"playLabel": "Reproducir",
|
||||
"forceRefresh": "Actualizar a la fuerza",
|
||||
"itemCount": "Cantidad de elementos: {count}",
|
||||
"@itemCount": {
|
||||
"description": "Cantidad de elementos",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "URL inválida",
|
||||
"invalidUrlDesc": "La URL debe comenzar con http(s)://",
|
||||
"incorrectPinTryAgain": "PIN incorrecto, intente nuevamente",
|
||||
"somethingWentWrongPasswordCheck": "Algo salió mal, verifique su contraseña",
|
||||
"unableToConnectHost": "No se puede conectar al host",
|
||||
"server": "Servidor",
|
||||
"retrievePublicListOfUsers": "Recuperar lista pública de usuarios",
|
||||
"displayLanguage": "Idioma de visualización",
|
||||
"deleteItem": "¿Eliminar {item}?",
|
||||
"@deleteItem": {
|
||||
"description": "deleteItem",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncDeleteItemTitle": "Eliminar elemento sincronizado",
|
||||
"syncDeleteItemDesc": "¿Eliminar todos los datos sincronizados de?\n{item}",
|
||||
"@syncDeleteItemDesc": {
|
||||
"description": "Ventana emergente de eliminación de elemento sincronizado",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncOpenParent": "Abrir padre",
|
||||
"syncRemoveDataTitle": "¿Eliminar datos sincronizados?",
|
||||
"syncRemoveDataDesc": "¿Eliminar los datos de video sincronizados? Esto es permanente y necesitarás volver a sincronizar los archivos",
|
||||
"collectionFolder": "Carpeta de colección",
|
||||
"musicAlbum": "Álbum",
|
||||
"active": "Activo",
|
||||
"name": "Nombre",
|
||||
"result": "Resultado",
|
||||
"close": "Cerrar",
|
||||
"replaceAllImages": "Reemplazar todas las imágenes",
|
||||
"noResults": "Sin resultados",
|
||||
"openWebLink": "Abrir enlace web",
|
||||
"setIdentityTo": "Establecer identidad a {name}",
|
||||
"@setIdentityTo": {
|
||||
"description": "setIdentityTo",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"somethingWentWrong": "Algo salió mal",
|
||||
"clearChanges": "Borrar cambios",
|
||||
"useDefaults": "Usar valores predeterminados",
|
||||
"light": "Ligero",
|
||||
"normal": "Normal",
|
||||
"bold": "Negrita",
|
||||
"fontSize": "Tamaño de fuente",
|
||||
"heightOffset": "Desplazamiento de altura",
|
||||
"fontColor": "Color de fuente",
|
||||
"outlineColor": "Color del contorno",
|
||||
"outlineSize": "Tamaño del contorno",
|
||||
"backgroundOpacity": "Opacidad de fondo",
|
||||
"shadow": "Sombra",
|
||||
"played": "Reproducido",
|
||||
"unPlayed": "No reproducido",
|
||||
"resumable": "Reanudable",
|
||||
"sortOrder": "Orden de clasificación",
|
||||
"sortName": "Nombre",
|
||||
"communityRating": "Calificación de la comunidad",
|
||||
"parentalRating": "Calificación parental",
|
||||
"dateAdded": "Fecha de añadido",
|
||||
"dateLastContentAdded": "Fecha del último contenido añadido",
|
||||
"favorite": "Favorito",
|
||||
"datePlayed": "Fecha de reproducción",
|
||||
"folders": "Carpetas",
|
||||
"playCount": "Conteo de reproducciones",
|
||||
"releaseDate": "Fecha de lanzamiento",
|
||||
"runTime": "Duración",
|
||||
"ascending": "Ascendente",
|
||||
"descending": "Descendente",
|
||||
"playFromStart": "Reproducir {name} desde el principio",
|
||||
"@playFromStart": {
|
||||
"description": "reproducir desde el inicio",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minutes": "{count, plural, other{Minutos} one{Minuto}}",
|
||||
"@minutes": {
|
||||
"description": "minuto",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds": "{count, plural, other{Segundos} one{Segundo}}",
|
||||
"@seconds": {
|
||||
"description": "segundo",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": "Página {index}",
|
||||
"@page": {
|
||||
"description": "página",
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set": "Establecer",
|
||||
"@set": {
|
||||
"description": "Usar para establecer un cierto valor",
|
||||
"context": "Establecer 'tiempo'"
|
||||
},
|
||||
"never": "Nunca",
|
||||
"selectTime": "Seleccionar hora",
|
||||
"immediately": "Inmediatamente",
|
||||
"timeAndAnnotation": "{minutes} y {seconds}",
|
||||
"@timeAndAnnotation": {
|
||||
"description": "timeAndAnnotation",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "String"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanYourFingerprintToAuthenticate": "Escanea tu huella digital para autenticar a {user}",
|
||||
"@scanYourFingerprintToAuthenticate": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanBiometricHint": "Verificar identidad",
|
||||
"deleteFileFromSystem": "Eliminar este elemento {item} lo eliminará tanto del sistema de archivos como de su biblioteca multimedia.\n¿Estás seguro de que deseas continuar?",
|
||||
"@deleteFileFromSystem": {
|
||||
"description": "Eliminar archivo del sistema",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notPartOfAlbum": "No forma parte de un álbum",
|
||||
"retry": "Reintentar",
|
||||
"failedToLoadImage": "Error al cargar la imagen",
|
||||
"save": "Guardar",
|
||||
"metaDataSavedFor": "Metadatos guardados para {item}",
|
||||
"@metaDataSavedFor": {
|
||||
"description": "metadatosGuardadosPara",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraryPageSizeTitle": "Tamaño de página de la biblioteca",
|
||||
"libraryPageSizeDesc": "Establece la cantidad a cargar a la vez. 0 desactiva el paginado.",
|
||||
"fetchingLibrary": "Cargando elementos de la biblioteca",
|
||||
"libraryFetchNoItemsFound": "No se encontraron elementos, intenta con diferentes configuraciones.",
|
||||
"noSuggestionsFound": "No se encontraron sugerencias",
|
||||
"viewPhotos": "Ver fotos",
|
||||
"random": "Aleatorio",
|
||||
"tag": "{count, plural, one{Etiqueta} other{Etiquetas}}",
|
||||
"@tag": {
|
||||
"description": "etiqueta",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": "Guardado",
|
||||
"discovered": "Descubierto",
|
||||
"noServersFound": "No se encontraron nuevos servidores",
|
||||
"about": "Acerca de",
|
||||
"openParent": "Abrir carpeta superior",
|
||||
"mouseDragSupport": "Arrastrar con el ratón",
|
||||
"controls": "Controles"
|
||||
}
|
||||
669
lib/l10n/app_fr.arb
Normal file
669
lib/l10n/app_fr.arb
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"switchUser": "Changer d'utilisateur",
|
||||
"userName": "Nom d'utilisateur",
|
||||
"password": "Mot de passe",
|
||||
"login": "Connexion",
|
||||
"logout": "Déconnexion",
|
||||
"cancel": "Annuler",
|
||||
"accept": "Accepter",
|
||||
"code": "Code",
|
||||
"error": "Erreur",
|
||||
"clear": "Effacer",
|
||||
"days": "Jours",
|
||||
"search": "Recherche",
|
||||
"loggedIn": "Connecté",
|
||||
"change": "Changer",
|
||||
"other": "Autre",
|
||||
"dynamicText": "Dynamique",
|
||||
"enabled": "Activé",
|
||||
"disabled": "Désactivé",
|
||||
"dashboard": "Tableau de bord",
|
||||
"advanced": "Avancé",
|
||||
"refresh": "Rafraîchir",
|
||||
"delete": "Supprimer",
|
||||
"goTo": "Aller à",
|
||||
"loop": "Boucle",
|
||||
"empty": "Vide",
|
||||
"noRating": "Pas de note",
|
||||
"backgroundBlur": "Flou de fond",
|
||||
"autoPlay": "Lecture automatique",
|
||||
"resume": "Reprendre {item}",
|
||||
"@resume": {
|
||||
"description": "reprendre",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"play": "Lire {item}",
|
||||
"@play": {
|
||||
"description": "Lire avec",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"read": "Lire {item}",
|
||||
"@read": {
|
||||
"description": "lire",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readFromStart": "Lire {item} depuis le début",
|
||||
"@readFromStart": {
|
||||
"description": "Lire le livre depuis le début",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFrom": "Lire depuis {name}",
|
||||
"@playFrom": {
|
||||
"description": "jouer de",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreFrom": "Plus de {info}",
|
||||
"@moreFrom": {
|
||||
"description": "Plus de",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedWith": "Sélectionné {info}",
|
||||
"@selectedWith": {
|
||||
"description": "sélectionné",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected": "Sélectionné",
|
||||
"restart": "Redémarrer",
|
||||
"reWatch": "Revoir",
|
||||
"watchOn": "Regarder sur",
|
||||
"options": "Options",
|
||||
"list": "Liste",
|
||||
"grid": "Grille",
|
||||
"masonry": "Maçonnerie",
|
||||
"start": "Commencer",
|
||||
"none": "Aucun",
|
||||
"chapter": "{count, plural, other{Chapitres} one{Chapitre}}",
|
||||
"@chapter": {
|
||||
"description": "chapitre",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sync": "Synchroniser",
|
||||
"moreOptions": "Plus d'options",
|
||||
"continuePage": "Continuer - page {page}",
|
||||
"@continuePage": {
|
||||
"description": "Continuer - page 1",
|
||||
"placeholders": {
|
||||
"page": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openShow": "Ouvrir le spectacle",
|
||||
"showDetails": "Afficher les détails",
|
||||
"showAlbum": "Afficher l'album",
|
||||
"season": "{count, plural, other{Saisons} one{Saison}}",
|
||||
"@season": {
|
||||
"description": "saison",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{Épisodes} one{Épisode}}",
|
||||
"@episode": {
|
||||
"description": "épisode",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToCollection": "Ajouter à la collection",
|
||||
"addToPlaylist": "Ajouter à la playlist",
|
||||
"removeFromCollection": "Retirer de la collection",
|
||||
"removeFromPlaylist": "Retirer de la playlist",
|
||||
"markAsWatched": "Marquer comme regardé",
|
||||
"markAsUnwatched": "Marquer comme non regardé",
|
||||
"removeAsFavorite": "Retirer des favoris",
|
||||
"addAsFavorite": "Ajouter aux favoris",
|
||||
"editMetadata": "Modifier les métadonnées",
|
||||
"refreshMetadata": "Rafraîchir les métadonnées",
|
||||
"syncDetails": "Synchroniser les détails",
|
||||
"identify": "Identifier",
|
||||
"info": "Infos",
|
||||
"clearAllSettings": "Effacer tous les paramètres",
|
||||
"clearAllSettingsQuestion": "Effacer tous les paramètres?",
|
||||
"unableToReverseAction": "Cette action ne peut pas être annulée, elle supprimera tous les paramètres.",
|
||||
"navigationDashboard": "Tableau de bord",
|
||||
"navigationFavorites": "Favoris",
|
||||
"navigationSync": "Synchronisé",
|
||||
"navigation": "Navigation",
|
||||
"library": "{count, plural, other{Bibliothèques} one{Bibliothèque}}",
|
||||
"@library": {
|
||||
"description": "pluriel",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanLibrary": "Scanner la bibliothèque",
|
||||
"dashboardContinue": "Continuer",
|
||||
"dashboardContinueWatching": "Continuer à regarder",
|
||||
"dashboardContinueReading": "Continuer à lire",
|
||||
"dashboardContinueListening": "Continuer à écouter",
|
||||
"dashboardNextUp": "Suivant",
|
||||
"dashboardRecentlyAdded": "Récemment ajouté dans {name}",
|
||||
"@dashboardRecentlyAdded": {
|
||||
"description": "Récemment ajouté sur l'écran d'accueil",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": "Paramètres",
|
||||
"settingsClientTitle": "Fladder",
|
||||
"settingsClientDesc": "Général, Timeout, Disposition, Thème",
|
||||
"settingsQuickConnectTitle": "Connexion rapide",
|
||||
"settingsProfileTitle": "Profil",
|
||||
"settingsProfileDesc": "Écran de verrouillage",
|
||||
"settingsPlayerTitle": "Lecteur",
|
||||
"settingsPlayerDesc": "Ratio, Avancé",
|
||||
"logoutUserPopupTitle": "Déconnecter l'utilisateur {userName}?",
|
||||
"@logoutUserPopupTitle": {
|
||||
"description": "Pop-up pour déconnecter l'utilisateur",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logoutUserPopupContent": "Cela déconnectera {userName} et supprimera l'utilisateur de l'application.\nVous devrez vous reconnecter à {serverName}.",
|
||||
"@logoutUserPopupContent": {
|
||||
"description": "Pop-up pour déconnecter l'utilisateur description",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
},
|
||||
"serverName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quickConnectTitle": "Connexion rapide",
|
||||
"quickConnectAction": "Entrer le code de connexion rapide pour",
|
||||
"quickConnectInputACode": "Entrer un code",
|
||||
"quickConnectWrongCode": "Code incorrect",
|
||||
"downloadsTitle": "Téléchargements",
|
||||
"downloadsPath": "Chemin",
|
||||
"pathEditTitle": "Changer l'emplacement",
|
||||
"pathEditSelect": "Sélectionner la destination des téléchargements",
|
||||
"pathClearTitle": "Effacer le chemin de téléchargement",
|
||||
"pathEditDesc": "Cet emplacement est défini pour tous les utilisateurs, toutes les données synchronisées ne seront plus accessibles.\nElles resteront sur votre stockage.",
|
||||
"downloadsSyncedData": "Données synchronisées",
|
||||
"downloadsClearTitle": "Effacer les données synchronisées",
|
||||
"downloadsClearDesc": "Êtes-vous sûr de vouloir supprimer toutes les données synchronisées?\nCela effacera toutes les données pour chaque utilisateur synchronisé!",
|
||||
"lockscreen": "Écran de verrouillage",
|
||||
"timeOut": "Timeout",
|
||||
"home": "Accueil",
|
||||
"settingsHomeCarouselTitle": "Carrousel du tableau de bord",
|
||||
"settingsHomeCarouselDesc": "Affiche un carrousel sur l'écran du tableau de bord",
|
||||
"settingsHomeNextUpTitle": "Affiches à venir",
|
||||
"settingsHomeNextUpDesc": "Type d'affiches affichées sur l'écran du tableau de bord",
|
||||
"settingsVisual": "Visuel",
|
||||
"settingsBlurredPlaceholderTitle": "Placeholder flou",
|
||||
"settingsBlurredPlaceholderDesc": "Afficher un arrière-plan flou lors du chargement des affiches",
|
||||
"settingsBlurEpisodesTitle": "Flouter les prochains épisodes",
|
||||
"settingsBlurEpisodesDesc": "Flouter tous les épisodes à venir",
|
||||
"settingsEnableOsMediaControls": "Activer les contrôles multimédia de l'OS",
|
||||
"settingsNextUpCutoffDays": "Jours de coupure suivants",
|
||||
"settingsShowScaleSlider": "Afficher le curseur de taille des affiches",
|
||||
"settingsPosterSize": "Taille de l'affiche",
|
||||
"settingsPosterSlider": "Afficher le curseur de mise à l'échelle",
|
||||
"settingsPosterPinch": "Pincer pour zoomer pour mettre à l'échelle les affiches",
|
||||
"theme": "Thème",
|
||||
"mode": "Mode",
|
||||
"themeModeSystem": "Système",
|
||||
"themeModeLight": "Clair",
|
||||
"themeModeDark": "Sombre",
|
||||
"themeColor": "Couleur du thème",
|
||||
"color": "Couleur",
|
||||
"amoledBlack": "Noir Amoled",
|
||||
"hide": "Cacher",
|
||||
"nextUp": "Suivant",
|
||||
"settingsContinue": "Continuer",
|
||||
"separate": "Séparer",
|
||||
"combined": "Combiné",
|
||||
"settingsSecurity": "Sécurité",
|
||||
"settingSecurityApplockTitle": "Verrouillage de l'application",
|
||||
"appLockTitle": "Définir la méthode de connexion pour {userName}",
|
||||
"@appLockTitle": {
|
||||
"description": "Pop-up pour choisir une méthode de connexion",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"biometricsFailedCheckAgain": "Échec de la biométrie, vérifiez les paramètres et réessayez",
|
||||
"appLockAutoLogin": "Connexion automatique",
|
||||
"appLockPasscode": "Code d'accès",
|
||||
"appLockBiometrics": "Biométrie",
|
||||
"settingsPlayerVideoHWAccelTitle": "Accélération matérielle",
|
||||
"settingsPlayerVideoHWAccelDesc": "Utiliser le GPU pour rendre la vidéo (recommandé)",
|
||||
"settingsPlayerNativeLibassAccelTitle": "Sous-titres natifs libass",
|
||||
"settingsPlayerNativeLibassAccelDesc": "Utiliser le rendu des sous-titres libass du lecteur vidéo",
|
||||
"settingsPlayerMobileWarning": "L'activation de l'accélération matérielle et des sous-titres natifs libass sur Android peut empêcher certains sous-titres de s'afficher.",
|
||||
"settingsPlayerCustomSubtitlesTitle": "Personnaliser les sous-titres",
|
||||
"settingsPlayerCustomSubtitlesDesc": "Personnaliser la taille, la couleur, la position, le contour",
|
||||
"videoScalingFillScreenTitle": "Remplir l'écran",
|
||||
"videoScalingFillScreenDesc": "Remplir la barre de navigation et la barre d'état",
|
||||
"videoScalingFillScreenNotif": "Le remplissage de l'écran écrase l'ajustement de la vidéo, en rotation horizontale",
|
||||
"videoScaling": "Mise à l'échelle de la vidéo",
|
||||
"videoScalingFill": "Remplir",
|
||||
"videoScalingContain": "Contenir",
|
||||
"videoScalingCover": "Couverture",
|
||||
"videoScalingFitWidth": "Ajuster la largeur",
|
||||
"videoScalingFitHeight": "Ajuster la hauteur",
|
||||
"videoScalingScaleDown": "Réduire l'échelle",
|
||||
"subtitleConfiguratorPlaceHolder": "Ceci est un texte de placeholder,\nrien à voir ici.",
|
||||
"subtitleConfigurator": "Configurateur de sous-titres",
|
||||
"refreshPopup": "Rafraîchir - {name}",
|
||||
"@refreshPopup": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanningName": "Analyse - {name}",
|
||||
"@scanningName": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshPopupContentMetadata": "Les métadonnées sont mises à jour en fonction des paramètres et des services Internet activés dans le tableau de bord.",
|
||||
"replaceExistingImages": "Remplacer les images existantes",
|
||||
"metadataRefreshDefault": "Rechercher des fichiers nouveaux et mis à jour",
|
||||
"metadataRefreshValidation": "Rechercher des métadonnées manquantes",
|
||||
"metadataRefreshFull": "Remplacer toutes les métadonnées",
|
||||
"syncedItems": "Éléments synchronisés",
|
||||
"noItemsSynced": "Aucun élément synchronisé",
|
||||
"syncDeletePopupPermanent": "Cette action est permanente et supprimera tous les fichiers synchronisés localement",
|
||||
"totalSize": "Taille totale : {size}",
|
||||
"@totalSize": {
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mediaTypeBase": "Type de base",
|
||||
"mediaTypeMovie": "Film",
|
||||
"mediaTypeSeries": "Série",
|
||||
"mediaTypeSeason": "Saison",
|
||||
"mediaTypeEpisode": "Épisode",
|
||||
"mediaTypePhoto": "Photo",
|
||||
"mediaTypePerson": "Personne",
|
||||
"mediaTypePhotoAlbum": "Album photo",
|
||||
"mediaTypeFolder": "Dossier",
|
||||
"mediaTypeBoxset": "Coffret",
|
||||
"mediaTypePlaylist": "Liste de lecture",
|
||||
"mediaTypeBook": "Livre",
|
||||
"actor": "{count, plural, other{Acteurs} one{Acteur}}",
|
||||
"@actor": {
|
||||
"description": "acteur",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"writer": "{count, plural, other{Écrivains} one{Écrivain}}",
|
||||
"@writer": {
|
||||
"description": "écrivain",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": "{count, plural, other{Réalisateurs} one{Réalisateur}}",
|
||||
"@director": {
|
||||
"description": "réalisateur",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": "Vidéo",
|
||||
"audio": "Audio",
|
||||
"subtitles": "Sous-titres",
|
||||
"related": "Lié",
|
||||
"all": "Tout",
|
||||
"overview": "Aperçu",
|
||||
"selectViewType": "Sélectionner le type de vue",
|
||||
"noItemsToShow": "Aucun élément à afficher",
|
||||
"sortBy": "Trier par",
|
||||
"groupBy": "Grouper par",
|
||||
"scrollToTop": "Défiler vers le haut",
|
||||
"disableFilters": "Désactiver les filtres",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"clearSelection": "Effacer la sélection",
|
||||
"shuffleVideos": "Mélanger les vidéos",
|
||||
"shuffleGallery": "Mélanger la galerie",
|
||||
"unknown": "Inconnu",
|
||||
"favorites": "Favoris",
|
||||
"recursive": "Récursif",
|
||||
"genre": "{count, plural, other{Genres} one{Genre}}",
|
||||
"@genre": {
|
||||
"description": "genre",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"studio": "{count, plural, other{Studios} one{Studio}}",
|
||||
"@studio": {
|
||||
"description": "studio",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "{count, plural, other{Étiquettes} one{Étiquette}}",
|
||||
"@label": {
|
||||
"description": "étiquette",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": "Groupe",
|
||||
"type": "{count, plural, other{Types} one{Type}}",
|
||||
"@type": {
|
||||
"description": "type",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "{count, plural, other{Filtres} one{Filtre}}",
|
||||
"@filter": {
|
||||
"description": "filtre",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showEmpty": "Afficher vide",
|
||||
"hideEmpty": "Cacher vide",
|
||||
"rating": "{count, plural, other{Notes} one{Note}}",
|
||||
"@rating": {
|
||||
"description": "note",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"year": "{count, plural, other{Années} one{Année}}",
|
||||
"@year": {
|
||||
"description": "année",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playVideos": "Lire les vidéos",
|
||||
"playLabel": "Lire",
|
||||
"forceRefresh": "Forcer le rafraîchissement",
|
||||
"itemCount": "Nombre d'éléments: {count}",
|
||||
"@itemCount": {
|
||||
"description": "Nombre d'éléments",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "URL invalide",
|
||||
"invalidUrlDesc": "L'URL doit commencer par http(s)://",
|
||||
"incorrectPinTryAgain": "PIN incorrect, réessayez",
|
||||
"somethingWentWrongPasswordCheck": "Quelque chose s'est mal passé, vérifiez votre mot de passe",
|
||||
"unableToConnectHost": "Impossible de se connecter à l'hôte",
|
||||
"server": "Serveur",
|
||||
"retrievePublicListOfUsers": "Récupérer la liste publique des utilisateurs",
|
||||
"displayLanguage": "Langue d'affichage",
|
||||
"deleteItem": "Supprimer {item}?",
|
||||
"@deleteItem": {
|
||||
"description": "deleteItem",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncDeleteItemTitle": "Supprimer l'élément synchronisé",
|
||||
"syncDeleteItemDesc": "Supprimer toutes les données synchronisées pour?\n{item}",
|
||||
"@syncDeleteItemDesc": {
|
||||
"description": "Fenêtre contextuelle de suppression d'élément synchronisé",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncOpenParent": "Ouvrir le parent",
|
||||
"syncRemoveDataTitle": "Supprimer les données synchronisées?",
|
||||
"syncRemoveDataDesc": "Supprimer les données vidéo synchronisées? Ceci est permanent et vous devrez resynchroniser les fichiers",
|
||||
"collectionFolder": "Dossier de collection",
|
||||
"musicAlbum": "Album",
|
||||
"active": "Actif",
|
||||
"name": "Nom",
|
||||
"result": "Résultat",
|
||||
"close": "Fermer",
|
||||
"replaceAllImages": "Remplacer toutes les images",
|
||||
"noResults": "Aucun résultat",
|
||||
"openWebLink": "Ouvrir le lien web",
|
||||
"setIdentityTo": "Définir l'identité sur {name}",
|
||||
"@setIdentityTo": {
|
||||
"description": "setIdentityTo",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"somethingWentWrong": "Quelque chose a mal tourné",
|
||||
"clearChanges": "Effacer les modifications",
|
||||
"useDefaults": "Utiliser les paramètres par défaut",
|
||||
"light": "Léger",
|
||||
"normal": "Normal",
|
||||
"bold": "Gras",
|
||||
"fontSize": "Taille de la police",
|
||||
"heightOffset": "Décalage de hauteur",
|
||||
"fontColor": "Couleur de la police",
|
||||
"outlineColor": "Couleur du contour",
|
||||
"outlineSize": "Taille du contour",
|
||||
"backgroundOpacity": "Opacité de l'arrière-plan",
|
||||
"shadow": "Ombre",
|
||||
"played": "Joué",
|
||||
"unPlayed": "Non joué",
|
||||
"resumable": "Reprenable",
|
||||
"sortOrder": "Ordre de tri",
|
||||
"sortName": "Nom",
|
||||
"communityRating": "Évaluation de la communauté",
|
||||
"parentalRating": "Évaluation parentale",
|
||||
"dateAdded": "Date d'ajout",
|
||||
"dateLastContentAdded": "Date du dernier contenu ajouté",
|
||||
"favorite": "Favori",
|
||||
"datePlayed": "Date de lecture",
|
||||
"folders": "Dossiers",
|
||||
"playCount": "Nombre de lectures",
|
||||
"releaseDate": "Date de sortie",
|
||||
"runTime": "Durée",
|
||||
"ascending": "Ascendant",
|
||||
"descending": "Descendant",
|
||||
"playFromStart": "Lire {name} depuis le début",
|
||||
"@playFromStart": {
|
||||
"description": "jouer depuis le début",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minutes": "{count, plural, other{Minutes} one{Minute}}",
|
||||
"@minutes": {
|
||||
"description": "minute",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds": "{count, plural, other{Secondes} one{Seconde}}",
|
||||
"@seconds": {
|
||||
"description": "seconde",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": "Page {index}",
|
||||
"@page": {
|
||||
"description": "page",
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set": "Régler",
|
||||
"@set": {
|
||||
"description": "Utiliser pour définir une certaine valeur",
|
||||
"context": "Régler 'heure'"
|
||||
},
|
||||
"never": "Jamais",
|
||||
"selectTime": "Sélectionner l'heure",
|
||||
"immediately": "Immédiatement",
|
||||
"timeAndAnnotation": "{minutes} et {seconds}",
|
||||
"@timeAndAnnotation": {
|
||||
"description": "timeAndAnnotation",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "String"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanYourFingerprintToAuthenticate": "Scannez votre empreinte digitale pour authentifier {user}",
|
||||
"@scanYourFingerprintToAuthenticate": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanBiometricHint": "Vérifier identité",
|
||||
"deleteFileFromSystem": "Supprimer cet élément {item} le supprimera à la fois du système de fichiers et de votre bibliothèque multimédia.\nÊtes-vous sûr de vouloir continuer?",
|
||||
"@deleteFileFromSystem": {
|
||||
"description": "Supprimer le fichier du système",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notPartOfAlbum": "Ne fait pas partie d'un album",
|
||||
"retry": "Réessayer",
|
||||
"failedToLoadImage": "Échec du chargement de l'image",
|
||||
"save": "Enregistrer",
|
||||
"metaDataSavedFor": "Métadonnées enregistrées pour {item}",
|
||||
"@metaDataSavedFor": {
|
||||
"description": "métadonnéesEnregistréesPour",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraryPageSizeTitle": "Taille de la page de la bibliothèque",
|
||||
"libraryPageSizeDesc": "Définir la quantité à charger à la fois. 0 désactive la pagination.",
|
||||
"fetchingLibrary": "Chargement des éléments de la bibliothèque",
|
||||
"libraryFetchNoItemsFound": "Aucun élément trouvé, essayez avec des paramètres différents.",
|
||||
"noSuggestionsFound": "Aucune suggestion trouvée",
|
||||
"viewPhotos": "Voir les photos",
|
||||
"random": "Aléatoire",
|
||||
"tag": "{count, plural, one{Étiquette} other{Étiquettes}}",
|
||||
"@tag": {
|
||||
"description": "étiquette",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": "Enregistré",
|
||||
"discovered": "Découvert",
|
||||
"noServersFound": "Aucun nouveau serveur trouvé",
|
||||
"about": "À propos",
|
||||
"openParent": "Ouvrir le dossier parent",
|
||||
"mouseDragSupport": "Faire glisser avec la souris",
|
||||
"controls": "Commandes"
|
||||
}
|
||||
669
lib/l10n/app_jp.arb
Normal file
669
lib/l10n/app_jp.arb
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
{
|
||||
"@@locale": "ja",
|
||||
"switchUser": "ユーザーを切り替える",
|
||||
"userName": "ユーザー名",
|
||||
"password": "パスワード",
|
||||
"login": "ログイン",
|
||||
"logout": "ログアウト",
|
||||
"cancel": "キャンセル",
|
||||
"accept": "承認",
|
||||
"code": "コード",
|
||||
"error": "エラー",
|
||||
"clear": "クリア",
|
||||
"days": "日",
|
||||
"search": "検索",
|
||||
"loggedIn": "ログイン済み",
|
||||
"change": "変更",
|
||||
"other": "その他",
|
||||
"dynamicText": "動的",
|
||||
"enabled": "有効",
|
||||
"disabled": "無効",
|
||||
"dashboard": "ダッシュボード",
|
||||
"advanced": "高度な",
|
||||
"refresh": "リフレッシュ",
|
||||
"delete": "削除",
|
||||
"goTo": "行く",
|
||||
"loop": "ループ",
|
||||
"empty": "空",
|
||||
"noRating": "評価なし",
|
||||
"backgroundBlur": "背景ぼかし",
|
||||
"autoPlay": "自動再生",
|
||||
"resume": "{item}を再開する",
|
||||
"@resume": {
|
||||
"description": "再開する",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"play": "{item}を再生する",
|
||||
"@play": {
|
||||
"description": "再生する",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"read": "{item}を読む",
|
||||
"@read": {
|
||||
"description": "読む",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readFromStart": "{item}を最初から読む",
|
||||
"@readFromStart": {
|
||||
"description": "最初から読む",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFrom": "{name}から再生する",
|
||||
"@playFrom": {
|
||||
"description": "から再生する",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreFrom": "{info}の詳細",
|
||||
"@moreFrom": {
|
||||
"description": "詳細",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedWith": "{info}を選択済み",
|
||||
"@selectedWith": {
|
||||
"description": "選択済み",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected": "選択済み",
|
||||
"restart": "再起動",
|
||||
"reWatch": "再視聴",
|
||||
"watchOn": "で見る",
|
||||
"options": "オプション",
|
||||
"list": "リスト",
|
||||
"grid": "グリッド",
|
||||
"masonry": "石工",
|
||||
"start": "スタート",
|
||||
"none": "なし",
|
||||
"chapter": "{count, plural, other{チャプター} one{チャプター}}",
|
||||
"@chapter": {
|
||||
"description": "チャプター",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sync": "同期",
|
||||
"moreOptions": "その他のオプション",
|
||||
"continuePage": "続行 - ページ {page}",
|
||||
"@continuePage": {
|
||||
"description": "続行 - ページ 1",
|
||||
"placeholders": {
|
||||
"page": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openShow": "番組を開く",
|
||||
"showDetails": "詳細を表示",
|
||||
"showAlbum": "アルバムを表示",
|
||||
"season": "{count, plural, other{シーズン} one{シーズン}}",
|
||||
"@season": {
|
||||
"description": "シーズン",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{エピソード} one{エピソード}}",
|
||||
"@episode": {
|
||||
"description": "エピソード",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToCollection": "コレクションに追加",
|
||||
"addToPlaylist": "プレイリストに追加",
|
||||
"removeFromCollection": "コレクションから削除",
|
||||
"removeFromPlaylist": "プレイリストから削除",
|
||||
"markAsWatched": "視聴済みにマーク",
|
||||
"markAsUnwatched": "未視聴にマーク",
|
||||
"removeAsFavorite": "お気に入りから削除",
|
||||
"addAsFavorite": "お気に入りに追加",
|
||||
"editMetadata": "メタデータを編集",
|
||||
"refreshMetadata": "メタデータを更新",
|
||||
"syncDetails": "詳細を同期",
|
||||
"identify": "識別",
|
||||
"info": "情報",
|
||||
"clearAllSettings": "すべての設定をクリア",
|
||||
"clearAllSettingsQuestion": "すべての設定をクリアしますか?",
|
||||
"unableToReverseAction": "この操作は元に戻せません。すべての設定が削除されます。",
|
||||
"navigationDashboard": "ダッシュボード",
|
||||
"navigationFavorites": "お気に入り",
|
||||
"navigationSync": "同期済み",
|
||||
"navigation": "ナビゲーション",
|
||||
"library": "{count, plural, other{ライブラリ} one{ライブラリ}}",
|
||||
"@library": {
|
||||
"description": "複数",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanLibrary": "ライブラリをスキャン",
|
||||
"dashboardContinue": "続行",
|
||||
"dashboardContinueWatching": "視聴を続ける",
|
||||
"dashboardContinueReading": "読み続ける",
|
||||
"dashboardContinueListening": "リスニングを続ける",
|
||||
"dashboardNextUp": "次へ",
|
||||
"dashboardRecentlyAdded": "{name}に最近追加",
|
||||
"@dashboardRecentlyAdded": {
|
||||
"description": "ホーム画面に最近追加",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": "設定",
|
||||
"settingsClientTitle": "Fladder",
|
||||
"settingsClientDesc": "一般, タイムアウト, レイアウト, テーマ",
|
||||
"settingsQuickConnectTitle": "クイック接続",
|
||||
"settingsProfileTitle": "プロフィール",
|
||||
"settingsProfileDesc": "ロック画面",
|
||||
"settingsPlayerTitle": "プレーヤー",
|
||||
"settingsPlayerDesc": "アスペクト比, 高度な",
|
||||
"logoutUserPopupTitle": "ユーザー {userName} をログアウトしますか?",
|
||||
"@logoutUserPopupTitle": {
|
||||
"description": "ユーザーをログアウトするためのポップアップ",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logoutUserPopupContent": "これにより、{userName} がログアウトされ、アプリからユーザーが削除されます。\n{serverName}に再ログインする必要があります。",
|
||||
"@logoutUserPopupContent": {
|
||||
"description": "ユーザーをログアウトするためのポップアップの説明",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
},
|
||||
"serverName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quickConnectTitle": "クイック接続",
|
||||
"quickConnectAction": "クイック接続コードを入力",
|
||||
"quickConnectInputACode": "コードを入力",
|
||||
"quickConnectWrongCode": "コードが間違っています",
|
||||
"downloadsTitle": "ダウンロード",
|
||||
"downloadsPath": "パス",
|
||||
"pathEditTitle": "場所を変更",
|
||||
"pathEditSelect": "ダウンロード先を選択",
|
||||
"pathClearTitle": "ダウンロードパスをクリア",
|
||||
"pathEditDesc": "この場所はすべてのユーザーに設定されており、同期されたデータはもうアクセスできません。\nストレージに残ります。",
|
||||
"downloadsSyncedData": "同期データ",
|
||||
"downloadsClearTitle": "同期データをクリア",
|
||||
"downloadsClearDesc": "すべての同期データを削除してもよろしいですか?\nこれにより、すべての同期ユーザーのデータがクリアされます!",
|
||||
"lockscreen": "ロック画面",
|
||||
"timeOut": "タイムアウト",
|
||||
"home": "ホーム",
|
||||
"settingsHomeCarouselTitle": "ダッシュボードのカルーセル",
|
||||
"settingsHomeCarouselDesc": "ダッシュボード画面にカルーセルを表示",
|
||||
"settingsHomeNextUpTitle": "次のポスター",
|
||||
"settingsHomeNextUpDesc": "ダッシュボード画面に表示されるポスターの種類",
|
||||
"settingsVisual": "ビジュアル",
|
||||
"settingsBlurredPlaceholderTitle": "ぼかしプレースホルダー",
|
||||
"settingsBlurredPlaceholderDesc": "ポスターの読み込み中にぼかし背景を表示",
|
||||
"settingsBlurEpisodesTitle": "次のエピソードをぼかす",
|
||||
"settingsBlurEpisodesDesc": "次のエピソードをすべてぼかす",
|
||||
"settingsEnableOsMediaControls": "OSメディアコントロールを有効にする",
|
||||
"settingsNextUpCutoffDays": "次のカットオフ日",
|
||||
"settingsShowScaleSlider": "ポスターサイズスライダーを表示",
|
||||
"settingsPosterSize": "ポスターサイズ",
|
||||
"settingsPosterSlider": "スケールスライダーを表示",
|
||||
"settingsPosterPinch": "ポスターをスケールするためにピンチズーム",
|
||||
"theme": "テーマ",
|
||||
"mode": "モード",
|
||||
"themeModeSystem": "システム",
|
||||
"themeModeLight": "ライト",
|
||||
"themeModeDark": "ダーク",
|
||||
"themeColor": "テーマカラー",
|
||||
"color": "カラー",
|
||||
"amoledBlack": "アモレッドブラック",
|
||||
"hide": "非表示",
|
||||
"nextUp": "次へ",
|
||||
"settingsContinue": "続行",
|
||||
"separate": "分離",
|
||||
"combined": "組み合わせ",
|
||||
"settingsSecurity": "セキュリティ",
|
||||
"settingSecurityApplockTitle": "アプリロック",
|
||||
"appLockTitle": "{userName}のログイン方法を設定",
|
||||
"@appLockTitle": {
|
||||
"description": "ログイン方法を選択するポップアップ",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"biometricsFailedCheckAgain": "生体認証に失敗しました。設定を確認してもう一度お試しください。",
|
||||
"appLockAutoLogin": "自動ログイン",
|
||||
"appLockPasscode": "パスコード",
|
||||
"appLockBiometrics": "生体認証",
|
||||
"settingsPlayerVideoHWAccelTitle": "ハードウェアアクセラレーション",
|
||||
"settingsPlayerVideoHWAccelDesc": "ビデオをレンダリングするためにGPUを使用する(推奨)",
|
||||
"settingsPlayerNativeLibassAccelTitle": "ネイティブlibass字幕",
|
||||
"settingsPlayerNativeLibassAccelDesc": "ビデオプレーヤーlibass字幕レンダラーを使用する",
|
||||
"settingsPlayerMobileWarning": "Androidでハードウェアアクセラレーションとネイティブlibass字幕を有効にすると、一部の字幕がレンダリングされない場合があります。",
|
||||
"settingsPlayerCustomSubtitlesTitle": "字幕のカスタマイズ",
|
||||
"settingsPlayerCustomSubtitlesDesc": "サイズ、色、位置、アウトラインをカスタマイズする",
|
||||
"videoScalingFillScreenTitle": "画面全体に表示",
|
||||
"videoScalingFillScreenDesc": "ナビゲーションバーとステータスバーを埋める",
|
||||
"videoScalingFillScreenNotif": "画面全体はビデオフィットを上書きし、水平回転で",
|
||||
"videoScaling": "ビデオスケーリング",
|
||||
"videoScalingFill": "全体に表示",
|
||||
"videoScalingContain": "含む",
|
||||
"videoScalingCover": "カバー",
|
||||
"videoScalingFitWidth": "幅に合わせる",
|
||||
"videoScalingFitHeight": "高さに合わせる",
|
||||
"videoScalingScaleDown": "スケールダウン",
|
||||
"subtitleConfiguratorPlaceHolder": "これはプレースホルダーテキストです。\nここには何もありません。",
|
||||
"subtitleConfigurator": "字幕コンフィギュレーター",
|
||||
"refreshPopup": "リフレッシュ - {name}",
|
||||
"@refreshPopup": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanningName": "スキャン中 - {name}",
|
||||
"@scanningName": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshPopupContentMetadata": "メタデータはダッシュボードで有効にされた設定とインターネットサービスに基づいて更新されます。",
|
||||
"replaceExistingImages": "既存の画像を置き換える",
|
||||
"metadataRefreshDefault": "新しいファイルと更新されたファイルをスキャン",
|
||||
"metadataRefreshValidation": "欠落しているメタデータを検索",
|
||||
"metadataRefreshFull": "すべてのメタデータを置き換える",
|
||||
"syncedItems": "同期されたアイテム",
|
||||
"noItemsSynced": "同期されたアイテムはありません",
|
||||
"syncDeletePopupPermanent": "この操作は恒久的であり、すべてのローカルに同期されたファイルを削除します",
|
||||
"totalSize": "合計サイズ: {size}",
|
||||
"@totalSize": {
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mediaTypeBase": "基本タイプ",
|
||||
"mediaTypeMovie": "映画",
|
||||
"mediaTypeSeries": "シリーズ",
|
||||
"mediaTypeSeason": "シーズン",
|
||||
"mediaTypeEpisode": "エピソード",
|
||||
"mediaTypePhoto": "写真",
|
||||
"mediaTypePerson": "人物",
|
||||
"mediaTypePhotoAlbum": "フォトアルバム",
|
||||
"mediaTypeFolder": "フォルダー",
|
||||
"mediaTypeBoxset": "ボックスセット",
|
||||
"mediaTypePlaylist": "プレイリスト",
|
||||
"mediaTypeBook": "本",
|
||||
"actor": "{count, plural, other{俳優} one{俳優}}",
|
||||
"@actor": {
|
||||
"description": "俳優",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"writer": "{count, plural, other{作家} one{作家}}",
|
||||
"@writer": {
|
||||
"description": "作家",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": "{count, plural, other{監督} one{監督}}",
|
||||
"@director": {
|
||||
"description": "監督",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": "ビデオ",
|
||||
"audio": "オーディオ",
|
||||
"subtitles": "字幕",
|
||||
"related": "関連",
|
||||
"all": "すべて",
|
||||
"overview": "概要",
|
||||
"selectViewType": "ビュータイプを選択",
|
||||
"noItemsToShow": "表示するアイテムがありません",
|
||||
"sortBy": "並び替え",
|
||||
"groupBy": "グループ化",
|
||||
"scrollToTop": "トップにスクロール",
|
||||
"disableFilters": "フィルターを無効にする",
|
||||
"selectAll": "すべて選択",
|
||||
"clearSelection": "選択をクリア",
|
||||
"shuffleVideos": "ビデオをシャッフル",
|
||||
"shuffleGallery": "ギャラリーをシャッフル",
|
||||
"unknown": "不明",
|
||||
"favorites": "お気に入り",
|
||||
"recursive": "再帰的",
|
||||
"genre": "{count, plural, other{ジャンル} one{ジャンル}}",
|
||||
"@genre": {
|
||||
"description": "ジャンル",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"studio": "{count, plural, other{スタジオ} one{スタジオ}}",
|
||||
"@studio": {
|
||||
"description": "スタジオ",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "{count, plural, other{ラベル} one{ラベル}}",
|
||||
"@label": {
|
||||
"description": "ラベル",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": "グループ",
|
||||
"type": "{count, plural, other{タイプ} one{タイプ}}",
|
||||
"@type": {
|
||||
"description": "タイプ",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "{count, plural, other{フィルター} one{フィルター}}",
|
||||
"@filter": {
|
||||
"description": "フィルター",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showEmpty": "空を表示",
|
||||
"hideEmpty": "空を非表示",
|
||||
"rating": "{count, plural, other{評価} one{評価}}",
|
||||
"@rating": {
|
||||
"description": "評価",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"year": "{count, plural, other{年} one{年}}",
|
||||
"@year": {
|
||||
"description": "年",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playVideos": "ビデオを再生",
|
||||
"playLabel": "再生",
|
||||
"forceRefresh": "強制リフレッシュ",
|
||||
"itemCount": "アイテム数: {count}",
|
||||
"@itemCount": {
|
||||
"description": "アイテム数",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "無効なURL",
|
||||
"invalidUrlDesc": "URLはhttp(s)://で始まる必要があります",
|
||||
"incorrectPinTryAgain": "PINが間違っています、もう一度試してください",
|
||||
"somethingWentWrongPasswordCheck": "何かがうまくいかなかった、パスワードを確認してください",
|
||||
"unableToConnectHost": "ホストに接続できません",
|
||||
"server": "サーバー",
|
||||
"retrievePublicListOfUsers": "ユーザーの公開リストを取得",
|
||||
"displayLanguage": "表示言語",
|
||||
"deleteItem": "{item}を削除しますか?",
|
||||
"@deleteItem": {
|
||||
"description": "deleteItem",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncDeleteItemTitle": "同期されたアイテムを削除",
|
||||
"syncDeleteItemDesc": "すべての同期されたデータを削除しますか?\n{item}",
|
||||
"@syncDeleteItemDesc": {
|
||||
"description": "同期削除アイテムのポップアップウィンドウ",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncOpenParent": "親を開く",
|
||||
"syncRemoveDataTitle": "同期データを削除しますか?",
|
||||
"syncRemoveDataDesc": "同期されたビデオデータを削除しますか?これは永久的なもので、ファイルを再同期する必要があります",
|
||||
"collectionFolder": "コレクションフォルダ",
|
||||
"musicAlbum": "アルバム",
|
||||
"active": "アクティブ",
|
||||
"name": "名前",
|
||||
"result": "結果",
|
||||
"close": "閉じる",
|
||||
"replaceAllImages": "すべての画像を置き換える",
|
||||
"noResults": "結果がありません",
|
||||
"openWebLink": "ウェブリンクを開く",
|
||||
"setIdentityTo": "{name}にアイデンティティを設定",
|
||||
"@setIdentityTo": {
|
||||
"description": "setIdentityTo",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"somethingWentWrong": "問題が発生しました",
|
||||
"clearChanges": "変更をクリア",
|
||||
"useDefaults": "デフォルトを使用",
|
||||
"light": "ライト",
|
||||
"normal": "ノーマル",
|
||||
"bold": "ボールド",
|
||||
"fontSize": "フォントサイズ",
|
||||
"heightOffset": "高さのオフセット",
|
||||
"fontColor": "フォントの色",
|
||||
"outlineColor": "アウトラインの色",
|
||||
"outlineSize": "アウトラインのサイズ",
|
||||
"backgroundOpacity": "背景の不透明度",
|
||||
"shadow": "影",
|
||||
"played": "再生済み",
|
||||
"unPlayed": "未再生",
|
||||
"resumable": "再開可能",
|
||||
"sortOrder": "並び順",
|
||||
"sortName": "名前",
|
||||
"communityRating": "コミュニティ評価",
|
||||
"parentalRating": "ペアレンタル評価",
|
||||
"dateAdded": "追加日",
|
||||
"dateLastContentAdded": "最後にコンテンツが追加された日",
|
||||
"favorite": "お気に入り",
|
||||
"datePlayed": "再生日",
|
||||
"folders": "フォルダ",
|
||||
"playCount": "再生回数",
|
||||
"releaseDate": "リリース日",
|
||||
"runTime": "再生時間",
|
||||
"ascending": "昇順",
|
||||
"descending": "降順",
|
||||
"playFromStart": "{name} を最初から再生",
|
||||
"@playFromStart": {
|
||||
"description": "最初から再生",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minutes": "分",
|
||||
"@minutes": {
|
||||
"description": "分",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds": "秒",
|
||||
"@seconds": {
|
||||
"description": "秒",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": "{index} ページ",
|
||||
"@page": {
|
||||
"description": "ページ",
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set": "設定",
|
||||
"@set": {
|
||||
"description": "特定の値を設定するために使用",
|
||||
"context": "時間を設定"
|
||||
},
|
||||
"never": "なし",
|
||||
"selectTime": "時間を選択",
|
||||
"immediately": "すぐに",
|
||||
"timeAndAnnotation": "{minutes} と {seconds}",
|
||||
"@timeAndAnnotation": {
|
||||
"description": "timeAndAnnotation",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "String"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanYourFingerprintToAuthenticate": "指紋をスキャンして{user}を認証してください",
|
||||
"@scanYourFingerprintToAuthenticate": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanBiometricHint": "身元を確認する",
|
||||
"deleteFileFromSystem": "このアイテム {item} を削除すると、ファイルシステムとメディアライブラリの両方から削除されます。\n続行してもよろしいですか?",
|
||||
"@deleteFileFromSystem": {
|
||||
"description": "システムからファイルを削除",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notPartOfAlbum": "アルバムの一部ではありません",
|
||||
"retry": "再試行",
|
||||
"failedToLoadImage": "画像の読み込みに失敗しました",
|
||||
"save": "保存",
|
||||
"metaDataSavedFor": "{item} のメタデータが保存されました",
|
||||
"@metaDataSavedFor": {
|
||||
"description": "メタデータが保存されました",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraryPageSizeTitle": "ライブラリページのサイズ",
|
||||
"libraryPageSizeDesc": "一度に読み込む量を設定します。0はページングを無効にします。",
|
||||
"fetchingLibrary": "ライブラリアイテムを取得しています",
|
||||
"libraryFetchNoItemsFound": "アイテムが見つかりませんでした。設定を変更してみてください。",
|
||||
"noSuggestionsFound": "提案が見つかりません",
|
||||
"viewPhotos": "写真を見る",
|
||||
"random": "ランダム",
|
||||
"tag": "{count, plural, one{タグ} other{タグ}}",
|
||||
"@tag": {
|
||||
"description": "タグ",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": "保存済み",
|
||||
"discovered": "発見済み",
|
||||
"noServersFound": "新しいサーバーが見つかりません",
|
||||
"about": "情報",
|
||||
"openParent": "親フォルダを開く",
|
||||
"mouseDragSupport": "マウスでドラッグ",
|
||||
"controls": "コントロール"
|
||||
}
|
||||
669
lib/l10n/app_nl.arb
Normal file
669
lib/l10n/app_nl.arb
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
{
|
||||
"@@locale": "nl",
|
||||
"switchUser": "Wissel gebruiker",
|
||||
"userName": "Gebruikersnaam",
|
||||
"password": "Wachtwoord",
|
||||
"login": "Inloggen",
|
||||
"logout": "Uitloggen",
|
||||
"cancel": "Annuleren",
|
||||
"accept": "Accepteren",
|
||||
"code": "Code",
|
||||
"error": "Fout",
|
||||
"clear": "Wissen",
|
||||
"days": "Dagen",
|
||||
"search": "Zoeken",
|
||||
"loggedIn": "Ingelogd",
|
||||
"change": "Wijzig",
|
||||
"other": "Overig",
|
||||
"dynamicText": "Dynamisch",
|
||||
"enabled": "Ingeschakeld",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"dashboard": "Dashboard",
|
||||
"advanced": "Geavanceerd",
|
||||
"refresh": "Vernieuwen",
|
||||
"delete": "Verwijderen",
|
||||
"goTo": "Ga naar",
|
||||
"loop": "Lus",
|
||||
"empty": "Leeg",
|
||||
"noRating": "Geen beoordeling",
|
||||
"backgroundBlur": "Achtergrond vervagen",
|
||||
"autoPlay": "Automatisch afspelen",
|
||||
"resume": "{item} Hervatten",
|
||||
"@resume": {
|
||||
"description": "hervatten",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"play": "{item} Afspelen",
|
||||
"@play": {
|
||||
"description": "Speel met",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"read": "{item} Lezen",
|
||||
"@read": {
|
||||
"description": "lezen",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readFromStart": "{item} vanaf het begin lezen",
|
||||
"@readFromStart": {
|
||||
"description": "Lees boek vanaf het begin",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFrom": "Speel vanaf {name}",
|
||||
"@playFrom": {
|
||||
"description": "speel vanaf",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFromStart": "{name} Vanaf het begin afspelen",
|
||||
"@playFromStart": {
|
||||
"description": "speel vanaf het begin",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreFrom": "Meer van {info}",
|
||||
"@moreFrom": {
|
||||
"description": "Meer van",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedWith": "Geselecteerd {info}",
|
||||
"@selectedWith": {
|
||||
"description": "geselecteerd",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected": "Geselecteerd",
|
||||
"restart": "Herstarten",
|
||||
"reWatch": "Opnieuw bekijken",
|
||||
"watchOn": "Kijk op",
|
||||
"options": "Opties",
|
||||
"list": "Lijst",
|
||||
"grid": "Raster",
|
||||
"masonry": "Metseleffect",
|
||||
"start": "Start",
|
||||
"none": "Geen",
|
||||
"chapter": "{count, plural, other{Hoofdstukken} one{Hoofdstuk}}",
|
||||
"@chapter": {
|
||||
"description": "hoofdstuk",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sync": "Synchroniseren",
|
||||
"moreOptions": "Meer opties",
|
||||
"continuePage": "Doorgaan - pagina {page}",
|
||||
"@continuePage": {
|
||||
"description": "Doorgaan - pagina 1",
|
||||
"placeholders": {
|
||||
"page": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openShow": "Open show",
|
||||
"showDetails": "Toon details",
|
||||
"showAlbum": "Toon album",
|
||||
"season": "{count, plural, other{Seizoenen} one{Seizoen}}",
|
||||
"@season": {
|
||||
"description": "seizoen",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{Afleveringen} one{Aflevering}}",
|
||||
"@episode": {
|
||||
"description": "aflevering",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToCollection": "Toevoegen aan collectie",
|
||||
"addToPlaylist": "Toevoegen aan afspeellijst",
|
||||
"removeFromCollection": "Verwijderen uit collectie",
|
||||
"removeFromPlaylist": "Verwijderen uit afspeellijst",
|
||||
"markAsWatched": "Markeer als bekeken",
|
||||
"markAsUnwatched": "Markeer als niet bekeken",
|
||||
"removeAsFavorite": "Verwijderen als favoriet",
|
||||
"addAsFavorite": "Toevoegen als favoriet",
|
||||
"editMetadata": "Metadata bewerken",
|
||||
"refreshMetadata": "Metadata vernieuwen",
|
||||
"syncDetails": "Details synchroniseren",
|
||||
"identify": "Identificeren",
|
||||
"info": "Info",
|
||||
"clearAllSettings": "Alle instellingen wissen",
|
||||
"clearAllSettingsQuestion": "Alle instellingen wissen?",
|
||||
"unableToReverseAction": "Deze actie kan niet ongedaan worden gemaakt, het verwijdert alle instellingen.",
|
||||
"navigationDashboard": "Dashboard",
|
||||
"navigationFavorites": "Favorieten",
|
||||
"navigationSync": "Gesynchroniseerd",
|
||||
"navigation": "Navigatie",
|
||||
"library": "{count, plural, other{Bibliotheken} one{Bibliotheek}}",
|
||||
"@library": {
|
||||
"description": "meervoud",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanLibrary": "Scan bibliotheek",
|
||||
"dashboardContinue": "Doorgaan",
|
||||
"dashboardContinueWatching": "Doorgaan met kijken",
|
||||
"dashboardContinueReading": "Doorgaan met lezen",
|
||||
"dashboardContinueListening": "Doorgaan met luisteren",
|
||||
"dashboardNextUp": "Volgende",
|
||||
"dashboardRecentlyAdded": "Onlangs toegevoegd in {name}",
|
||||
"@dashboardRecentlyAdded": {
|
||||
"description": "Onlangs toegevoegd op startscherm",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": "Instellingen",
|
||||
"settingsClientTitle": "Fladder",
|
||||
"settingsClientDesc": "Algemeen, Time-out, Lay-out, Thema",
|
||||
"settingsQuickConnectTitle": "Snel verbinden",
|
||||
"settingsProfileTitle": "Profiel",
|
||||
"settingsProfileDesc": "Vergrendelscherm",
|
||||
"settingsPlayerTitle": "Speler",
|
||||
"settingsPlayerDesc": "Beeldverhouding, Geavanceerd",
|
||||
"logoutUserPopupTitle": "Gebruiker {userName} uitloggen?",
|
||||
"@logoutUserPopupTitle": {
|
||||
"description": "Pop-up voor uitloggen gebruiker",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logoutUserPopupContent": "Dit zal {userName} uitloggen en de gebruiker uit de app verwijderen.\nJe moet opnieuw inloggen op {serverName}.",
|
||||
"@logoutUserPopupContent": {
|
||||
"description": "Pop-up voor uitloggen gebruiker beschrijving",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
},
|
||||
"serverName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quickConnectTitle": "Snel verbinden",
|
||||
"quickConnectAction": "Voer snelverbind code in voor",
|
||||
"quickConnectInputACode": "Voer een code in",
|
||||
"quickConnectWrongCode": "Verkeerde code",
|
||||
"downloadsTitle": "Downloads",
|
||||
"downloadsPath": "Pad",
|
||||
"pathEditTitle": "Locatie wijzigen",
|
||||
"pathEditSelect": "Selecteer downloadbestemming",
|
||||
"pathClearTitle": "Downloadpad wissen",
|
||||
"pathEditDesc": "Deze locatie is ingesteld voor alle gebruikers, gesynchroniseerde gegevens zijn niet meer toegankelijk.\nHet blijft op je opslag.",
|
||||
"downloadsSyncedData": "Gesynchroniseerde gegevens",
|
||||
"downloadsClearTitle": "Gesynchroniseerde gegevens wissen",
|
||||
"downloadsClearDesc": "Weet je zeker dat je alle gesynchroniseerde gegevens wilt verwijderen?\nDit wist alle gegevens voor elke gesynchroniseerde gebruiker!",
|
||||
"lockscreen": "Vergrendelscherm",
|
||||
"timeOut": "Time-out",
|
||||
"home": "Home",
|
||||
"settingsHomeCarouselTitle": "Dashboard carrousel",
|
||||
"settingsHomeCarouselDesc": "Toont een carrousel op het dashboard scherm",
|
||||
"settingsHomeNextUpTitle": "Volgende posters",
|
||||
"settingsHomeNextUpDesc": "Soort posters getoond op het dashboard scherm",
|
||||
"settingsVisual": "Visueel",
|
||||
"settingsBlurredPlaceholderTitle": "Vervaagde placeholder",
|
||||
"settingsBlurredPlaceholderDesc": "Toon vervaagde achtergrond bij het laden van posters",
|
||||
"settingsBlurEpisodesTitle": "Vervagen volgende afleveringen",
|
||||
"settingsBlurEpisodesDesc": "Vervaag alle komende afleveringen",
|
||||
"settingsEnableOsMediaControls": "OS media bediening inschakelen",
|
||||
"settingsNextUpCutoffDays": "Volgende cutoff dagen",
|
||||
"settingsShowScaleSlider": "Toon posterschaal schuifregelaar",
|
||||
"settingsPosterSize": "Poster grootte",
|
||||
"settingsPosterSlider": "Toon schaal schuifregelaar",
|
||||
"settingsPosterPinch": "Knijp-zoom om posters te schalen",
|
||||
"theme": "Thema",
|
||||
"mode": "Modus",
|
||||
"themeModeSystem": "Systeem",
|
||||
"themeModeLight": "Licht",
|
||||
"themeModeDark": "Donker",
|
||||
"themeColor": "Thema kleur",
|
||||
"color": "Kleur",
|
||||
"amoledBlack": "Amoled zwart",
|
||||
"hide": "Verbergen",
|
||||
"nextUp": "Volgende",
|
||||
"settingsContinue": "Doorgaan",
|
||||
"separate": "Gescheiden",
|
||||
"combined": "Gecombineerd",
|
||||
"settingsSecurity": "Beveiliging",
|
||||
"settingSecurityApplockTitle": "App vergrendelen",
|
||||
"appLockTitle": "Stel de inlogmethode in voor {userName}",
|
||||
"@appLockTitle": {
|
||||
"description": "Pop-up om een inlogmethode te kiezen",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"biometricsFailedCheckAgain": "Biometrie mislukt, controleer de instellingen en probeer opnieuw",
|
||||
"appLockAutoLogin": "Automatisch inloggen",
|
||||
"appLockPasscode": "Wachtwoord",
|
||||
"appLockBiometrics": "Biometrie",
|
||||
"settingsPlayerVideoHWAccelTitle": "Hardwareversnelling",
|
||||
"settingsPlayerVideoHWAccelDesc": "Gebruik de gpu om video weer te geven (aanbevolen)",
|
||||
"settingsPlayerNativeLibassAccelTitle": "Native libass ondertiteling",
|
||||
"settingsPlayerNativeLibassAccelDesc": "Gebruik videospeler libass ondertitel renderer",
|
||||
"settingsPlayerMobileWarning": "Hardwareversnelling en native libass ondertitels inschakelen op Android kan ervoor zorgen dat sommige ondertitels niet worden weergegeven.",
|
||||
"settingsPlayerCustomSubtitlesTitle": "Ondertitels aanpassen",
|
||||
"settingsPlayerCustomSubtitlesDesc": "Pas grootte, kleur, positie, omtrek aan",
|
||||
"videoScalingFillScreenTitle": "Vul scherm",
|
||||
"videoScalingFillScreenDesc": "Vul de navigatie- en statusbalk",
|
||||
"videoScalingFillScreenNotif": "Volledig scherm overschrijft videopasvorm, in horizontale rotatie",
|
||||
"videoScaling": "Videoschaling",
|
||||
"videoScalingFill": "Vullen",
|
||||
"videoScalingContain": "Bevatten",
|
||||
"videoScalingCover": "Bedekken",
|
||||
"videoScalingFitWidth": "Pas breedte aan",
|
||||
"videoScalingFitHeight": "Pas hoogte aan",
|
||||
"videoScalingScaleDown": "Schaal omlaag",
|
||||
"subtitleConfiguratorPlaceHolder": "Dit is placeholder tekst,\nniets te zien hier.",
|
||||
"subtitleConfigurator": "Ondertitel configurator",
|
||||
"refreshPopup": "Vernieuwen - {name}",
|
||||
"@refreshPopup": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanningName": "Scannen - {name}",
|
||||
"@scanningName": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshPopupContentMetadata": "Metadata wordt vernieuwd op basis van instellingen en internetdiensten die zijn ingeschakeld in het Dashboard.",
|
||||
"replaceExistingImages": "Bestaande afbeeldingen vervangen",
|
||||
"metadataRefreshDefault": "Scannen naar nieuwe en bijgewerkte bestanden",
|
||||
"metadataRefreshValidation": "Zoeken naar ontbrekende metadata",
|
||||
"metadataRefreshFull": "Alle metadata vervangen",
|
||||
"syncedItems": "Gesynchroniseerde items",
|
||||
"noItemsSynced": "Geen items gesynchroniseerd",
|
||||
"syncDeletePopupPermanent": "Deze actie is permanent en verwijdert alle lokaal gesynchroniseerde bestanden",
|
||||
"totalSize": "Totale grootte: {size}",
|
||||
"@totalSize": {
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mediaTypeBase": "Basis type",
|
||||
"mediaTypeMovie": "Film",
|
||||
"mediaTypeSeries": "Serie",
|
||||
"mediaTypeSeason": "Seizoen",
|
||||
"mediaTypeEpisode": "Aflevering",
|
||||
"mediaTypePhoto": "Foto",
|
||||
"mediaTypePerson": "Persoon",
|
||||
"mediaTypePhotoAlbum": "Foto album",
|
||||
"mediaTypeFolder": "Map",
|
||||
"mediaTypeBoxset": "Boxset",
|
||||
"mediaTypePlaylist": "Afspeellijst",
|
||||
"mediaTypeBook": "Boek",
|
||||
"actor": "{count, plural, other{Acteurs} one{Acteur}}",
|
||||
"@actor": {
|
||||
"description": "acteur",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"writer": "{count, plural, other{Schrijvers} one{Schrijver}}",
|
||||
"@writer": {
|
||||
"description": "schrijver",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": "{count, plural, other{Regisseurs} one{Regisseur}}",
|
||||
"@director": {
|
||||
"description": "regisseur",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": "Video",
|
||||
"audio": "Audio",
|
||||
"subtitles": "Ondertitels",
|
||||
"related": "Gerelateerd",
|
||||
"all": "Alles",
|
||||
"overview": "Overzicht",
|
||||
"selectViewType": "Selecteer weergavetype",
|
||||
"noItemsToShow": "Geen items om te tonen",
|
||||
"sortBy": "Sorteer op",
|
||||
"groupBy": "Groepeer op",
|
||||
"scrollToTop": "Scroll naar boven",
|
||||
"disableFilters": "Filters uitschakelen",
|
||||
"selectAll": "Alles selecteren",
|
||||
"clearSelection": "Selectie wissen",
|
||||
"shuffleVideos": "Video's shuffle",
|
||||
"shuffleGallery": "Galerij shuffle",
|
||||
"unknown": "Onbekend",
|
||||
"favorites": "Favorieten",
|
||||
"recursive": "Recursief",
|
||||
"genre": "{count, plural, other{Genres} one{Genre}}",
|
||||
"@genre": {
|
||||
"description": "genre",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"studio": "{count, plural, other{Studio's} one{Studio}}",
|
||||
"@studio": {
|
||||
"description": "studio",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "{count, plural, other{Labels} one{Label}}",
|
||||
"@label": {
|
||||
"description": "label",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": "Groeperen",
|
||||
"type": "{count, plural, other{Types} one{Type}}",
|
||||
"@type": {
|
||||
"description": "type",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "{count, plural, other{Filters} one{Filter}}",
|
||||
"@filter": {
|
||||
"description": "filter",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showEmpty": "Toon leeg",
|
||||
"hideEmpty": "Verberg leeg",
|
||||
"rating": "{count, plural, other{Beoordelingen} one{Beoordeling}}",
|
||||
"@rating": {
|
||||
"description": "beoordeling",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"year": "{count, plural, other{Jaren} one{Jaar}}",
|
||||
"@year": {
|
||||
"description": "jaar",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playVideos": "Video's afspelen",
|
||||
"playLabel": "Afspelen",
|
||||
"forceRefresh": "Geforceerd vernieuwen",
|
||||
"itemCount": "Aantal items: {count}",
|
||||
"@itemCount": {
|
||||
"description": "Aantal items",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "Ongeldige url",
|
||||
"invalidUrlDesc": "Url moet beginnen met http(s)://",
|
||||
"incorrectPinTryAgain": "Onjuiste pin, probeer opnieuw",
|
||||
"somethingWentWrongPasswordCheck": "Er is iets misgegaan, controleer uw wachtwoord",
|
||||
"unableToConnectHost": "Kan geen verbinding maken met host",
|
||||
"server": "Server",
|
||||
"retrievePublicListOfUsers": "Publieke lijst van gebruikers ophalen",
|
||||
"displayLanguage": "Weergavetaal",
|
||||
"deleteItem": "{item} verwijderen?",
|
||||
"@deleteItem": {
|
||||
"description": "deleteItem",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncDeleteItemTitle": "Gesynchroniseerd item verwijderen",
|
||||
"syncDeleteItemDesc": "Alle gesynchroniseerde gegevens verwijderen voor?\n{item}",
|
||||
"@syncDeleteItemDesc": {
|
||||
"description": "Pop-upvenster voor synchronisatie van te verwijderen item",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncOpenParent": "Ouder openen",
|
||||
"syncRemoveDataTitle": "Gesynchroniseerde gegevens verwijderen?",
|
||||
"syncRemoveDataDesc": "Gesynchroniseerde videogegevens verwijderen? Dit is permanent en u moet de bestanden opnieuw synchroniseren",
|
||||
"collectionFolder": "Collectiemap",
|
||||
"musicAlbum": "Album",
|
||||
"active": "Actief",
|
||||
"name": "Naam",
|
||||
"result": "Resultaat",
|
||||
"close": "Sluiten",
|
||||
"replaceAllImages": "Alle afbeeldingen vervangen",
|
||||
"noResults": "Geen resultaten",
|
||||
"openWebLink": "Weblink openen",
|
||||
"setIdentityTo": "Identiteit instellen op {name}",
|
||||
"@setIdentityTo": {
|
||||
"description": "setIdentityTo",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"somethingWentWrong": "Er is iets misgegaan",
|
||||
"clearChanges": "Wijzigingen wissen",
|
||||
"useDefaults": "Standaardwaarden gebruiken",
|
||||
"light": "Licht",
|
||||
"normal": "Normaal",
|
||||
"bold": "Vet",
|
||||
"fontSize": "Lettergrootte",
|
||||
"heightOffset": "Hoogte-offset",
|
||||
"fontColor": "Letterkleur",
|
||||
"outlineColor": "Omtreklijnkleur",
|
||||
"outlineSize": "Omtreklijn grootte",
|
||||
"backgroundOpacity": "Achtergrond doorzichtigheid",
|
||||
"shadow": "Schaduw",
|
||||
"played": "Gespeeld",
|
||||
"unPlayed": "Ongespeeld",
|
||||
"resumable": "Hervatbaar",
|
||||
"sortOrder": "Sorteervolgorde",
|
||||
"sortName": "Naam",
|
||||
"communityRating": "Gemeenschapswaardering",
|
||||
"parentalRating": "Ouderlijk toezicht beoordeling",
|
||||
"dateAdded": "Datum toegevoegd",
|
||||
"dateLastContentAdded": "Datum laatste inhoud toegevoegd",
|
||||
"favorite": "Favoriet",
|
||||
"datePlayed": "Datum afgespeeld",
|
||||
"folders": "Mappen",
|
||||
"playCount": "Aantal keren afgespeeld",
|
||||
"releaseDate": "Releasedatum",
|
||||
"runTime": "Looptijd",
|
||||
"ascending": "Oplopend",
|
||||
"descending": "Aflopend",
|
||||
"minutes": "{count, plural, other{Minuten} one{Minuut}}",
|
||||
"@minutes": {
|
||||
"description": "minuut",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds": "{count, plural, other{Seconden} one{Seconde}}",
|
||||
"@seconds": {
|
||||
"description": "seconde",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": "Pagina {index}",
|
||||
"@page": {
|
||||
"description": "pagina",
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set": "Instellen",
|
||||
"@set": {
|
||||
"description": "Gebruik voor het instellen van een bepaalde waarde",
|
||||
"context": "Instellen 'tijd'"
|
||||
},
|
||||
"never": "Nooit",
|
||||
"selectTime": "Tijd selecteren",
|
||||
"immediately": "Direct",
|
||||
"timeAndAnnotation": "{minutes} en {seconds}",
|
||||
"@timeAndAnnotation": {
|
||||
"description": "timeAndAnnotation",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "String"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanYourFingerprintToAuthenticate": "Scan je vingerafdruk om {user} te verifiëren",
|
||||
"@scanYourFingerprintToAuthenticate": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanBiometricHint": "Identiteit verifiëren",
|
||||
"deleteFileFromSystem": "Het verwijderen van dit item {item} zal het zowel uit het bestandssysteem als uit je mediatheek verwijderen.\nWeet je zeker dat je wilt doorgaan?",
|
||||
"@deleteFileFromSystem": {
|
||||
"description": "Bestand uit systeem verwijderen",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notPartOfAlbum": "Maakt geen deel uit van een album",
|
||||
"retry": "Opnieuw proberen",
|
||||
"failedToLoadImage": "Kan afbeelding niet laden",
|
||||
"save": "Opslaan",
|
||||
"metaDataSavedFor": "Metadata opgeslagen voor {item}",
|
||||
"@metaDataSavedFor": {
|
||||
"description": "metadataOpgeslagenVoor",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraryPageSizeTitle": "Bibliotheek paginagrootte",
|
||||
"libraryPageSizeDesc": "Stel de hoeveelheid in om per keer te laden. 0 schakelt paginering uit.",
|
||||
"fetchingLibrary": "Bibliotheek items ophalen",
|
||||
"libraryFetchNoItemsFound": "Geen items gevonden, probeer andere instellingen.",
|
||||
"noSuggestionsFound": "Geen suggesties gevonden",
|
||||
"viewPhotos": "Foto's bekijken",
|
||||
"random": "Willekeurig",
|
||||
"tag": "{count, plural, one{Label} other{Labels}}",
|
||||
"@tag": {
|
||||
"description": "label",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": "Opgeslagen",
|
||||
"discovered": "Ontdekt",
|
||||
"noServersFound": "Geen nieuwe servers gevonden",
|
||||
"about": "Over",
|
||||
"openParent": "Bovenliggende map openen",
|
||||
"mouseDragSupport": "Slepen met de muis",
|
||||
"controls": "Bediening"
|
||||
}
|
||||
669
lib/l10n/app_zh.arb
Normal file
669
lib/l10n/app_zh.arb
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
{
|
||||
"@@locale": "zh",
|
||||
"switchUser": "切换用户",
|
||||
"userName": "用户名",
|
||||
"password": "密码",
|
||||
"login": "登录",
|
||||
"logout": "登出",
|
||||
"cancel": "取消",
|
||||
"accept": "接受",
|
||||
"code": "代码",
|
||||
"error": "错误",
|
||||
"clear": "清除",
|
||||
"days": "天",
|
||||
"search": "搜索",
|
||||
"loggedIn": "已登录",
|
||||
"change": "更改",
|
||||
"other": "其他",
|
||||
"dynamicText": "动态",
|
||||
"enabled": "启用",
|
||||
"disabled": "禁用",
|
||||
"dashboard": "仪表板",
|
||||
"advanced": "高级",
|
||||
"refresh": "刷新",
|
||||
"delete": "删除",
|
||||
"goTo": "去往",
|
||||
"loop": "循环",
|
||||
"empty": "空",
|
||||
"noRating": "无评分",
|
||||
"backgroundBlur": "背景模糊",
|
||||
"autoPlay": "自动播放",
|
||||
"resume": "继续 {item}",
|
||||
"@resume": {
|
||||
"description": "继续",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"play": "播放 {item}",
|
||||
"@play": {
|
||||
"description": "播放",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"read": "阅读 {item}",
|
||||
"@read": {
|
||||
"description": "阅读",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readFromStart": "从头开始阅读 {item}",
|
||||
"@readFromStart": {
|
||||
"description": "从头开始阅读书籍",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playFrom": "从 {name} 开始播放",
|
||||
"@playFrom": {
|
||||
"description": "从...开始播放",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreFrom": "更多来自 {info}",
|
||||
"@moreFrom": {
|
||||
"description": "更多来自",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedWith": "选择了 {info}",
|
||||
"@selectedWith": {
|
||||
"description": "选择",
|
||||
"placeholders": {
|
||||
"info": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selected": "已选择",
|
||||
"restart": "重新开始",
|
||||
"reWatch": "重新观看",
|
||||
"watchOn": "观看于",
|
||||
"options": "选项",
|
||||
"list": "列表",
|
||||
"grid": "网格",
|
||||
"masonry": "砖石",
|
||||
"start": "开始",
|
||||
"none": "无",
|
||||
"chapter": "{count, plural, other{章节} one{章节}}",
|
||||
"@chapter": {
|
||||
"description": "章节",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sync": "同步",
|
||||
"moreOptions": "更多选项",
|
||||
"continuePage": "继续 - 第 {page} 页",
|
||||
"@continuePage": {
|
||||
"description": "继续 - 第 1 页",
|
||||
"placeholders": {
|
||||
"page": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openShow": "打开节目",
|
||||
"showDetails": "显示详情",
|
||||
"showAlbum": "显示相册",
|
||||
"season": "{count, plural, other{季} one{季}}",
|
||||
"@season": {
|
||||
"description": "季",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"episode": "{count, plural, other{集} one{集}}",
|
||||
"@episode": {
|
||||
"description": "集",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"addToCollection": "添加到收藏",
|
||||
"addToPlaylist": "添加到播放列表",
|
||||
"removeFromCollection": "从收藏中删除",
|
||||
"removeFromPlaylist": "从播放列表中删除",
|
||||
"markAsWatched": "标记为已观看",
|
||||
"markAsUnwatched": "标记为未观看",
|
||||
"removeAsFavorite": "取消收藏",
|
||||
"addAsFavorite": "添加为收藏",
|
||||
"editMetadata": "编辑元数据",
|
||||
"refreshMetadata": "刷新元数据",
|
||||
"syncDetails": "同步详情",
|
||||
"identify": "识别",
|
||||
"info": "信息",
|
||||
"clearAllSettings": "清除所有设置",
|
||||
"clearAllSettingsQuestion": "清除所有设置?",
|
||||
"unableToReverseAction": "此操作无法撤销,它将删除所有设置。",
|
||||
"navigationDashboard": "仪表板",
|
||||
"navigationFavorites": "收藏夹",
|
||||
"navigationSync": "已同步",
|
||||
"navigation": "导航",
|
||||
"library": "{count, plural, other{库} one{库}}",
|
||||
"@library": {
|
||||
"description": "复数",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanLibrary": "扫描库",
|
||||
"dashboardContinue": "继续",
|
||||
"dashboardContinueWatching": "继续观看",
|
||||
"dashboardContinueReading": "继续阅读",
|
||||
"dashboardContinueListening": "继续听",
|
||||
"dashboardNextUp": "下一个",
|
||||
"dashboardRecentlyAdded": "最近添加于 {name}",
|
||||
"@dashboardRecentlyAdded": {
|
||||
"description": "最近添加到首页",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": "设置",
|
||||
"settingsClientTitle": "Fladder",
|
||||
"settingsClientDesc": "常规,超时,布局,主题",
|
||||
"settingsQuickConnectTitle": "快速连接",
|
||||
"settingsProfileTitle": "个人资料",
|
||||
"settingsProfileDesc": "锁屏",
|
||||
"settingsPlayerTitle": "播放器",
|
||||
"settingsPlayerDesc": "纵横比,高级",
|
||||
"logoutUserPopupTitle": "登出用户 {userName}?",
|
||||
"@logoutUserPopupTitle": {
|
||||
"description": "登出用户的弹窗",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logoutUserPopupContent": "这将登出 {userName} 并从应用程序中删除用户。\n你需要重新登录到 {serverName}。",
|
||||
"@logoutUserPopupContent": {
|
||||
"description": "登出用户的弹窗描述",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
},
|
||||
"serverName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"quickConnectTitle": "快速连接",
|
||||
"quickConnectAction": "输入快速连接代码",
|
||||
"quickConnectInputACode": "输入代码",
|
||||
"quickConnectWrongCode": "错误的代码",
|
||||
"downloadsTitle": "下载",
|
||||
"downloadsPath": "路径",
|
||||
"pathEditTitle": "更改位置",
|
||||
"pathEditSelect": "选择下载目标",
|
||||
"pathClearTitle": "清除下载路径",
|
||||
"pathEditDesc": "此位置设置适用于所有用户,任何同步的数据将不再可访问。\n它将保留在你的存储中。",
|
||||
"downloadsSyncedData": "同步数据",
|
||||
"downloadsClearTitle": "清除同步数据",
|
||||
"downloadsClearDesc": "你确定要删除所有同步数据吗?\n这将清除每个同步用户的所有数据!",
|
||||
"lockscreen": "锁屏",
|
||||
"timeOut": "超时",
|
||||
"home": "主页",
|
||||
"settingsHomeCarouselTitle": "仪表板轮播",
|
||||
"settingsHomeCarouselDesc": "在仪表板屏幕上显示轮播",
|
||||
"settingsHomeNextUpTitle": "下一个海报",
|
||||
"settingsHomeNextUpDesc": "仪表板屏幕上显示的海报类型",
|
||||
"settingsVisual": "视觉",
|
||||
"settingsBlurredPlaceholderTitle": "模糊的占位符",
|
||||
"settingsBlurredPlaceholderDesc": "加载海报时显示模糊的背景",
|
||||
"settingsBlurEpisodesTitle": "模糊下一个集数",
|
||||
"settingsBlurEpisodesDesc": "模糊所有即将播放的集数",
|
||||
"settingsEnableOsMediaControls": "启用操作系统媒体控制",
|
||||
"settingsNextUpCutoffDays": "下一个截止天数",
|
||||
"settingsShowScaleSlider": "显示海报大小滑块",
|
||||
"settingsPosterSize": "海报大小",
|
||||
"settingsPosterSlider": "显示比例滑块",
|
||||
"settingsPosterPinch": "捏合缩放以调整海报大小",
|
||||
"theme": "主题",
|
||||
"mode": "模式",
|
||||
"themeModeSystem": "系统",
|
||||
"themeModeLight": "浅色",
|
||||
"themeModeDark": "深色",
|
||||
"themeColor": "主题颜色",
|
||||
"color": "颜色",
|
||||
"amoledBlack": "Amoled 黑色",
|
||||
"hide": "隐藏",
|
||||
"nextUp": "下一个",
|
||||
"settingsContinue": "继续",
|
||||
"separate": "分开",
|
||||
"combined": "组合",
|
||||
"settingsSecurity": "安全",
|
||||
"settingSecurityApplockTitle": "应用锁",
|
||||
"appLockTitle": "设置 {userName} 的登录方式",
|
||||
"@appLockTitle": {
|
||||
"description": "选择登录方式的弹窗",
|
||||
"placeholders": {
|
||||
"userName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"biometricsFailedCheckAgain": "生物识别失败,请检查设置并重试",
|
||||
"appLockAutoLogin": "自动登录",
|
||||
"appLockPasscode": "密码",
|
||||
"appLockBiometrics": "生物识别",
|
||||
"settingsPlayerVideoHWAccelTitle": "硬件加速",
|
||||
"settingsPlayerVideoHWAccelDesc": "使用 GPU 渲染视频(推荐)",
|
||||
"settingsPlayerNativeLibassAccelTitle": "本地 libass 字幕",
|
||||
"settingsPlayerNativeLibassAccelDesc": "使用视频播放器 libass 字幕渲染器",
|
||||
"settingsPlayerMobileWarning": "在 Android 上启用硬件加速和本地 libass 字幕可能会导致某些字幕无法渲染。",
|
||||
"settingsPlayerCustomSubtitlesTitle": "自定义字幕",
|
||||
"settingsPlayerCustomSubtitlesDesc": "自定义大小、颜色、位置、轮廓",
|
||||
"videoScalingFillScreenTitle": "填满屏幕",
|
||||
"videoScalingFillScreenDesc": "填充导航栏和状态栏",
|
||||
"videoScalingFillScreenNotif": "全屏覆盖视频适应,在水平旋转中",
|
||||
"videoScaling": "视频缩放",
|
||||
"videoScalingFill": "填充",
|
||||
"videoScalingContain": "包含",
|
||||
"videoScalingCover": "覆盖",
|
||||
"videoScalingFitWidth": "适应宽度",
|
||||
"videoScalingFitHeight": "适应高度",
|
||||
"videoScalingScaleDown": "缩小",
|
||||
"subtitleConfiguratorPlaceHolder": "这是占位符文本,\n这里没有什么可看的。",
|
||||
"subtitleConfigurator": "字幕配置器",
|
||||
"refreshPopup": "刷新 - {name}",
|
||||
"@refreshPopup": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanningName": "扫描 - {name}",
|
||||
"@scanningName": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshPopupContentMetadata": "根据仪表板中启用的设置和互联网服务刷新元数据。",
|
||||
"replaceExistingImages": "替换现有图像",
|
||||
"metadataRefreshDefault": "扫描新文件和更新的文件",
|
||||
"metadataRefreshValidation": "搜索缺失的元数据",
|
||||
"metadataRefreshFull": "替换所有元数据",
|
||||
"syncedItems": "同步项目",
|
||||
"noItemsSynced": "没有同步项目",
|
||||
"syncDeletePopupPermanent": "此操作是永久性的,将删除所有本地同步的文件",
|
||||
"totalSize": "总大小:{size}",
|
||||
"@totalSize": {
|
||||
"placeholders": {
|
||||
"size": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mediaTypeBase": "基础类型",
|
||||
"mediaTypeMovie": "电影",
|
||||
"mediaTypeSeries": "系列",
|
||||
"mediaTypeSeason": "季",
|
||||
"mediaTypeEpisode": "集",
|
||||
"mediaTypePhoto": "照片",
|
||||
"mediaTypePerson": "人物",
|
||||
"mediaTypePhotoAlbum": "相册",
|
||||
"mediaTypeFolder": "文件夹",
|
||||
"mediaTypeBoxset": "套装",
|
||||
"mediaTypePlaylist": "播放列表",
|
||||
"mediaTypeBook": "书籍",
|
||||
"actor": "{count, plural, other{演员} one{演员}}",
|
||||
"@actor": {
|
||||
"description": "演员",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"writer": "{count, plural, other{作家} one{作家}}",
|
||||
"@writer": {
|
||||
"description": "作家",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": "{count, plural, other{导演} one{导演}}",
|
||||
"@director": {
|
||||
"description": "导演",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"video": "视频",
|
||||
"audio": "音频",
|
||||
"subtitles": "字幕",
|
||||
"related": "相关",
|
||||
"all": "全部",
|
||||
"overview": "概述",
|
||||
"selectViewType": "选择查看类型",
|
||||
"noItemsToShow": "没有可显示的项目",
|
||||
"sortBy": "排序方式",
|
||||
"groupBy": "分组依据",
|
||||
"scrollToTop": "滚动到顶部",
|
||||
"disableFilters": "禁用过滤器",
|
||||
"selectAll": "全选",
|
||||
"clearSelection": "清除选择",
|
||||
"shuffleVideos": "随机播放视频",
|
||||
"shuffleGallery": "随机播放图库",
|
||||
"unknown": "未知",
|
||||
"favorites": "收藏",
|
||||
"recursive": "递归",
|
||||
"genre": "{count, plural, other{类型} one{类型}}",
|
||||
"@genre": {
|
||||
"description": "类型",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"studio": "{count, plural, other{工作室} one{工作室}}",
|
||||
"@studio": {
|
||||
"description": "工作室",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": "{count, plural, other{标签} one{标签}}",
|
||||
"@label": {
|
||||
"description": "标签",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"group": "组",
|
||||
"type": "{count, plural, other{类型} one{类型}}",
|
||||
"@type": {
|
||||
"description": "类型",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "{count, plural, other{过滤器} one{过滤器}}",
|
||||
"@filter": {
|
||||
"description": "过滤器",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"showEmpty": "显示空",
|
||||
"hideEmpty": "隐藏空",
|
||||
"rating": "{count, plural, other{评分} one{评分}}",
|
||||
"@rating": {
|
||||
"description": "评分",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"year": "{count, plural, other{年} one{年}}",
|
||||
"@year": {
|
||||
"description": "年",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"playVideos": "播放视频",
|
||||
"playLabel": "播放",
|
||||
"forceRefresh": "强制刷新",
|
||||
"itemCount": "项目数:{count}",
|
||||
"@itemCount": {
|
||||
"description": "项目数",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "无效的 URL",
|
||||
"invalidUrlDesc": "URL 必须以 http(s):// 开头",
|
||||
"incorrectPinTryAgain": "错误的 PIN,请重试",
|
||||
"somethingWentWrongPasswordCheck": "出了点问题,请检查您的密码",
|
||||
"unableToConnectHost": "无法连接到主机",
|
||||
"server": "服务器",
|
||||
"retrievePublicListOfUsers": "检索公共用户列表",
|
||||
"displayLanguage": "显示语言 (显示语言)",
|
||||
"deleteItem": "删除 {item}?",
|
||||
"@deleteItem": {
|
||||
"description": "deleteItem",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncDeleteItemTitle": "删除同步的项目",
|
||||
"syncDeleteItemDesc": "删除所有同步数据?\n{item}",
|
||||
"@syncDeleteItemDesc": {
|
||||
"description": "同步删除项目弹出窗口",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"syncOpenParent": "打开父级",
|
||||
"syncRemoveDataTitle": "删除同步的数据?",
|
||||
"syncRemoveDataDesc": "删除同步的视频数据?这是永久性的,您需要重新同步文件",
|
||||
"collectionFolder": "收藏夹",
|
||||
"musicAlbum": "专辑",
|
||||
"active": "活跃",
|
||||
"name": "名称",
|
||||
"result": "结果",
|
||||
"close": "关闭",
|
||||
"replaceAllImages": "替换所有图像",
|
||||
"noResults": "没有结果",
|
||||
"openWebLink": "打开网页链接",
|
||||
"setIdentityTo": "设置身份为 {name}",
|
||||
"@setIdentityTo": {
|
||||
"description": "setIdentityTo",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"somethingWentWrong": "出了些问题",
|
||||
"clearChanges": "清除更改",
|
||||
"useDefaults": "使用默认值",
|
||||
"light": "浅色",
|
||||
"normal": "正常",
|
||||
"bold": "粗体",
|
||||
"fontSize": "字体大小",
|
||||
"heightOffset": "高度偏移",
|
||||
"fontColor": "字体颜色",
|
||||
"outlineColor": "轮廓颜色",
|
||||
"outlineSize": "轮廓大小",
|
||||
"backgroundOpacity": "背景不透明度",
|
||||
"shadow": "阴影",
|
||||
"played": "已播放",
|
||||
"unPlayed": "未播放",
|
||||
"resumable": "可恢复",
|
||||
"sortOrder": "排序顺序",
|
||||
"sortName": "名称",
|
||||
"communityRating": "社区评分",
|
||||
"parentalRating": "家长评级",
|
||||
"dateAdded": "添加日期",
|
||||
"dateLastContentAdded": "最后添加内容的日期",
|
||||
"favorite": "收藏",
|
||||
"datePlayed": "播放日期",
|
||||
"folders": "文件夹",
|
||||
"playCount": "播放次数",
|
||||
"releaseDate": "发布日期",
|
||||
"runTime": "运行时间",
|
||||
"ascending": "升序",
|
||||
"descending": "降序",
|
||||
"playFromStart": "从头播放 {name}",
|
||||
"@playFromStart": {
|
||||
"description": "从头播放",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minutes": "分钟",
|
||||
"@minutes": {
|
||||
"description": "分钟",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"seconds": "秒",
|
||||
"@seconds": {
|
||||
"description": "秒",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page": "第 {index} 页",
|
||||
"@page": {
|
||||
"description": "页数",
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"set": "设置",
|
||||
"@set": {
|
||||
"description": "用于设置某个值",
|
||||
"context": "设置时间"
|
||||
},
|
||||
"never": "从不",
|
||||
"selectTime": "选择时间",
|
||||
"immediately": "立即",
|
||||
"timeAndAnnotation": "{minutes} 分 {seconds} 秒",
|
||||
"@timeAndAnnotation": {
|
||||
"description": "时间和注释",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"type": "String"
|
||||
},
|
||||
"seconds": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanYourFingerprintToAuthenticate": "扫描您的指纹以验证 {user}",
|
||||
"@scanYourFingerprintToAuthenticate": {
|
||||
"placeholders": {
|
||||
"user": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scanBiometricHint": "验证身份",
|
||||
"deleteFileFromSystem": "删除此项目 {item} 将从文件系统和您的媒体库中删除它。\n您确定要继续吗?",
|
||||
"@deleteFileFromSystem": {
|
||||
"description": "从系统中删除文件",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notPartOfAlbum": "不属于专辑的一部分",
|
||||
"retry": "重试",
|
||||
"failedToLoadImage": "加载图像失败",
|
||||
"save": "保存",
|
||||
"metaDataSavedFor": "{item} 的元数据已保存",
|
||||
"@metaDataSavedFor": {
|
||||
"description": "元数据已保存",
|
||||
"placeholders": {
|
||||
"item": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraryPageSizeTitle": "库页面大小",
|
||||
"libraryPageSizeDesc": "设置每次加载的数量。0 禁用分页。",
|
||||
"fetchingLibrary": "正在获取库项目",
|
||||
"libraryFetchNoItemsFound": "未找到项目,请尝试不同的设置。",
|
||||
"noSuggestionsFound": "未找到建议",
|
||||
"viewPhotos": "查看照片",
|
||||
"random": "随机",
|
||||
"tag": "{count, plural, one{标签} other{标签}}",
|
||||
"@tag": {
|
||||
"description": "标签",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": "已保存",
|
||||
"discovered": "已发现",
|
||||
"noServersFound": "未找到新服务器",
|
||||
"about": "关于",
|
||||
"openParent": "打开上级文件夹",
|
||||
"mouseDragSupport": "使用鼠标拖动",
|
||||
"controls": "控制"
|
||||
}
|
||||
1
lib/l10n/l10n_errors.txt
Normal file
1
lib/l10n/l10n_errors.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
339
lib/main.dart
Normal file
339
lib/main.dart
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'package:fladder/models/account_model.dart';
|
||||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/routes/app_routes.dart';
|
||||
import 'package:fladder/routes/build_routes/route_builder.dart';
|
||||
import 'package:fladder/screens/login/lock_screen.dart';
|
||||
import 'package:fladder/theme.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/application_info.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:fladder/util/themes_data.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
bool get _isDesktop {
|
||||
if (kIsWeb) return false;
|
||||
return [
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
].contains(defaultTargetPlatform);
|
||||
}
|
||||
|
||||
class CustomCacheManager {
|
||||
static const key = 'customCacheKey';
|
||||
static CacheManager instance = CacheManager(
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: const Duration(days: 3),
|
||||
maxNrOfCacheObjects: 500,
|
||||
repo: JsonCacheInfoRepository(databaseName: key),
|
||||
fileService: HttpFileService(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void main() async {
|
||||
if (kIsWeb) {
|
||||
usePathUrlStrategy();
|
||||
html.document.onContextMenu.listen((event) => event.preventDefault());
|
||||
GoRouter.optionURLReflectsImperativeAPIs = true;
|
||||
}
|
||||
_setupLogging();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
MediaKit.ensureInitialized();
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
Directory isarPath = Directory("");
|
||||
Directory applicationDirectory = Directory("");
|
||||
|
||||
if (!kIsWeb) {
|
||||
applicationDirectory = await getApplicationDocumentsDirectory();
|
||||
isarPath = Directory(path.joinAll([applicationDirectory.path, 'Fladder', 'Database']));
|
||||
await isarPath.create(recursive: true);
|
||||
}
|
||||
|
||||
if (_isDesktop) {
|
||||
await WindowManager.instance.ensureInitialized();
|
||||
}
|
||||
|
||||
final applicationInfo = ApplicationInfo(
|
||||
name: kIsWeb ? "${packageInfo.appName.capitalize()} Web" : packageInfo.appName.capitalize(),
|
||||
version: "${packageInfo.version}(${packageInfo.buildNumber})",
|
||||
os: defaultTargetPlatform.name.capitalize(),
|
||||
);
|
||||
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
|
||||
applicationInfoProvider.overrideWith((ref) => applicationInfo),
|
||||
syncProvider.overrideWith((ref) => SyncNotifier(
|
||||
ref,
|
||||
!kIsWeb
|
||||
? Isar.open(
|
||||
schemas: [ISyncedItemSchema],
|
||||
directory: isarPath.path,
|
||||
)
|
||||
: null,
|
||||
applicationDirectory,
|
||||
))
|
||||
],
|
||||
child: AdaptiveLayoutBuilder(
|
||||
fallBack: LayoutState.phone,
|
||||
layoutPoints: [
|
||||
LayoutPoints(start: 0, end: 599, type: LayoutState.phone),
|
||||
LayoutPoints(start: 600, end: 1919, type: LayoutState.tablet),
|
||||
LayoutPoints(start: 1920, end: 3180, type: LayoutState.desktop),
|
||||
],
|
||||
child: const Main(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _setupLogging() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((rec) {
|
||||
if (kDebugMode) {
|
||||
print('${rec.level.name}: ${rec.time}: ${rec.message}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Main extends ConsumerStatefulWidget with WindowListener {
|
||||
const Main({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _MainState();
|
||||
}
|
||||
|
||||
class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBindingObserver {
|
||||
DateTime dateTime = DateTime.now();
|
||||
bool hidden = false;
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||
if (ref.read(lockScreenActiveProvider) || ref.read(userProvider) == null) {
|
||||
dateTime = DateTime.now();
|
||||
return;
|
||||
}
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
enableTimeOut();
|
||||
break;
|
||||
case AppLifecycleState.hidden:
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
hidden = true;
|
||||
dateTime = DateTime.now();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void enableTimeOut() async {
|
||||
final timeOut = ref.read(clientSettingsProvider).timeOut;
|
||||
|
||||
if (timeOut == null) return;
|
||||
|
||||
final difference = DateTime.now().difference(dateTime).abs();
|
||||
|
||||
if (difference > timeOut && ref.read(userProvider)?.authMethod != Authentication.autoLogin && hidden) {
|
||||
hidden = false;
|
||||
dateTime = DateTime.now();
|
||||
|
||||
// Stop playback if the user was still watching a video
|
||||
await ref.read(videoPlayerProvider).pause();
|
||||
|
||||
if (context.mounted) {
|
||||
AdaptiveLayout.of(context).router.push(LockScreenRoute().route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
windowManager.addListener(this);
|
||||
_init();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
windowManager.removeListener(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowClose() {
|
||||
ref.read(videoPlayerProvider).stop();
|
||||
super.onWindowClose();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowResize() async {
|
||||
final size = await windowManager.getSize();
|
||||
ref.read(clientSettingsProvider.notifier).setWindowSize(size);
|
||||
super.onWindowResize();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowResized() async {
|
||||
final size = await windowManager.getSize();
|
||||
ref.read(clientSettingsProvider.notifier).setWindowSize(size);
|
||||
super.onWindowResized();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMove() async {
|
||||
final position = await windowManager.getPosition();
|
||||
ref.read(clientSettingsProvider.notifier).setWindowPosition(position);
|
||||
super.onWindowMove();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMoved() async {
|
||||
final position = await windowManager.getPosition();
|
||||
ref.read(clientSettingsProvider.notifier).setWindowPosition(position);
|
||||
super.onWindowMoved();
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
ref.read(sharedUtilityProvider).loadSettings();
|
||||
|
||||
final clientSettings = ref.read(clientSettingsProvider);
|
||||
|
||||
if (_isDesktop) {
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(clientSettings.size.x, clientSettings.size.y),
|
||||
center: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
skipTaskbar: false,
|
||||
titleBarStyle: TitleBarStyle.hidden,
|
||||
title: packageInfo.appName.capitalize());
|
||||
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
});
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeMode = ref.watch(clientSettingsProvider.select((value) => value.themeMode));
|
||||
final themeColor = ref.watch(clientSettingsProvider.select((value) => value.themeColor));
|
||||
final amoledBlack = ref.watch(clientSettingsProvider.select((value) => value.amoledBlack));
|
||||
final mouseDrag = ref.watch(clientSettingsProvider.select((value) => value.mouseDragSupport));
|
||||
final language = ref.watch(clientSettingsProvider
|
||||
.select((value) => value.selectedLocale ?? WidgetsBinding.instance.platformDispatcher.locale));
|
||||
final scrollBehaviour = const MaterialScrollBehavior();
|
||||
return Shortcuts(
|
||||
shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
|
||||
},
|
||||
child: DynamicColorBuilder(builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||
final lightTheme = themeColor == null
|
||||
? FladderTheme.theme(lightDynamic ?? FladderTheme.defaultScheme(Brightness.light))
|
||||
: FladderTheme.theme(themeColor.schemeLight);
|
||||
final darkTheme = (themeColor == null
|
||||
? FladderTheme.theme(darkDynamic ?? FladderTheme.defaultScheme(Brightness.dark))
|
||||
: FladderTheme.theme(themeColor.schemeDark));
|
||||
final amoledOverwrite = amoledBlack ? Colors.black : null;
|
||||
return ThemesData(
|
||||
light: lightTheme,
|
||||
dark: darkTheme,
|
||||
child: MaterialApp.router(
|
||||
onGenerateTitle: (context) => ref.watch(currentTitleProvider),
|
||||
theme: lightTheme,
|
||||
scrollBehavior: scrollBehaviour.copyWith(
|
||||
dragDevices: {
|
||||
...scrollBehaviour.dragDevices,
|
||||
mouseDrag ? PointerDeviceKind.mouse : null,
|
||||
}.whereNotNull().toSet(),
|
||||
),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
builder: (context, child) => Localizations.override(
|
||||
context: context,
|
||||
locale: AppLocalizations.supportedLocales.firstWhere(
|
||||
(element) => element.languageCode == language.languageCode,
|
||||
orElse: () => Locale('en', "GB"),
|
||||
),
|
||||
child: ScaffoldMessenger(child: child ?? Container()),
|
||||
),
|
||||
darkTheme: darkTheme.copyWith(
|
||||
scaffoldBackgroundColor: amoledOverwrite,
|
||||
cardColor: amoledOverwrite,
|
||||
canvasColor: amoledOverwrite,
|
||||
colorScheme: darkTheme.colorScheme.copyWith(
|
||||
surface: amoledOverwrite,
|
||||
surfaceContainerHighest: amoledOverwrite,
|
||||
),
|
||||
),
|
||||
themeMode: themeMode,
|
||||
routerConfig: AdaptiveLayout.of(context).router,
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
List<RouteBase> getRoutes(LayoutState state) {
|
||||
switch (state) {
|
||||
case LayoutState.phone:
|
||||
return AppRoutes.linearRoutes;
|
||||
case LayoutState.tablet:
|
||||
case LayoutState.desktop:
|
||||
return AppRoutes.nestedRoutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final currentTitleProvider = StateProvider<String>((ref) {
|
||||
return "Fladder";
|
||||
});
|
||||
107
lib/models/account_model.dart
Normal file
107
lib/models/account_model.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first, invalid_annotation_target
|
||||
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/credentials_model.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'account_model.freezed.dart';
|
||||
part 'account_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class AccountModel with _$AccountModel {
|
||||
const AccountModel._();
|
||||
|
||||
const factory AccountModel({
|
||||
required String name,
|
||||
required String id,
|
||||
required String avatar,
|
||||
required DateTime lastUsed,
|
||||
@Default(Authentication.autoLogin) Authentication authMethod,
|
||||
@Default("") String localPin,
|
||||
required CredentialsModel credentials,
|
||||
@Default([]) List<String> latestItemsExcludes,
|
||||
@Default([]) List<String> searchQueryHistory,
|
||||
@Default(false) bool quickConnectState,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
|
||||
}) = _AccountModel;
|
||||
|
||||
factory AccountModel.fromJson(Map<String, dynamic> json) => _$AccountModelFromJson(json);
|
||||
|
||||
String get server {
|
||||
return credentials.server;
|
||||
}
|
||||
|
||||
bool get canDownload {
|
||||
return (policy?.enableContentDownloading ?? false) && !kIsWeb;
|
||||
}
|
||||
|
||||
//Check if it's the same account on the same server
|
||||
bool sameIdentity(AccountModel other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other.id == id && other.credentials.serverId == credentials.serverId;
|
||||
}
|
||||
}
|
||||
|
||||
enum Authentication {
|
||||
autoLogin(0),
|
||||
biometrics(1),
|
||||
passcode(2),
|
||||
none(3);
|
||||
|
||||
const Authentication(this.value);
|
||||
final int value;
|
||||
|
||||
bool available(BuildContext context) {
|
||||
switch (this) {
|
||||
case Authentication.none:
|
||||
case Authentication.autoLogin:
|
||||
case Authentication.passcode:
|
||||
return true;
|
||||
case Authentication.biometrics:
|
||||
return !AdaptiveLayout.of(context).isDesktop;
|
||||
}
|
||||
}
|
||||
|
||||
String name(BuildContext context) {
|
||||
switch (this) {
|
||||
case Authentication.none:
|
||||
return context.localized.none;
|
||||
case Authentication.autoLogin:
|
||||
return context.localized.appLockAutoLogin;
|
||||
case Authentication.biometrics:
|
||||
return context.localized.appLockBiometrics;
|
||||
case Authentication.passcode:
|
||||
return context.localized.appLockPasscode;
|
||||
}
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
switch (this) {
|
||||
case Authentication.none:
|
||||
return IconsaxBold.arrow_bottom;
|
||||
case Authentication.autoLogin:
|
||||
return IconsaxOutline.login_1;
|
||||
case Authentication.biometrics:
|
||||
return IconsaxOutline.finger_scan;
|
||||
case Authentication.passcode:
|
||||
return IconsaxOutline.password_check;
|
||||
}
|
||||
}
|
||||
|
||||
static Authentication fromMap(int value) {
|
||||
return Authentication.values[value];
|
||||
}
|
||||
|
||||
int toMap() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
450
lib/models/account_model.freezed.dart
Normal file
450
lib/models/account_model.freezed.dart
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'account_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
AccountModel _$AccountModelFromJson(Map<String, dynamic> json) {
|
||||
return _AccountModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AccountModel {
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
String get avatar => throw _privateConstructorUsedError;
|
||||
DateTime get lastUsed => throw _privateConstructorUsedError;
|
||||
Authentication get authMethod => throw _privateConstructorUsedError;
|
||||
String get localPin => throw _privateConstructorUsedError;
|
||||
CredentialsModel get credentials => throw _privateConstructorUsedError;
|
||||
List<String> get latestItemsExcludes => throw _privateConstructorUsedError;
|
||||
List<String> get searchQueryHistory => throw _privateConstructorUsedError;
|
||||
bool get quickConnectState => throw _privateConstructorUsedError;
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
UserPolicy? get policy => throw _privateConstructorUsedError;
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? get serverConfiguration =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$AccountModelCopyWith<AccountModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $AccountModelCopyWith<$Res> {
|
||||
factory $AccountModelCopyWith(
|
||||
AccountModel value, $Res Function(AccountModel) then) =
|
||||
_$AccountModelCopyWithImpl<$Res, AccountModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String name,
|
||||
String id,
|
||||
String avatar,
|
||||
DateTime lastUsed,
|
||||
Authentication authMethod,
|
||||
String localPin,
|
||||
CredentialsModel credentials,
|
||||
List<String> latestItemsExcludes,
|
||||
List<String> searchQueryHistory,
|
||||
bool quickConnectState,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? serverConfiguration});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel>
|
||||
implements $AccountModelCopyWith<$Res> {
|
||||
_$AccountModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? id = null,
|
||||
Object? avatar = null,
|
||||
Object? lastUsed = null,
|
||||
Object? authMethod = null,
|
||||
Object? localPin = null,
|
||||
Object? credentials = null,
|
||||
Object? latestItemsExcludes = null,
|
||||
Object? searchQueryHistory = null,
|
||||
Object? quickConnectState = null,
|
||||
Object? policy = freezed,
|
||||
Object? serverConfiguration = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
avatar: null == avatar
|
||||
? _value.avatar
|
||||
: avatar // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
lastUsed: null == lastUsed
|
||||
? _value.lastUsed
|
||||
: lastUsed // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
authMethod: null == authMethod
|
||||
? _value.authMethod
|
||||
: authMethod // ignore: cast_nullable_to_non_nullable
|
||||
as Authentication,
|
||||
localPin: null == localPin
|
||||
? _value.localPin
|
||||
: localPin // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
credentials: null == credentials
|
||||
? _value.credentials
|
||||
: credentials // ignore: cast_nullable_to_non_nullable
|
||||
as CredentialsModel,
|
||||
latestItemsExcludes: null == latestItemsExcludes
|
||||
? _value.latestItemsExcludes
|
||||
: latestItemsExcludes // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
searchQueryHistory: null == searchQueryHistory
|
||||
? _value.searchQueryHistory
|
||||
: searchQueryHistory // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
quickConnectState: null == quickConnectState
|
||||
? _value.quickConnectState
|
||||
: quickConnectState // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
policy: freezed == policy
|
||||
? _value.policy
|
||||
: policy // ignore: cast_nullable_to_non_nullable
|
||||
as UserPolicy?,
|
||||
serverConfiguration: freezed == serverConfiguration
|
||||
? _value.serverConfiguration
|
||||
: serverConfiguration // ignore: cast_nullable_to_non_nullable
|
||||
as ServerConfiguration?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$AccountModelImplCopyWith<$Res>
|
||||
implements $AccountModelCopyWith<$Res> {
|
||||
factory _$$AccountModelImplCopyWith(
|
||||
_$AccountModelImpl value, $Res Function(_$AccountModelImpl) then) =
|
||||
__$$AccountModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String name,
|
||||
String id,
|
||||
String avatar,
|
||||
DateTime lastUsed,
|
||||
Authentication authMethod,
|
||||
String localPin,
|
||||
CredentialsModel credentials,
|
||||
List<String> latestItemsExcludes,
|
||||
List<String> searchQueryHistory,
|
||||
bool quickConnectState,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? serverConfiguration});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$AccountModelImplCopyWithImpl<$Res>
|
||||
extends _$AccountModelCopyWithImpl<$Res, _$AccountModelImpl>
|
||||
implements _$$AccountModelImplCopyWith<$Res> {
|
||||
__$$AccountModelImplCopyWithImpl(
|
||||
_$AccountModelImpl _value, $Res Function(_$AccountModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? id = null,
|
||||
Object? avatar = null,
|
||||
Object? lastUsed = null,
|
||||
Object? authMethod = null,
|
||||
Object? localPin = null,
|
||||
Object? credentials = null,
|
||||
Object? latestItemsExcludes = null,
|
||||
Object? searchQueryHistory = null,
|
||||
Object? quickConnectState = null,
|
||||
Object? policy = freezed,
|
||||
Object? serverConfiguration = freezed,
|
||||
}) {
|
||||
return _then(_$AccountModelImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
avatar: null == avatar
|
||||
? _value.avatar
|
||||
: avatar // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
lastUsed: null == lastUsed
|
||||
? _value.lastUsed
|
||||
: lastUsed // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
authMethod: null == authMethod
|
||||
? _value.authMethod
|
||||
: authMethod // ignore: cast_nullable_to_non_nullable
|
||||
as Authentication,
|
||||
localPin: null == localPin
|
||||
? _value.localPin
|
||||
: localPin // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
credentials: null == credentials
|
||||
? _value.credentials
|
||||
: credentials // ignore: cast_nullable_to_non_nullable
|
||||
as CredentialsModel,
|
||||
latestItemsExcludes: null == latestItemsExcludes
|
||||
? _value._latestItemsExcludes
|
||||
: latestItemsExcludes // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
searchQueryHistory: null == searchQueryHistory
|
||||
? _value._searchQueryHistory
|
||||
: searchQueryHistory // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
quickConnectState: null == quickConnectState
|
||||
? _value.quickConnectState
|
||||
: quickConnectState // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
policy: freezed == policy
|
||||
? _value.policy
|
||||
: policy // ignore: cast_nullable_to_non_nullable
|
||||
as UserPolicy?,
|
||||
serverConfiguration: freezed == serverConfiguration
|
||||
? _value.serverConfiguration
|
||||
: serverConfiguration // ignore: cast_nullable_to_non_nullable
|
||||
as ServerConfiguration?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
|
||||
const _$AccountModelImpl(
|
||||
{required this.name,
|
||||
required this.id,
|
||||
required this.avatar,
|
||||
required this.lastUsed,
|
||||
this.authMethod = Authentication.autoLogin,
|
||||
this.localPin = "",
|
||||
required this.credentials,
|
||||
final List<String> latestItemsExcludes = const [],
|
||||
final List<String> searchQueryHistory = const [],
|
||||
this.quickConnectState = false,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) this.policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
this.serverConfiguration})
|
||||
: _latestItemsExcludes = latestItemsExcludes,
|
||||
_searchQueryHistory = searchQueryHistory,
|
||||
super._();
|
||||
|
||||
factory _$AccountModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$AccountModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String avatar;
|
||||
@override
|
||||
final DateTime lastUsed;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Authentication authMethod;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String localPin;
|
||||
@override
|
||||
final CredentialsModel credentials;
|
||||
final List<String> _latestItemsExcludes;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get latestItemsExcludes {
|
||||
if (_latestItemsExcludes is EqualUnmodifiableListView)
|
||||
return _latestItemsExcludes;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_latestItemsExcludes);
|
||||
}
|
||||
|
||||
final List<String> _searchQueryHistory;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get searchQueryHistory {
|
||||
if (_searchQueryHistory is EqualUnmodifiableListView)
|
||||
return _searchQueryHistory;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_searchQueryHistory);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool quickConnectState;
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final UserPolicy? policy;
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final ServerConfiguration? serverConfiguration;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, policy: $policy, serverConfiguration: $serverConfiguration)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'AccountModel'))
|
||||
..add(DiagnosticsProperty('name', name))
|
||||
..add(DiagnosticsProperty('id', id))
|
||||
..add(DiagnosticsProperty('avatar', avatar))
|
||||
..add(DiagnosticsProperty('lastUsed', lastUsed))
|
||||
..add(DiagnosticsProperty('authMethod', authMethod))
|
||||
..add(DiagnosticsProperty('localPin', localPin))
|
||||
..add(DiagnosticsProperty('credentials', credentials))
|
||||
..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes))
|
||||
..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory))
|
||||
..add(DiagnosticsProperty('quickConnectState', quickConnectState))
|
||||
..add(DiagnosticsProperty('policy', policy))
|
||||
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$AccountModelImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.avatar, avatar) || other.avatar == avatar) &&
|
||||
(identical(other.lastUsed, lastUsed) ||
|
||||
other.lastUsed == lastUsed) &&
|
||||
(identical(other.authMethod, authMethod) ||
|
||||
other.authMethod == authMethod) &&
|
||||
(identical(other.localPin, localPin) ||
|
||||
other.localPin == localPin) &&
|
||||
(identical(other.credentials, credentials) ||
|
||||
other.credentials == credentials) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._latestItemsExcludes, _latestItemsExcludes) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._searchQueryHistory, _searchQueryHistory) &&
|
||||
(identical(other.quickConnectState, quickConnectState) ||
|
||||
other.quickConnectState == quickConnectState) &&
|
||||
(identical(other.policy, policy) || other.policy == policy) &&
|
||||
(identical(other.serverConfiguration, serverConfiguration) ||
|
||||
other.serverConfiguration == serverConfiguration));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
name,
|
||||
id,
|
||||
avatar,
|
||||
lastUsed,
|
||||
authMethod,
|
||||
localPin,
|
||||
credentials,
|
||||
const DeepCollectionEquality().hash(_latestItemsExcludes),
|
||||
const DeepCollectionEquality().hash(_searchQueryHistory),
|
||||
quickConnectState,
|
||||
policy,
|
||||
serverConfiguration);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$AccountModelImplCopyWith<_$AccountModelImpl> get copyWith =>
|
||||
__$$AccountModelImplCopyWithImpl<_$AccountModelImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$AccountModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _AccountModel extends AccountModel {
|
||||
const factory _AccountModel(
|
||||
{required final String name,
|
||||
required final String id,
|
||||
required final String avatar,
|
||||
required final DateTime lastUsed,
|
||||
final Authentication authMethod,
|
||||
final String localPin,
|
||||
required final CredentialsModel credentials,
|
||||
final List<String> latestItemsExcludes,
|
||||
final List<String> searchQueryHistory,
|
||||
final bool quickConnectState,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final UserPolicy? policy,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final ServerConfiguration? serverConfiguration}) = _$AccountModelImpl;
|
||||
const _AccountModel._() : super._();
|
||||
|
||||
factory _AccountModel.fromJson(Map<String, dynamic> json) =
|
||||
_$AccountModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get name;
|
||||
@override
|
||||
String get id;
|
||||
@override
|
||||
String get avatar;
|
||||
@override
|
||||
DateTime get lastUsed;
|
||||
@override
|
||||
Authentication get authMethod;
|
||||
@override
|
||||
String get localPin;
|
||||
@override
|
||||
CredentialsModel get credentials;
|
||||
@override
|
||||
List<String> get latestItemsExcludes;
|
||||
@override
|
||||
List<String> get searchQueryHistory;
|
||||
@override
|
||||
bool get quickConnectState;
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
UserPolicy? get policy;
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ServerConfiguration? get serverConfiguration;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$AccountModelImplCopyWith<_$AccountModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
50
lib/models/account_model.g.dart
Normal file
50
lib/models/account_model.g.dart
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'account_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$AccountModelImpl _$$AccountModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$AccountModelImpl(
|
||||
name: json['name'] as String,
|
||||
id: json['id'] as String,
|
||||
avatar: json['avatar'] as String,
|
||||
lastUsed: DateTime.parse(json['lastUsed'] as String),
|
||||
authMethod:
|
||||
$enumDecodeNullable(_$AuthenticationEnumMap, json['authMethod']) ??
|
||||
Authentication.autoLogin,
|
||||
localPin: json['localPin'] as String? ?? "",
|
||||
credentials: CredentialsModel.fromJson(json['credentials'] as String),
|
||||
latestItemsExcludes: (json['latestItemsExcludes'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
searchQueryHistory: (json['searchQueryHistory'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
quickConnectState: json['quickConnectState'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AccountModelImplToJson(_$AccountModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'id': instance.id,
|
||||
'avatar': instance.avatar,
|
||||
'lastUsed': instance.lastUsed.toIso8601String(),
|
||||
'authMethod': _$AuthenticationEnumMap[instance.authMethod]!,
|
||||
'localPin': instance.localPin,
|
||||
'credentials': instance.credentials,
|
||||
'latestItemsExcludes': instance.latestItemsExcludes,
|
||||
'searchQueryHistory': instance.searchQueryHistory,
|
||||
'quickConnectState': instance.quickConnectState,
|
||||
};
|
||||
|
||||
const _$AuthenticationEnumMap = {
|
||||
Authentication.autoLogin: 'autoLogin',
|
||||
Authentication.biometrics: 'biometrics',
|
||||
Authentication.passcode: 'passcode',
|
||||
Authentication.none: 'none',
|
||||
};
|
||||
72
lib/models/book_model.dart
Normal file
72
lib/models/book_model.dart
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class BookModel extends ItemBaseModel {
|
||||
final String? parentName;
|
||||
final List<ItemBaseModel> items;
|
||||
BookModel(
|
||||
{this.items = const [],
|
||||
required this.parentName,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
super.jellyType,
|
||||
required super.canDownload,
|
||||
required super.canDelete});
|
||||
|
||||
@override
|
||||
String? get subText => parentName;
|
||||
|
||||
@override
|
||||
String? detailedName(BuildContext context) => "$name ${parentName != null ? "\n ($parentName)" : ""} ";
|
||||
|
||||
@override
|
||||
ItemBaseModel get parentBaseModel => copyWith(id: parentId);
|
||||
|
||||
@override
|
||||
bool get playAble => true;
|
||||
|
||||
int get currentPage => userData.playbackPositionTicks ~/ 10000;
|
||||
|
||||
@override
|
||||
String playText(BuildContext context) => context.localized.read(name);
|
||||
|
||||
@override
|
||||
double get progress => userData.progress != 0 ? 100 : 0;
|
||||
|
||||
@override
|
||||
String playButtonLabel(BuildContext context) => progress != 0
|
||||
? context.localized.continuePage(currentPage)
|
||||
: userData.played == true
|
||||
? "${context.localized.restart} $name"
|
||||
: context.localized.read(name);
|
||||
|
||||
factory BookModel.fromBaseDto(BaseItemDto item, Ref ref) {
|
||||
return BookModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
parentName: item.seriesName ?? item.seasonName,
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
);
|
||||
}
|
||||
}
|
||||
47
lib/models/boxset_model.dart
Normal file
47
lib/models/boxset_model.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'boxset_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class BoxSetModel extends ItemBaseModel with BoxSetModelMappable {
|
||||
final List<ItemBaseModel> items;
|
||||
const BoxSetModel({
|
||||
this.items = const [],
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.canDelete,
|
||||
required super.canDownload,
|
||||
super.jellyType,
|
||||
});
|
||||
|
||||
factory BoxSetModel.fromBaseDto(BaseItemDto item, Ref ref) {
|
||||
return BoxSetModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
jellyType: item.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
242
lib/models/boxset_model.mapper.dart
Normal file
242
lib/models/boxset_model.mapper.dart
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'boxset_model.dart';
|
||||
|
||||
class BoxSetModelMapper extends SubClassMapperBase<BoxSetModel> {
|
||||
BoxSetModelMapper._();
|
||||
|
||||
static BoxSetModelMapper? _instance;
|
||||
static BoxSetModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = BoxSetModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'BoxSetModel';
|
||||
|
||||
static List<ItemBaseModel> _$items(BoxSetModel v) => v.items;
|
||||
static const Field<BoxSetModel, List<ItemBaseModel>> _f$items =
|
||||
Field('items', _$items, opt: true, def: const []);
|
||||
static String _$name(BoxSetModel v) => v.name;
|
||||
static const Field<BoxSetModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(BoxSetModel v) => v.id;
|
||||
static const Field<BoxSetModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(BoxSetModel v) => v.overview;
|
||||
static const Field<BoxSetModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(BoxSetModel v) => v.parentId;
|
||||
static const Field<BoxSetModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(BoxSetModel v) => v.playlistId;
|
||||
static const Field<BoxSetModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(BoxSetModel v) => v.images;
|
||||
static const Field<BoxSetModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(BoxSetModel v) => v.childCount;
|
||||
static const Field<BoxSetModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(BoxSetModel v) => v.primaryRatio;
|
||||
static const Field<BoxSetModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(BoxSetModel v) => v.userData;
|
||||
static const Field<BoxSetModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDelete(BoxSetModel v) => v.canDelete;
|
||||
static const Field<BoxSetModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete);
|
||||
static bool? _$canDownload(BoxSetModel v) => v.canDownload;
|
||||
static const Field<BoxSetModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload);
|
||||
static BaseItemKind? _$jellyType(BoxSetModel v) => v.jellyType;
|
||||
static const Field<BoxSetModel, BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<BoxSetModel> fields = const {
|
||||
#items: _f$items,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDelete: _f$canDelete,
|
||||
#canDownload: _f$canDownload,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'BoxSetModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static BoxSetModel _instantiate(DecodingData data) {
|
||||
return BoxSetModel(
|
||||
items: data.dec(_f$items),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static BoxSetModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<BoxSetModel>(map);
|
||||
}
|
||||
|
||||
static BoxSetModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<BoxSetModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin BoxSetModelMappable {
|
||||
String toJson() {
|
||||
return BoxSetModelMapper.ensureInitialized()
|
||||
.encodeJson<BoxSetModel>(this as BoxSetModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return BoxSetModelMapper.ensureInitialized()
|
||||
.encodeMap<BoxSetModel>(this as BoxSetModel);
|
||||
}
|
||||
|
||||
BoxSetModelCopyWith<BoxSetModel, BoxSetModel, BoxSetModel> get copyWith =>
|
||||
_BoxSetModelCopyWithImpl(this as BoxSetModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return BoxSetModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as BoxSetModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension BoxSetModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, BoxSetModel, $Out> {
|
||||
BoxSetModelCopyWith<$R, BoxSetModel, $Out> get $asBoxSetModel =>
|
||||
$base.as((v, t, t2) => _BoxSetModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class BoxSetModelCopyWith<$R, $In extends BoxSetModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get items;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{List<ItemBaseModel>? items,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDelete,
|
||||
bool? canDownload,
|
||||
BaseItemKind? jellyType});
|
||||
BoxSetModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _BoxSetModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, BoxSetModel, $Out>
|
||||
implements BoxSetModelCopyWith<$R, BoxSetModel, $Out> {
|
||||
_BoxSetModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<BoxSetModel> $mapper =
|
||||
BoxSetModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get items => ListCopyWith(
|
||||
$value.items, (v, t) => v.copyWith.$chain(t), (v) => call(items: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{List<ItemBaseModel>? items,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDelete = $none,
|
||||
Object? canDownload = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (items != null) #items: items,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
BoxSetModel $make(CopyWithData data) => BoxSetModel(
|
||||
items: data.get(#items, or: $value.items),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
BoxSetModelCopyWith<$R2, BoxSetModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_BoxSetModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
49
lib/models/collection_types.dart
Normal file
49
lib/models/collection_types.dart
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension CollectionTypeExtension on CollectionType {
|
||||
IconData get iconOutlined {
|
||||
return getIconType(true);
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
return getIconType(false);
|
||||
}
|
||||
|
||||
Set<FladderItemType> get itemKinds {
|
||||
switch (this) {
|
||||
case CollectionType.movies:
|
||||
return {FladderItemType.movie};
|
||||
case CollectionType.tvshows:
|
||||
return {FladderItemType.series};
|
||||
case CollectionType.homevideos:
|
||||
return {FladderItemType.photoalbum, FladderItemType.folder, FladderItemType.photo, FladderItemType.video};
|
||||
case CollectionType.boxsets:
|
||||
case CollectionType.folders:
|
||||
case CollectionType.books:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
IconData getIconType(bool outlined) {
|
||||
switch (this) {
|
||||
case CollectionType.movies:
|
||||
return outlined ? IconsaxOutline.video_horizontal : IconsaxBold.video_horizontal;
|
||||
case CollectionType.tvshows:
|
||||
return outlined ? IconsaxOutline.video_vertical : IconsaxBold.video_vertical;
|
||||
case CollectionType.boxsets:
|
||||
case CollectionType.folders:
|
||||
return outlined ? IconsaxOutline.folder : IconsaxBold.folder;
|
||||
case CollectionType.homevideos:
|
||||
return outlined ? IconsaxOutline.gallery : IconsaxBold.gallery;
|
||||
case CollectionType.books:
|
||||
return outlined ? IconsaxOutline.book : Icons.book_rounded;
|
||||
default:
|
||||
return IconsaxOutline.info_circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
lib/models/credentials_model.dart
Normal file
81
lib/models/credentials_model.dart
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/util/application_info.dart';
|
||||
import 'package:xid/xid.dart';
|
||||
|
||||
class CredentialsModel {
|
||||
final String token;
|
||||
final String server;
|
||||
final String serverName;
|
||||
final String serverId;
|
||||
final String deviceId;
|
||||
CredentialsModel({
|
||||
this.token = "",
|
||||
this.server = "",
|
||||
this.serverName = "",
|
||||
this.serverId = "",
|
||||
required this.deviceId,
|
||||
});
|
||||
|
||||
factory CredentialsModel.createNewCredentials() {
|
||||
return CredentialsModel(deviceId: Xid().toString());
|
||||
}
|
||||
|
||||
Map<String, String> header(Ref ref) {
|
||||
final application = ref.read(applicationInfoProvider);
|
||||
final headers = {
|
||||
'content-type': 'application/json',
|
||||
'x-emby-token': token,
|
||||
'x-emby-authorization':
|
||||
'MediaBrowser Client="${application.name}", Device="${application.os}", DeviceId="$deviceId", Version="${application.version}"'
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
CredentialsModel copyWith({
|
||||
String? token,
|
||||
String? server,
|
||||
String? serverName,
|
||||
String? serverId,
|
||||
String? deviceId,
|
||||
}) {
|
||||
return CredentialsModel(
|
||||
token: token ?? this.token,
|
||||
server: server ?? this.server,
|
||||
serverName: serverName ?? this.serverName,
|
||||
serverId: serverId ?? this.serverId,
|
||||
deviceId: deviceId ?? this.deviceId,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'token': token,
|
||||
'server': server,
|
||||
'serverName': serverName,
|
||||
'serverId': serverId,
|
||||
'deviceId': deviceId,
|
||||
};
|
||||
}
|
||||
|
||||
factory CredentialsModel.fromMap(Map<String, dynamic> map) {
|
||||
return CredentialsModel(
|
||||
token: map['token'] ?? '',
|
||||
server: map['server'] ?? '',
|
||||
serverName: map['serverName'] ?? '',
|
||||
serverId: map['serverId'] ?? '',
|
||||
deviceId: map['deviceId'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory CredentialsModel.fromJson(String source) => CredentialsModel.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CredentialsModel(token: $token, server: $server, serverName: $serverName, serverId: $serverId, header: $header)';
|
||||
}
|
||||
}
|
||||
26
lib/models/favourites_model.dart
Normal file
26
lib/models/favourites_model.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
|
||||
class FavouritesModel {
|
||||
final bool loading;
|
||||
final Map<FladderItemType, List<ItemBaseModel>> favourites;
|
||||
final List<ItemBaseModel> people;
|
||||
|
||||
FavouritesModel({
|
||||
this.loading = false,
|
||||
this.favourites = const {},
|
||||
this.people = const [],
|
||||
});
|
||||
|
||||
FavouritesModel copyWith({
|
||||
bool? loading,
|
||||
String? searchQuery,
|
||||
Map<FladderItemType, List<ItemBaseModel>>? favourites,
|
||||
List<ItemBaseModel>? people,
|
||||
}) {
|
||||
return FavouritesModel(
|
||||
loading: loading ?? this.loading,
|
||||
favourites: favourites ?? this.favourites,
|
||||
people: people ?? this.people,
|
||||
);
|
||||
}
|
||||
}
|
||||
34
lib/models/home_model.dart
Normal file
34
lib/models/home_model.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
|
||||
class HomeModel {
|
||||
final bool loading;
|
||||
final List<ItemBaseModel> resumeVideo;
|
||||
final List<ItemBaseModel> resumeAudio;
|
||||
final List<ItemBaseModel> resumeBooks;
|
||||
final List<ItemBaseModel> nextUp;
|
||||
HomeModel({
|
||||
this.loading = false,
|
||||
this.resumeVideo = const [],
|
||||
this.resumeAudio = const [],
|
||||
this.resumeBooks = const [],
|
||||
this.nextUp = const [],
|
||||
});
|
||||
|
||||
HomeModel copyWith({
|
||||
bool? loading,
|
||||
List<ItemBaseModel>? resumeVideo,
|
||||
List<ItemBaseModel>? resumeAudio,
|
||||
List<ItemBaseModel>? resumeBooks,
|
||||
List<ItemBaseModel>? nextUp,
|
||||
List<ItemBaseModel>? nextUpBooks,
|
||||
}) {
|
||||
return HomeModel(
|
||||
loading: loading ?? this.loading,
|
||||
resumeVideo: resumeVideo ?? this.resumeVideo,
|
||||
resumeAudio: resumeAudio ?? this.resumeAudio,
|
||||
resumeBooks: resumeBooks ?? this.resumeBooks,
|
||||
nextUp: nextUp ?? this.nextUp,
|
||||
);
|
||||
}
|
||||
}
|
||||
107
lib/models/information_model.dart
Normal file
107
lib/models/information_model.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/util/size_formatting.dart';
|
||||
|
||||
class InformationModel {
|
||||
final Map<String, dynamic> baseInformation;
|
||||
final List<Map<String, dynamic>> videoStreams;
|
||||
final List<Map<String, dynamic>> audioStreams;
|
||||
final List<Map<String, dynamic>> subStreams;
|
||||
InformationModel({
|
||||
required this.baseInformation,
|
||||
required this.videoStreams,
|
||||
required this.audioStreams,
|
||||
required this.subStreams,
|
||||
});
|
||||
|
||||
static InformationModel? fromResponse(BaseItemDto? item) {
|
||||
if (item == null) return null;
|
||||
var videoStreams = item.mediaStreams?.where((element) => element.type == MediaStreamType.video).toList() ?? [];
|
||||
var audioStreams = item.mediaStreams?.where((element) => element.type == MediaStreamType.audio).toList() ?? [];
|
||||
var subStreams = item.mediaStreams?.where((element) => element.type == MediaStreamType.subtitle).toList() ?? [];
|
||||
return InformationModel(
|
||||
baseInformation: {
|
||||
"Title": item.name,
|
||||
"Container": item.container,
|
||||
"Path": item.path,
|
||||
"Size": item.mediaSources?.firstOrNull?.size.byteFormat,
|
||||
},
|
||||
videoStreams: videoStreams
|
||||
.map(
|
||||
(e) => {
|
||||
"Title": e.displayTitle,
|
||||
"Codec": e.codec,
|
||||
"Profile": e.profile,
|
||||
"Level": e.level,
|
||||
"Resolution": "${e.width}x${e.height}",
|
||||
"Aspect Ration": e.aspectRatio,
|
||||
"Interlaced": e.isInterlaced,
|
||||
"FrameRate": e.realFrameRate,
|
||||
"Bitrate": "${e.bitRate} kbps",
|
||||
"Bit depth": e.bitDepth,
|
||||
"Video range": e.videoRange,
|
||||
"Video range type": e.videoRangeType,
|
||||
"Ref frames": e.refFrames,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
audioStreams: audioStreams
|
||||
.map(
|
||||
(e) => {
|
||||
"Title": e.displayTitle,
|
||||
"Language": e.language,
|
||||
"Codec": e.codec,
|
||||
"Layout": e.channelLayout,
|
||||
"Bitrate": "${e.bitRate} kbps",
|
||||
"Sample Rate": "${e.sampleRate} Hz",
|
||||
"Default": e.isDefault,
|
||||
"Forced": e.isForced,
|
||||
"External": e.isExternal,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
subStreams: subStreams
|
||||
.map(
|
||||
(e) => {
|
||||
"Title": e.displayTitle,
|
||||
"Language": e.language,
|
||||
"Codec": e.codec,
|
||||
"Profile": e.profile,
|
||||
"Default": e.isDefault,
|
||||
"Forced": e.isForced,
|
||||
"External": e.isExternal,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
InformationModel copyWith({
|
||||
Map<String, dynamic>? baseInformation,
|
||||
List<Map<String, dynamic>>? videoStreams,
|
||||
List<Map<String, dynamic>>? audioStreams,
|
||||
List<Map<String, dynamic>>? subStreams,
|
||||
}) {
|
||||
return InformationModel(
|
||||
baseInformation: baseInformation ?? this.baseInformation,
|
||||
videoStreams: videoStreams ?? this.videoStreams,
|
||||
audioStreams: audioStreams ?? this.audioStreams,
|
||||
subStreams: subStreams ?? this.subStreams,
|
||||
);
|
||||
}
|
||||
|
||||
static String mapToString(Map<String, dynamic> map) {
|
||||
return map.entries.map((e) => "${e.key}: ${e.value}").join("\n");
|
||||
}
|
||||
|
||||
static String streamsToString(List<Map<String, dynamic>> streams) {
|
||||
return streams.map((e) => mapToString(e)).join("\n");
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => "${mapToString(baseInformation)}\n\n"
|
||||
"${streamsToString(videoStreams)}\n\n"
|
||||
"${streamsToString(audioStreams)}\n\n"
|
||||
"${streamsToString(subStreams)}\n\n";
|
||||
}
|
||||
363
lib/models/item_base_model.dart
Normal file
363
lib/models/item_base_model.dart
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/models/book_model.dart';
|
||||
import 'package:fladder/models/boxset_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/models/playlist_model.dart';
|
||||
import 'package:fladder/routes/build_routes/home_routes.dart';
|
||||
import 'package:fladder/routes/build_routes/route_builder.dart';
|
||||
import 'package:fladder/screens/details_screens/book_detail_screen.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/folder_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/models/items/person_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/screens/details_screens/details_screens.dart';
|
||||
import 'package:fladder/screens/details_screens/episode_detail_screen.dart';
|
||||
import 'package:fladder/screens/details_screens/season_detail_screen.dart';
|
||||
import 'package:fladder/screens/library_search/library_search_screen.dart';
|
||||
|
||||
part 'item_base_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class ItemBaseModel with ItemBaseModelMappable {
|
||||
final String name;
|
||||
final String id;
|
||||
final OverviewModel overview;
|
||||
final String? parentId;
|
||||
final String? playlistId;
|
||||
final ImagesData? images;
|
||||
final int? childCount;
|
||||
final double? primaryRatio;
|
||||
final UserData userData;
|
||||
final bool? canDownload;
|
||||
final bool? canDelete;
|
||||
final dto.BaseItemKind? jellyType;
|
||||
|
||||
const ItemBaseModel({
|
||||
required this.name,
|
||||
required this.id,
|
||||
required this.overview,
|
||||
required this.parentId,
|
||||
required this.playlistId,
|
||||
required this.images,
|
||||
required this.childCount,
|
||||
required this.primaryRatio,
|
||||
required this.userData,
|
||||
required this.canDownload,
|
||||
required this.canDelete,
|
||||
required this.jellyType,
|
||||
});
|
||||
|
||||
String get title => name;
|
||||
|
||||
ItemBaseModel? setProgress(double progress) {
|
||||
return copyWith(userData: userData.copyWith(progress: progress));
|
||||
}
|
||||
|
||||
Widget? subTitle(SortingOptions options) => switch (options) {
|
||||
SortingOptions.parentalRating => overview.parentalRating != null
|
||||
? Row(
|
||||
children: [
|
||||
Icon(
|
||||
IconsaxBold.star_1,
|
||||
size: 14,
|
||||
color: Colors.yellowAccent,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text((overview.parentalRating ?? 0.0).toString())
|
||||
],
|
||||
)
|
||||
: null,
|
||||
SortingOptions.communityRating => overview.communityRating != null
|
||||
? Row(
|
||||
children: [
|
||||
Icon(
|
||||
IconsaxBold.star_1,
|
||||
size: 14,
|
||||
color: Colors.yellowAccent,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text((overview.communityRating ?? 0.0).toString())
|
||||
],
|
||||
)
|
||||
: null,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
///Used for retrieving the correct id when fetching queue
|
||||
String get streamId => id;
|
||||
|
||||
ItemBaseModel get parentBaseModel => copyWith(id: parentId);
|
||||
|
||||
bool get emptyShow => false;
|
||||
|
||||
bool get identifiable => false;
|
||||
|
||||
int? get unPlayedItemCount => userData.unPlayedItemCount;
|
||||
|
||||
bool get unWatched => !userData.played && userData.progress <= 0 && userData.unPlayedItemCount == 0;
|
||||
|
||||
String? detailedName(BuildContext context) => null;
|
||||
|
||||
String? get subText => null;
|
||||
String? subTextShort(BuildContext context) => null;
|
||||
String? label(BuildContext context) => null;
|
||||
|
||||
ImagesData? get getPosters => images;
|
||||
|
||||
ImageData? get bannerImage => images?.primary ?? getPosters?.randomBackDrop ?? getPosters?.primary;
|
||||
|
||||
bool get playAble => false;
|
||||
|
||||
bool get syncAble => false;
|
||||
|
||||
bool get galleryItem => false;
|
||||
|
||||
MediaStreamsModel? get streamModel => null;
|
||||
|
||||
String playText(BuildContext context) => context.localized.play(name);
|
||||
|
||||
double get progress => userData.progress;
|
||||
|
||||
String playButtonLabel(BuildContext context) =>
|
||||
progress != 0 ? context.localized.resume(name.maxLength()) : context.localized.play(name.maxLength());
|
||||
|
||||
Widget get detailScreenWidget {
|
||||
switch (this) {
|
||||
case PersonModel _:
|
||||
return PersonDetailScreen(person: Person(id: id, image: images?.primary));
|
||||
case SeasonModel _:
|
||||
return SeasonDetailScreen(item: this);
|
||||
case FolderModel _:
|
||||
case PhotoAlbumModel _:
|
||||
case BoxSetModel _:
|
||||
case PlaylistModel _:
|
||||
return LibrarySearchScreen(folderId: [id]);
|
||||
case PhotoModel _:
|
||||
final photo = this as PhotoModel;
|
||||
return LibrarySearchScreen(
|
||||
folderId: [photo.albumId ?? photo.parentId ?? ""],
|
||||
photoToView: photo,
|
||||
);
|
||||
case BookModel book:
|
||||
return BookDetailScreen(item: book);
|
||||
case MovieModel _:
|
||||
return MovieDetailScreen(item: this);
|
||||
case EpisodeModel _:
|
||||
return EpisodeDetailScreen(item: this);
|
||||
case SeriesModel series:
|
||||
return SeriesDetailScreen(item: series);
|
||||
default:
|
||||
return EmptyItem(item: this);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> navigateTo(BuildContext context) async => context.routePush(DetailsRoute(id: id), extra: this);
|
||||
|
||||
factory ItemBaseModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return switch (item.type) {
|
||||
BaseItemKind.photo || BaseItemKind.video => PhotoModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.photoalbum => PhotoAlbumModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.folder ||
|
||||
BaseItemKind.collectionfolder ||
|
||||
BaseItemKind.aggregatefolder =>
|
||||
FolderModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.episode => EpisodeModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.movie => MovieModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.series => SeriesModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.person => PersonModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.season => SeasonModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.boxset => BoxSetModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.book => BookModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.playlist => PlaylistModel.fromBaseDto(item, ref),
|
||||
_ => ItemBaseModel._fromBaseDto(item, ref)
|
||||
};
|
||||
}
|
||||
|
||||
factory ItemBaseModel._fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return ItemBaseModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
jellyType: item.type,
|
||||
);
|
||||
}
|
||||
|
||||
FladderItemType get type => switch (this) {
|
||||
MovieModel _ => FladderItemType.movie,
|
||||
SeriesModel _ => FladderItemType.series,
|
||||
SeasonModel _ => FladderItemType.season,
|
||||
PhotoAlbumModel _ => FladderItemType.photoalbum,
|
||||
PhotoModel model => model.internalType,
|
||||
EpisodeModel _ => FladderItemType.episode,
|
||||
BookModel _ => FladderItemType.book,
|
||||
PlaylistModel _ => FladderItemType.playlist,
|
||||
FolderModel _ => FladderItemType.folder,
|
||||
ItemBaseModel _ => FladderItemType.baseType,
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(covariant ItemBaseModel other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^ type.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Currently supported types
|
||||
enum FladderItemType {
|
||||
baseType(
|
||||
icon: IconsaxOutline.folder_2,
|
||||
selectedicon: IconsaxBold.folder_2,
|
||||
),
|
||||
audio(
|
||||
icon: IconsaxOutline.music,
|
||||
selectedicon: IconsaxBold.music,
|
||||
),
|
||||
musicAlbum(
|
||||
icon: IconsaxOutline.music,
|
||||
selectedicon: IconsaxBold.music,
|
||||
),
|
||||
musicVideo(
|
||||
icon: IconsaxOutline.music,
|
||||
selectedicon: IconsaxBold.music,
|
||||
),
|
||||
collectionFolder(
|
||||
icon: IconsaxOutline.music,
|
||||
selectedicon: IconsaxBold.music,
|
||||
),
|
||||
video(
|
||||
icon: IconsaxOutline.video,
|
||||
selectedicon: IconsaxBold.video,
|
||||
),
|
||||
movie(
|
||||
icon: IconsaxOutline.video_horizontal,
|
||||
selectedicon: IconsaxBold.video_horizontal,
|
||||
),
|
||||
series(
|
||||
icon: IconsaxOutline.video_vertical,
|
||||
selectedicon: IconsaxBold.video_vertical,
|
||||
),
|
||||
season(
|
||||
icon: IconsaxOutline.video_vertical,
|
||||
selectedicon: IconsaxBold.video_vertical,
|
||||
),
|
||||
episode(
|
||||
icon: IconsaxOutline.video_vertical,
|
||||
selectedicon: IconsaxBold.video_vertical,
|
||||
),
|
||||
photo(
|
||||
icon: IconsaxOutline.picture_frame,
|
||||
selectedicon: IconsaxBold.picture_frame,
|
||||
),
|
||||
person(
|
||||
icon: IconsaxOutline.user,
|
||||
selectedicon: IconsaxBold.user,
|
||||
),
|
||||
photoalbum(
|
||||
icon: IconsaxOutline.gallery,
|
||||
selectedicon: IconsaxBold.gallery,
|
||||
),
|
||||
folder(
|
||||
icon: IconsaxOutline.folder,
|
||||
selectedicon: IconsaxBold.folder,
|
||||
),
|
||||
boxset(
|
||||
icon: IconsaxOutline.bookmark,
|
||||
selectedicon: IconsaxBold.bookmark,
|
||||
),
|
||||
playlist(
|
||||
icon: IconsaxOutline.archive_book,
|
||||
selectedicon: IconsaxBold.archive_book,
|
||||
),
|
||||
book(
|
||||
icon: IconsaxOutline.book,
|
||||
selectedicon: IconsaxBold.book,
|
||||
);
|
||||
|
||||
const FladderItemType({required this.icon, required this.selectedicon});
|
||||
|
||||
static Set<FladderItemType> get playable => {
|
||||
FladderItemType.series,
|
||||
FladderItemType.episode,
|
||||
FladderItemType.season,
|
||||
FladderItemType.movie,
|
||||
FladderItemType.musicVideo,
|
||||
};
|
||||
|
||||
static Set<FladderItemType> get galleryItem => {
|
||||
FladderItemType.photo,
|
||||
FladderItemType.video,
|
||||
};
|
||||
|
||||
String label(BuildContext context) {
|
||||
return switch (this) {
|
||||
FladderItemType.baseType => context.localized.mediaTypeBase,
|
||||
FladderItemType.audio => context.localized.audio,
|
||||
FladderItemType.collectionFolder => context.localized.collectionFolder,
|
||||
FladderItemType.musicAlbum => context.localized.musicAlbum,
|
||||
FladderItemType.musicVideo => context.localized.video,
|
||||
FladderItemType.video => context.localized.video,
|
||||
FladderItemType.movie => context.localized.mediaTypeMovie,
|
||||
FladderItemType.series => context.localized.mediaTypeSeries,
|
||||
FladderItemType.season => context.localized.mediaTypeSeason,
|
||||
FladderItemType.episode => context.localized.mediaTypeEpisode,
|
||||
FladderItemType.photo => context.localized.mediaTypePhoto,
|
||||
FladderItemType.person => context.localized.mediaTypePerson,
|
||||
FladderItemType.photoalbum => context.localized.mediaTypePhotoAlbum,
|
||||
FladderItemType.folder => context.localized.mediaTypeFolder,
|
||||
FladderItemType.boxset => context.localized.mediaTypeBoxset,
|
||||
FladderItemType.playlist => context.localized.mediaTypePlaylist,
|
||||
FladderItemType.book => context.localized.mediaTypeBook,
|
||||
};
|
||||
}
|
||||
|
||||
BaseItemKind get dtoKind => switch (this) {
|
||||
FladderItemType.baseType => BaseItemKind.userrootfolder,
|
||||
FladderItemType.audio => BaseItemKind.audio,
|
||||
FladderItemType.collectionFolder => BaseItemKind.collectionfolder,
|
||||
FladderItemType.musicAlbum => BaseItemKind.musicalbum,
|
||||
FladderItemType.musicVideo => BaseItemKind.video,
|
||||
FladderItemType.video => BaseItemKind.video,
|
||||
FladderItemType.movie => BaseItemKind.movie,
|
||||
FladderItemType.series => BaseItemKind.series,
|
||||
FladderItemType.season => BaseItemKind.season,
|
||||
FladderItemType.episode => BaseItemKind.episode,
|
||||
FladderItemType.photo => BaseItemKind.photo,
|
||||
FladderItemType.person => BaseItemKind.person,
|
||||
FladderItemType.photoalbum => BaseItemKind.photoalbum,
|
||||
FladderItemType.folder => BaseItemKind.folder,
|
||||
FladderItemType.boxset => BaseItemKind.boxset,
|
||||
FladderItemType.playlist => BaseItemKind.playlist,
|
||||
FladderItemType.book => BaseItemKind.book,
|
||||
};
|
||||
|
||||
final IconData icon;
|
||||
final IconData selectedicon;
|
||||
}
|
||||
214
lib/models/item_base_model.mapper.dart
Normal file
214
lib/models/item_base_model.mapper.dart
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'item_base_model.dart';
|
||||
|
||||
class ItemBaseModelMapper extends ClassMapperBase<ItemBaseModel> {
|
||||
ItemBaseModelMapper._();
|
||||
|
||||
static ItemBaseModelMapper? _instance;
|
||||
static ItemBaseModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = ItemBaseModelMapper._());
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'ItemBaseModel';
|
||||
|
||||
static String _$name(ItemBaseModel v) => v.name;
|
||||
static const Field<ItemBaseModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(ItemBaseModel v) => v.id;
|
||||
static const Field<ItemBaseModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(ItemBaseModel v) => v.overview;
|
||||
static const Field<ItemBaseModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(ItemBaseModel v) => v.parentId;
|
||||
static const Field<ItemBaseModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(ItemBaseModel v) => v.playlistId;
|
||||
static const Field<ItemBaseModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(ItemBaseModel v) => v.images;
|
||||
static const Field<ItemBaseModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(ItemBaseModel v) => v.childCount;
|
||||
static const Field<ItemBaseModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(ItemBaseModel v) => v.primaryRatio;
|
||||
static const Field<ItemBaseModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(ItemBaseModel v) => v.userData;
|
||||
static const Field<ItemBaseModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDownload(ItemBaseModel v) => v.canDownload;
|
||||
static const Field<ItemBaseModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload);
|
||||
static bool? _$canDelete(ItemBaseModel v) => v.canDelete;
|
||||
static const Field<ItemBaseModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete);
|
||||
static dto.BaseItemKind? _$jellyType(ItemBaseModel v) => v.jellyType;
|
||||
static const Field<ItemBaseModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType);
|
||||
|
||||
@override
|
||||
final MappableFields<ItemBaseModel> fields = const {
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDownload: _f$canDownload,
|
||||
#canDelete: _f$canDelete,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
static ItemBaseModel _instantiate(DecodingData data) {
|
||||
return ItemBaseModel(
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static ItemBaseModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<ItemBaseModel>(map);
|
||||
}
|
||||
|
||||
static ItemBaseModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<ItemBaseModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin ItemBaseModelMappable {
|
||||
String toJson() {
|
||||
return ItemBaseModelMapper.ensureInitialized()
|
||||
.encodeJson<ItemBaseModel>(this as ItemBaseModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return ItemBaseModelMapper.ensureInitialized()
|
||||
.encodeMap<ItemBaseModel>(this as ItemBaseModel);
|
||||
}
|
||||
|
||||
ItemBaseModelCopyWith<ItemBaseModel, ItemBaseModel, ItemBaseModel>
|
||||
get copyWith => _ItemBaseModelCopyWithImpl(
|
||||
this as ItemBaseModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return ItemBaseModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as ItemBaseModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemBaseModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, ItemBaseModel, $Out> {
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, $Out> get $asItemBaseModel =>
|
||||
$base.as((v, t, t2) => _ItemBaseModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class ItemBaseModelCopyWith<$R, $In extends ItemBaseModel, $Out>
|
||||
implements ClassCopyWith<$R, $In, $Out> {
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
$R call(
|
||||
{String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDownload,
|
||||
bool? canDelete,
|
||||
dto.BaseItemKind? jellyType});
|
||||
ItemBaseModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _ItemBaseModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, ItemBaseModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, ItemBaseModel, $Out> {
|
||||
_ItemBaseModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<ItemBaseModel> $mapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDownload = $none,
|
||||
Object? canDelete = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
ItemBaseModel $make(CopyWithData data) => ItemBaseModel(
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
ItemBaseModelCopyWith<$R2, ItemBaseModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_ItemBaseModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
318
lib/models/item_editing_model.dart
Normal file
318
lib/models/item_editing_model.dart
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as jelly;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/providers/image_provider.dart';
|
||||
|
||||
class EditItemsProvider {
|
||||
final List<EditingImageModel> serverImages;
|
||||
final List<EditingImageModel> images;
|
||||
final List<EditingImageModel> customImages;
|
||||
final EditingImageModel? selected;
|
||||
final List<EditingImageModel> selection;
|
||||
const EditItemsProvider({
|
||||
this.serverImages = const [],
|
||||
this.images = const [],
|
||||
this.customImages = const [],
|
||||
this.selected,
|
||||
this.selection = const [],
|
||||
});
|
||||
|
||||
Future<void> setImage(
|
||||
jelly.ImageType type, {
|
||||
required Function(EditingImageModel? imageModel) uploadData,
|
||||
required Function(EditingImageModel? imageModel) uploadUrl,
|
||||
}) async {
|
||||
switch (type) {
|
||||
case jelly.ImageType.primary:
|
||||
case jelly.ImageType.logo:
|
||||
{
|
||||
if (selected == null) return;
|
||||
if (selected?.imageData != null) {
|
||||
await uploadData(selected!.copyWith(type: type));
|
||||
} else if (selected?.url != null) {
|
||||
await uploadUrl(selected!.copyWith(type: type));
|
||||
}
|
||||
}
|
||||
case jelly.ImageType.backdrop:
|
||||
{
|
||||
for (var element in selection) {
|
||||
if (element.imageData != null) {
|
||||
await uploadData(element.copyWith(type: type));
|
||||
} else if (element.url != null) {
|
||||
await uploadUrl(element.copyWith(type: type));
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EditItemsProvider copyWith({
|
||||
List<EditingImageModel>? serverImages,
|
||||
List<EditingImageModel>? images,
|
||||
List<EditingImageModel>? customImages,
|
||||
ValueGetter<EditingImageModel?>? selected,
|
||||
List<EditingImageModel>? selection,
|
||||
}) {
|
||||
return EditItemsProvider(
|
||||
serverImages: serverImages ?? this.serverImages,
|
||||
images: images ?? this.images,
|
||||
customImages: customImages ?? this.customImages,
|
||||
selected: selected != null ? selected() : this.selected,
|
||||
selection: selection ?? this.selection,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemEditingModel {
|
||||
final ItemBaseModel? item;
|
||||
final jelly.MetadataEditorInfo? editorInfo;
|
||||
final Map<String, dynamic>? json;
|
||||
final Map<String, dynamic>? editedJson;
|
||||
final bool includeAllImages;
|
||||
final EditItemsProvider primary;
|
||||
final EditItemsProvider logo;
|
||||
final EditItemsProvider backdrop;
|
||||
final bool saving;
|
||||
ItemEditingModel({
|
||||
this.item,
|
||||
this.editorInfo,
|
||||
this.json,
|
||||
this.editedJson,
|
||||
this.includeAllImages = false,
|
||||
this.primary = const EditItemsProvider(),
|
||||
this.logo = const EditItemsProvider(),
|
||||
this.backdrop = const EditItemsProvider(),
|
||||
this.saving = false,
|
||||
});
|
||||
|
||||
Map<String, dynamic>? editAbleFields() {
|
||||
return editedJson == null
|
||||
? {}
|
||||
: {
|
||||
"Name": editedJson?["Name"] as String?,
|
||||
"OriginalTitle": editedJson?["OriginalTitle"] as String?,
|
||||
"PremiereDate": editedJson?["PremiereDate"] != null ? DateTime.tryParse(editedJson!["PremiereDate"]) : null,
|
||||
"DateCreated": editedJson?["DateCreated"] != null ? DateTime.tryParse(editedJson!["DateCreated"]) : null,
|
||||
"ProductionYear": editedJson?["ProductionYear"] as int?,
|
||||
"Path": editedJson?["Path"] as String?,
|
||||
"Overview": editedJson?["Overview"] as String? ?? "",
|
||||
}
|
||||
..removeWhere((key, value) => value == null);
|
||||
}
|
||||
|
||||
Map<String, dynamic>? editAdvancedAbleFields(Ref ref) => editedJson == null
|
||||
? {}
|
||||
: {
|
||||
if (item is SeriesModel) "DisplayOrder": DisplayOrder.fromMap(editedJson?["DisplayOrder"]),
|
||||
if (item is SeriesModel) ...{
|
||||
"OfficialRating": {
|
||||
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
|
||||
?..add(json?["OfficialRating"] as String?))
|
||||
?.whereNotNull()
|
||||
.toList() ??
|
||||
[])
|
||||
element: (editedJson?["OfficialRating"] as String?) == element
|
||||
},
|
||||
"CustomRating": {
|
||||
for (String element in (editorInfo?.parentalRatingOptions?.map((e) => e.name).toSet()
|
||||
?..add(json?["CustomRating"] as String?))
|
||||
?.whereNotNull()
|
||||
.toList() ??
|
||||
[])
|
||||
element: (editedJson?["CustomRating"] as String?) == element
|
||||
},
|
||||
},
|
||||
"People": editedJson?["People"] != null
|
||||
? (editedJson!["People"] as List<dynamic>)
|
||||
.map((e) => Person.fromBasePerson(jelly.BaseItemPerson.fromJson(e), ref))
|
||||
.toList()
|
||||
: null,
|
||||
"ExternalUrls": editedJson?["ExternalUrls"] != null
|
||||
? (editedJson!["ExternalUrls"] as List<dynamic>).map((e) => ExternalUrls.fromMap(e)).toList()
|
||||
: null,
|
||||
"CommunityRating": double.tryParse((editedJson?["CommunityRating"] as num?).toString()),
|
||||
"SeriesName": editedJson?["SeriesName"] as String?,
|
||||
"IndexNumber": editedJson?["IndexNumber"] as int?,
|
||||
"RunTimeTicks": (editedJson?["RunTimeTicks"] == null)
|
||||
? null
|
||||
: Duration(milliseconds: editedJson?["RunTimeTicks"] ~/ 10000),
|
||||
"ParentIndexNumber": editedJson?["ParentIndexNumber"] as int?,
|
||||
if (item is SeriesModel) "Status": ShowStatus.fromMap(editedJson?["Status"] as String?),
|
||||
"Genres": editedJson?["Genres"] != null ? (List<String>.from(editedJson!["Genres"])) : null,
|
||||
"Tags": editedJson?["Tags"] != null ? (List<String>.from(editedJson?["Tags"])) : null,
|
||||
"Studios": editedJson?["Studios"] != null
|
||||
? (editedJson!["Studios"] as List<dynamic>).map((e) => Studio.fromMap(e)).toList()
|
||||
: null,
|
||||
"SeriesStudio": editedJson?["SeriesStudio"] as String?,
|
||||
"LockData": editedJson?["LockData"] as bool? ?? false,
|
||||
"LockedFields": ((editedJson?["LockData"] as bool?) == false)
|
||||
? EditorLockedFields.enabled(List<String>.from(editedJson?["LockedFields"]))
|
||||
: null,
|
||||
}
|
||||
..removeWhere((key, value) => value == null);
|
||||
|
||||
ItemEditingModel copyWith({
|
||||
ValueGetter<ItemBaseModel?>? item,
|
||||
ValueGetter<jelly.MetadataEditorInfo?>? editorInfo,
|
||||
ValueGetter<Map<String, dynamic>?>? json,
|
||||
ValueGetter<Map<String, dynamic>?>? editedJson,
|
||||
bool? includeAllImages,
|
||||
EditItemsProvider? primary,
|
||||
EditItemsProvider? logo,
|
||||
EditItemsProvider? backdrop,
|
||||
bool? saving,
|
||||
}) {
|
||||
return ItemEditingModel(
|
||||
item: item != null ? item() : this.item,
|
||||
editorInfo: editorInfo != null ? editorInfo() : this.editorInfo,
|
||||
json: json != null ? json() : this.json,
|
||||
editedJson: editedJson != null ? editedJson() : this.editedJson,
|
||||
includeAllImages: includeAllImages ?? this.includeAllImages,
|
||||
primary: primary ?? this.primary,
|
||||
logo: logo ?? this.logo,
|
||||
backdrop: backdrop ?? this.backdrop,
|
||||
saving: saving ?? this.saving,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EditingImageModel {
|
||||
final String providerName;
|
||||
final String? url;
|
||||
final Uint8List? imageData;
|
||||
final int? index;
|
||||
final int height;
|
||||
final int width;
|
||||
final double communityRating;
|
||||
final int voteCount;
|
||||
final String language;
|
||||
final jelly.ImageType type;
|
||||
final jelly.RatingType ratingType;
|
||||
EditingImageModel({
|
||||
required this.providerName,
|
||||
this.url,
|
||||
this.imageData,
|
||||
this.index,
|
||||
this.height = 0,
|
||||
this.width = 0,
|
||||
this.communityRating = 0.0,
|
||||
this.voteCount = 0,
|
||||
this.language = "",
|
||||
this.type = jelly.ImageType.primary,
|
||||
this.ratingType = jelly.RatingType.likes,
|
||||
});
|
||||
|
||||
double get ratio {
|
||||
if (width == 0 && height == 0) return 1;
|
||||
final ratio = (width.toDouble() / height.toDouble()).clamp(0.1, 5).toDouble();
|
||||
if (ratio < 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return ratio;
|
||||
}
|
||||
}
|
||||
|
||||
factory EditingImageModel.fromDto(jelly.RemoteImageInfo info) {
|
||||
return EditingImageModel(
|
||||
providerName: info.providerName ?? "",
|
||||
url: info.url ?? "",
|
||||
height: info.height ?? 0,
|
||||
width: info.width ?? 0,
|
||||
communityRating: info.communityRating ?? 0.0,
|
||||
voteCount: info.voteCount ?? 0,
|
||||
language: info.language ?? "",
|
||||
type: info.type ?? jelly.ImageType.primary,
|
||||
ratingType: info.ratingType ?? jelly.RatingType.likes,
|
||||
);
|
||||
}
|
||||
|
||||
factory EditingImageModel.fromImage(jelly.ImageInfo info, String itemId, Ref ref) {
|
||||
return EditingImageModel(
|
||||
providerName: "",
|
||||
url: switch (info.imageType ?? ImageType.primary) {
|
||||
ImageType.backdrop => ref.read(imageUtilityProvider).getBackdropOrigImage(
|
||||
itemId,
|
||||
info.imageIndex ?? 0,
|
||||
info.hashCode.toString(),
|
||||
),
|
||||
_ => ref.read(imageUtilityProvider).getItemsOrigImageUrl(
|
||||
itemId,
|
||||
type: info.imageType ?? ImageType.primary,
|
||||
),
|
||||
},
|
||||
index: info.imageIndex,
|
||||
height: info.height ?? 0,
|
||||
width: info.width ?? 0,
|
||||
type: info.imageType ?? ImageType.primary,
|
||||
);
|
||||
}
|
||||
|
||||
EditingImageModel copyWith({
|
||||
String? providerName,
|
||||
ValueGetter<String?>? url,
|
||||
ValueGetter<Uint8List?>? imageData,
|
||||
ValueGetter<int?>? index,
|
||||
int? height,
|
||||
int? width,
|
||||
double? communityRating,
|
||||
int? voteCount,
|
||||
String? language,
|
||||
jelly.ImageType? type,
|
||||
jelly.RatingType? ratingType,
|
||||
}) {
|
||||
return EditingImageModel(
|
||||
providerName: providerName ?? this.providerName,
|
||||
url: url != null ? url() : this.url,
|
||||
imageData: imageData != null ? imageData() : this.imageData,
|
||||
index: index != null ? index() : this.index,
|
||||
height: height ?? this.height,
|
||||
width: width ?? this.width,
|
||||
communityRating: communityRating ?? this.communityRating,
|
||||
voteCount: voteCount ?? this.voteCount,
|
||||
language: language ?? this.language,
|
||||
type: type ?? this.type,
|
||||
ratingType: ratingType ?? this.ratingType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is EditingImageModel &&
|
||||
other.providerName == providerName &&
|
||||
other.url == url &&
|
||||
other.imageData == imageData &&
|
||||
other.height == height &&
|
||||
other.width == width &&
|
||||
other.communityRating == communityRating &&
|
||||
other.voteCount == voteCount &&
|
||||
other.language == language &&
|
||||
other.type == type &&
|
||||
other.ratingType == ratingType;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return providerName.hashCode ^
|
||||
url.hashCode ^
|
||||
imageData.hashCode ^
|
||||
height.hashCode ^
|
||||
width.hashCode ^
|
||||
communityRating.hashCode ^
|
||||
voteCount.hashCode ^
|
||||
language.hashCode ^
|
||||
type.hashCode ^
|
||||
ratingType.hashCode;
|
||||
}
|
||||
}
|
||||
90
lib/models/items/chapters_model.dart
Normal file
90
lib/models/items/chapters_model.dart
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/providers/image_provider.dart';
|
||||
|
||||
class Chapter {
|
||||
final String name;
|
||||
final String imageUrl;
|
||||
final Uint8List? imageData;
|
||||
final Duration startPosition;
|
||||
Chapter({
|
||||
required this.name,
|
||||
required this.imageUrl,
|
||||
this.imageData,
|
||||
required this.startPosition,
|
||||
});
|
||||
|
||||
ImageProvider get imageProvider {
|
||||
if (imageData != null) {
|
||||
return Image.memory(imageData!).image;
|
||||
}
|
||||
if (imageUrl.startsWith("http")) {
|
||||
return CachedNetworkImageProvider(
|
||||
cacheKey: name + imageUrl,
|
||||
cacheManager: CustomCacheManager.instance,
|
||||
imageUrl,
|
||||
);
|
||||
} else {
|
||||
return Image.file(
|
||||
key: Key(name + imageUrl),
|
||||
File(imageUrl),
|
||||
).image;
|
||||
}
|
||||
}
|
||||
|
||||
static List<Chapter> chaptersFromInfo(String itemId, List<dto.ChapterInfo> chapters, Ref ref) {
|
||||
return chapters
|
||||
.mapIndexed((index, element) => Chapter(
|
||||
name: element.name ?? "",
|
||||
imageUrl: ref.read(imageUtilityProvider).getChapterUrl(itemId, index),
|
||||
startPosition: Duration(milliseconds: (element.startPositionTicks ?? 0) ~/ 10000)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Chapter copyWith({
|
||||
String? name,
|
||||
String? imageUrl,
|
||||
Duration? startPosition,
|
||||
}) {
|
||||
return Chapter(
|
||||
name: name ?? this.name,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
startPosition: startPosition ?? this.startPosition,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
'imageUrl': imageUrl,
|
||||
'startPosition': startPosition.inMilliseconds,
|
||||
};
|
||||
}
|
||||
|
||||
factory Chapter.fromMap(Map<String, dynamic> map) {
|
||||
return Chapter(
|
||||
name: map['name'] ?? '',
|
||||
imageUrl: map['imageUrl'] ?? '',
|
||||
startPosition: Duration(milliseconds: map['startPosition'] as int),
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Chapter.fromJson(String source) => Chapter.fromMap(json.decode(source));
|
||||
}
|
||||
|
||||
extension ChapterExtension on List<Chapter> {
|
||||
Chapter? getChapterFromDuration(Duration duration) {
|
||||
return lastWhereOrNull((element) => element.startPosition < duration);
|
||||
}
|
||||
}
|
||||
201
lib/models/items/episode_model.dart
Normal file
201
lib/models/items/episode_model.dart
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/jellyfin/enum_models.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/item_stream_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'episode_model.mapper.dart';
|
||||
|
||||
enum EpisodeStatus { available, unaired, missing }
|
||||
|
||||
@MappableClass()
|
||||
class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
||||
final String? seriesName;
|
||||
final int season;
|
||||
final int episode;
|
||||
final List<Chapter> chapters;
|
||||
final ItemLocation? location;
|
||||
final DateTime? dateAired;
|
||||
const EpisodeModel({
|
||||
required this.seriesName,
|
||||
required this.season,
|
||||
required this.episode,
|
||||
this.chapters = const [],
|
||||
this.location,
|
||||
this.dateAired,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.parentImages,
|
||||
required super.mediaStreams,
|
||||
super.canDelete,
|
||||
super.canDownload,
|
||||
super.jellyType,
|
||||
});
|
||||
EpisodeStatus get status {
|
||||
return switch (location) {
|
||||
ItemLocation.filesystem => EpisodeStatus.available,
|
||||
ItemLocation.virtual =>
|
||||
(dateAired?.isBefore(DateTime.now()) == true) ? EpisodeStatus.missing : EpisodeStatus.unaired,
|
||||
_ => EpisodeStatus.missing
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String? detailedName(BuildContext context) => "${subTextShort(context)} - $name";
|
||||
|
||||
@override
|
||||
SeriesModel get parentBaseModel => SeriesModel(
|
||||
originalTitle: '',
|
||||
sortName: '',
|
||||
status: "",
|
||||
name: seriesName ?? "",
|
||||
id: parentId ?? "",
|
||||
playlistId: playlistId,
|
||||
overview: overview,
|
||||
parentId: parentId,
|
||||
images: images,
|
||||
childCount: childCount,
|
||||
primaryRatio: primaryRatio,
|
||||
userData: UserData(),
|
||||
);
|
||||
|
||||
@override
|
||||
String get streamId => parentId ?? "";
|
||||
|
||||
@override
|
||||
String get title => seriesName ?? name;
|
||||
|
||||
@override
|
||||
MediaStreamsModel? get streamModel => mediaStreams;
|
||||
|
||||
@override
|
||||
ImagesData? get getPosters => parentImages;
|
||||
|
||||
@override
|
||||
String? get subText => name.isEmpty ? "TBA" : name;
|
||||
|
||||
@override
|
||||
String? subTextShort(BuildContext context) => seasonEpisodeLabel(context);
|
||||
|
||||
@override
|
||||
String? label(BuildContext context) => "${subTextShort(context)} - $name";
|
||||
|
||||
@override
|
||||
bool get playAble => switch (status) {
|
||||
EpisodeStatus.available => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@override
|
||||
String playButtonLabel(BuildContext context) {
|
||||
final string = seasonEpisodeLabel(context).maxLength();
|
||||
return progress != 0 ? context.localized.resume(string) : context.localized.play(string);
|
||||
}
|
||||
|
||||
String seasonAnnotation(BuildContext context) => context.localized.season(1)[0];
|
||||
String episodeAnnotation(BuildContext context) => context.localized.episode(1)[0];
|
||||
|
||||
String seasonEpisodeLabel(BuildContext context) {
|
||||
return "${seasonAnnotation(context)}$season - ${episodeAnnotation(context)}$episode";
|
||||
}
|
||||
|
||||
String seasonEpisodeLabelFull(BuildContext context) {
|
||||
return "${context.localized.season(1)} $season - ${context.localized.episode(1)} $episode";
|
||||
}
|
||||
|
||||
String episodeLabel(BuildContext context) {
|
||||
return "${seasonEpisodeLabel(context)} - $subText";
|
||||
}
|
||||
|
||||
String get fullName {
|
||||
return "$episode. $subText";
|
||||
}
|
||||
|
||||
@override
|
||||
bool get syncAble => playAble;
|
||||
|
||||
@override
|
||||
factory EpisodeModel.fromBaseDto(dto.BaseItemDto item, Ref ref) => EpisodeModel(
|
||||
seriesName: item.seriesName,
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.seriesId,
|
||||
playlistId: item.playlistItemId,
|
||||
dateAired: item.premiereDate,
|
||||
chapters: Chapter.chaptersFromInfo(item.id ?? "", item.chapters ?? [], ref),
|
||||
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
season: item.parentIndexNumber ?? 0,
|
||||
episode: item.indexNumber ?? 0,
|
||||
location: ItemLocation.fromDto(item.locationType),
|
||||
parentImages: ImagesData.fromBaseItemParent(item, ref),
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
mediaStreams:
|
||||
MediaStreamsModel.fromMediaStreamsList(item.mediaSources?.firstOrNull, item.mediaStreams ?? [], ref),
|
||||
jellyType: item.type,
|
||||
);
|
||||
|
||||
static List<EpisodeModel> episodesFromDto(List<dto.BaseItemDto>? dto, Ref ref) {
|
||||
return dto?.map((e) => EpisodeModel.fromBaseDto(e, ref)).toList() ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
extension EpisodeListExtensions on List<EpisodeModel> {
|
||||
Map<int, List<EpisodeModel>> get episodesBySeason {
|
||||
Map<int, List<EpisodeModel>> groupedItems = {};
|
||||
for (int i = 0; i < length; i++) {
|
||||
int seasonIndex = this[i].season;
|
||||
if (!groupedItems.containsKey(seasonIndex)) {
|
||||
groupedItems[seasonIndex] = [this[i]];
|
||||
} else {
|
||||
groupedItems[seasonIndex]?.add(this[i]);
|
||||
}
|
||||
}
|
||||
return groupedItems;
|
||||
}
|
||||
|
||||
EpisodeModel? get nextUp {
|
||||
final lastProgress =
|
||||
lastIndexWhere((element) => element.userData.progress != 0 && element.status == EpisodeStatus.available);
|
||||
final lastPlayed =
|
||||
lastIndexWhere((element) => element.userData.played && element.status == EpisodeStatus.available);
|
||||
|
||||
if (lastProgress == -1 && lastPlayed == -1) {
|
||||
return firstWhereOrNull((element) => element.status == EpisodeStatus.available);
|
||||
} else {
|
||||
return getRange(lastProgress > lastPlayed ? lastProgress : lastPlayed + 1, length)
|
||||
.firstWhereOrNull((element) => element.status == EpisodeStatus.available);
|
||||
}
|
||||
}
|
||||
|
||||
bool get allPlayed {
|
||||
for (var element in this) {
|
||||
if (!element.userData.played) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
301
lib/models/items/episode_model.mapper.dart
Normal file
301
lib/models/items/episode_model.mapper.dart
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'episode_model.dart';
|
||||
|
||||
class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
||||
EpisodeModelMapper._();
|
||||
|
||||
static EpisodeModelMapper? _instance;
|
||||
static EpisodeModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = EpisodeModelMapper._());
|
||||
ItemStreamModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'EpisodeModel';
|
||||
|
||||
static String? _$seriesName(EpisodeModel v) => v.seriesName;
|
||||
static const Field<EpisodeModel, String> _f$seriesName =
|
||||
Field('seriesName', _$seriesName);
|
||||
static int _$season(EpisodeModel v) => v.season;
|
||||
static const Field<EpisodeModel, int> _f$season = Field('season', _$season);
|
||||
static int _$episode(EpisodeModel v) => v.episode;
|
||||
static const Field<EpisodeModel, int> _f$episode =
|
||||
Field('episode', _$episode);
|
||||
static List<Chapter> _$chapters(EpisodeModel v) => v.chapters;
|
||||
static const Field<EpisodeModel, List<Chapter>> _f$chapters =
|
||||
Field('chapters', _$chapters, opt: true, def: const []);
|
||||
static ItemLocation? _$location(EpisodeModel v) => v.location;
|
||||
static const Field<EpisodeModel, ItemLocation> _f$location =
|
||||
Field('location', _$location, opt: true);
|
||||
static DateTime? _$dateAired(EpisodeModel v) => v.dateAired;
|
||||
static const Field<EpisodeModel, DateTime> _f$dateAired =
|
||||
Field('dateAired', _$dateAired, opt: true);
|
||||
static String _$name(EpisodeModel v) => v.name;
|
||||
static const Field<EpisodeModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(EpisodeModel v) => v.id;
|
||||
static const Field<EpisodeModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(EpisodeModel v) => v.overview;
|
||||
static const Field<EpisodeModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(EpisodeModel v) => v.parentId;
|
||||
static const Field<EpisodeModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(EpisodeModel v) => v.playlistId;
|
||||
static const Field<EpisodeModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(EpisodeModel v) => v.images;
|
||||
static const Field<EpisodeModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(EpisodeModel v) => v.childCount;
|
||||
static const Field<EpisodeModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(EpisodeModel v) => v.primaryRatio;
|
||||
static const Field<EpisodeModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(EpisodeModel v) => v.userData;
|
||||
static const Field<EpisodeModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static ImagesData? _$parentImages(EpisodeModel v) => v.parentImages;
|
||||
static const Field<EpisodeModel, ImagesData> _f$parentImages =
|
||||
Field('parentImages', _$parentImages);
|
||||
static MediaStreamsModel _$mediaStreams(EpisodeModel v) => v.mediaStreams;
|
||||
static const Field<EpisodeModel, MediaStreamsModel> _f$mediaStreams =
|
||||
Field('mediaStreams', _$mediaStreams);
|
||||
static bool? _$canDelete(EpisodeModel v) => v.canDelete;
|
||||
static const Field<EpisodeModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete, opt: true);
|
||||
static bool? _$canDownload(EpisodeModel v) => v.canDownload;
|
||||
static const Field<EpisodeModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload, opt: true);
|
||||
static dto.BaseItemKind? _$jellyType(EpisodeModel v) => v.jellyType;
|
||||
static const Field<EpisodeModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<EpisodeModel> fields = const {
|
||||
#seriesName: _f$seriesName,
|
||||
#season: _f$season,
|
||||
#episode: _f$episode,
|
||||
#chapters: _f$chapters,
|
||||
#location: _f$location,
|
||||
#dateAired: _f$dateAired,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#parentImages: _f$parentImages,
|
||||
#mediaStreams: _f$mediaStreams,
|
||||
#canDelete: _f$canDelete,
|
||||
#canDownload: _f$canDownload,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'EpisodeModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemStreamModelMapper.ensureInitialized();
|
||||
|
||||
static EpisodeModel _instantiate(DecodingData data) {
|
||||
return EpisodeModel(
|
||||
seriesName: data.dec(_f$seriesName),
|
||||
season: data.dec(_f$season),
|
||||
episode: data.dec(_f$episode),
|
||||
chapters: data.dec(_f$chapters),
|
||||
location: data.dec(_f$location),
|
||||
dateAired: data.dec(_f$dateAired),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
parentImages: data.dec(_f$parentImages),
|
||||
mediaStreams: data.dec(_f$mediaStreams),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static EpisodeModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<EpisodeModel>(map);
|
||||
}
|
||||
|
||||
static EpisodeModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<EpisodeModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin EpisodeModelMappable {
|
||||
String toJson() {
|
||||
return EpisodeModelMapper.ensureInitialized()
|
||||
.encodeJson<EpisodeModel>(this as EpisodeModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return EpisodeModelMapper.ensureInitialized()
|
||||
.encodeMap<EpisodeModel>(this as EpisodeModel);
|
||||
}
|
||||
|
||||
EpisodeModelCopyWith<EpisodeModel, EpisodeModel, EpisodeModel> get copyWith =>
|
||||
_EpisodeModelCopyWithImpl(this as EpisodeModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return EpisodeModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as EpisodeModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension EpisodeModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, EpisodeModel, $Out> {
|
||||
EpisodeModelCopyWith<$R, EpisodeModel, $Out> get $asEpisodeModel =>
|
||||
$base.as((v, t, t2) => _EpisodeModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class EpisodeModelCopyWith<$R, $In extends EpisodeModel, $Out>
|
||||
implements ItemStreamModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>> get chapters;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{String? seriesName,
|
||||
int? season,
|
||||
int? episode,
|
||||
List<Chapter>? chapters,
|
||||
ItemLocation? location,
|
||||
DateTime? dateAired,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
ImagesData? parentImages,
|
||||
MediaStreamsModel? mediaStreams,
|
||||
bool? canDelete,
|
||||
bool? canDownload,
|
||||
dto.BaseItemKind? jellyType});
|
||||
EpisodeModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _EpisodeModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, EpisodeModel, $Out>
|
||||
implements EpisodeModelCopyWith<$R, EpisodeModel, $Out> {
|
||||
_EpisodeModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<EpisodeModel> $mapper =
|
||||
EpisodeModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>
|
||||
get chapters => ListCopyWith($value.chapters,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(chapters: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? seriesName = $none,
|
||||
int? season,
|
||||
int? episode,
|
||||
List<Chapter>? chapters,
|
||||
Object? location = $none,
|
||||
Object? dateAired = $none,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? parentImages = $none,
|
||||
MediaStreamsModel? mediaStreams,
|
||||
Object? canDelete = $none,
|
||||
Object? canDownload = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (seriesName != $none) #seriesName: seriesName,
|
||||
if (season != null) #season: season,
|
||||
if (episode != null) #episode: episode,
|
||||
if (chapters != null) #chapters: chapters,
|
||||
if (location != $none) #location: location,
|
||||
if (dateAired != $none) #dateAired: dateAired,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (parentImages != $none) #parentImages: parentImages,
|
||||
if (mediaStreams != null) #mediaStreams: mediaStreams,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
EpisodeModel $make(CopyWithData data) => EpisodeModel(
|
||||
seriesName: data.get(#seriesName, or: $value.seriesName),
|
||||
season: data.get(#season, or: $value.season),
|
||||
episode: data.get(#episode, or: $value.episode),
|
||||
chapters: data.get(#chapters, or: $value.chapters),
|
||||
location: data.get(#location, or: $value.location),
|
||||
dateAired: data.get(#dateAired, or: $value.dateAired),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
parentImages: data.get(#parentImages, or: $value.parentImages),
|
||||
mediaStreams: data.get(#mediaStreams, or: $value.mediaStreams),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
EpisodeModelCopyWith<$R2, EpisodeModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_EpisodeModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
50
lib/models/items/folder_model.dart
Normal file
50
lib/models/items/folder_model.dart
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
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/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'folder_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class FolderModel extends ItemBaseModel with FolderModelMappable {
|
||||
final List<ItemBaseModel> items;
|
||||
|
||||
const FolderModel({
|
||||
required this.items,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.name,
|
||||
required super.id,
|
||||
super.canDownload,
|
||||
super.canDelete,
|
||||
super.jellyType,
|
||||
});
|
||||
|
||||
factory FolderModel.fromBaseDto(BaseItemDto item, Ref ref) {
|
||||
return FolderModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
items: [],
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
);
|
||||
}
|
||||
}
|
||||
242
lib/models/items/folder_model.mapper.dart
Normal file
242
lib/models/items/folder_model.mapper.dart
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'folder_model.dart';
|
||||
|
||||
class FolderModelMapper extends SubClassMapperBase<FolderModel> {
|
||||
FolderModelMapper._();
|
||||
|
||||
static FolderModelMapper? _instance;
|
||||
static FolderModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = FolderModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'FolderModel';
|
||||
|
||||
static List<ItemBaseModel> _$items(FolderModel v) => v.items;
|
||||
static const Field<FolderModel, List<ItemBaseModel>> _f$items =
|
||||
Field('items', _$items);
|
||||
static OverviewModel _$overview(FolderModel v) => v.overview;
|
||||
static const Field<FolderModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(FolderModel v) => v.parentId;
|
||||
static const Field<FolderModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(FolderModel v) => v.playlistId;
|
||||
static const Field<FolderModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(FolderModel v) => v.images;
|
||||
static const Field<FolderModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(FolderModel v) => v.childCount;
|
||||
static const Field<FolderModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(FolderModel v) => v.primaryRatio;
|
||||
static const Field<FolderModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(FolderModel v) => v.userData;
|
||||
static const Field<FolderModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static String _$name(FolderModel v) => v.name;
|
||||
static const Field<FolderModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(FolderModel v) => v.id;
|
||||
static const Field<FolderModel, String> _f$id = Field('id', _$id);
|
||||
static bool? _$canDownload(FolderModel v) => v.canDownload;
|
||||
static const Field<FolderModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload, opt: true);
|
||||
static bool? _$canDelete(FolderModel v) => v.canDelete;
|
||||
static const Field<FolderModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete, opt: true);
|
||||
static BaseItemKind? _$jellyType(FolderModel v) => v.jellyType;
|
||||
static const Field<FolderModel, BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<FolderModel> fields = const {
|
||||
#items: _f$items,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#canDownload: _f$canDownload,
|
||||
#canDelete: _f$canDelete,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'FolderModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static FolderModel _instantiate(DecodingData data) {
|
||||
return FolderModel(
|
||||
items: data.dec(_f$items),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static FolderModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<FolderModel>(map);
|
||||
}
|
||||
|
||||
static FolderModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<FolderModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin FolderModelMappable {
|
||||
String toJson() {
|
||||
return FolderModelMapper.ensureInitialized()
|
||||
.encodeJson<FolderModel>(this as FolderModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return FolderModelMapper.ensureInitialized()
|
||||
.encodeMap<FolderModel>(this as FolderModel);
|
||||
}
|
||||
|
||||
FolderModelCopyWith<FolderModel, FolderModel, FolderModel> get copyWith =>
|
||||
_FolderModelCopyWithImpl(this as FolderModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return FolderModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as FolderModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension FolderModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, FolderModel, $Out> {
|
||||
FolderModelCopyWith<$R, FolderModel, $Out> get $asFolderModel =>
|
||||
$base.as((v, t, t2) => _FolderModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class FolderModelCopyWith<$R, $In extends FolderModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get items;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{List<ItemBaseModel>? items,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
String? name,
|
||||
String? id,
|
||||
bool? canDownload,
|
||||
bool? canDelete,
|
||||
BaseItemKind? jellyType});
|
||||
FolderModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _FolderModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, FolderModel, $Out>
|
||||
implements FolderModelCopyWith<$R, FolderModel, $Out> {
|
||||
_FolderModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<FolderModel> $mapper =
|
||||
FolderModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get items => ListCopyWith(
|
||||
$value.items, (v, t) => v.copyWith.$chain(t), (v) => call(items: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{List<ItemBaseModel>? items,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
String? name,
|
||||
String? id,
|
||||
Object? canDownload = $none,
|
||||
Object? canDelete = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (items != null) #items: items,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
FolderModel $make(CopyWithData data) => FolderModel(
|
||||
items: data.get(#items, or: $value.items),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
FolderModelCopyWith<$R2, FolderModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_FolderModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
286
lib/models/items/images_models.dart
Normal file
286
lib/models/items/images_models.dart
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart' as enums;
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/providers/image_provider.dart';
|
||||
import 'package:fladder/util/jelly_id.dart';
|
||||
|
||||
class ImagesData {
|
||||
final ImageData? primary;
|
||||
final List<ImageData>? backDrop;
|
||||
final ImageData? logo;
|
||||
ImagesData({
|
||||
this.primary,
|
||||
this.backDrop,
|
||||
this.logo,
|
||||
});
|
||||
|
||||
bool get isEmpty {
|
||||
if (primary == null && backDrop == null) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageData? get firstOrNull {
|
||||
return primary ?? backDrop?[0];
|
||||
}
|
||||
|
||||
ImageData? get randomBackDrop => (backDrop?..shuffle())?.firstOrNull ?? primary;
|
||||
|
||||
factory ImagesData.fromBaseItem(
|
||||
dto.BaseItemDto item,
|
||||
Ref ref, {
|
||||
Size backDrop = const Size(2000, 2000),
|
||||
Size logo = const Size(1000, 1000),
|
||||
Size primary = const Size(600, 600),
|
||||
bool getOriginalSize = false,
|
||||
int quality = 90,
|
||||
}) {
|
||||
final newImgesData = ImagesData(
|
||||
primary: item.imageTags?['Primary'] != null
|
||||
? ImageData(
|
||||
path: getOriginalSize
|
||||
? ref.read(imageUtilityProvider).getItemsOrigImageUrl(
|
||||
item.id!,
|
||||
type: enums.ImageType.primary,
|
||||
)
|
||||
: ref.read(imageUtilityProvider).getItemsImageUrl(
|
||||
(item.id!),
|
||||
type: enums.ImageType.primary,
|
||||
maxHeight: primary.height.toInt(),
|
||||
maxWidth: primary.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: item.imageTags?['Primary'],
|
||||
hash: item.imageBlurHashes?.primary?[item.imageTags?['Primary']] as String? ?? "",
|
||||
)
|
||||
: null,
|
||||
logo: item.imageTags?['Logo'] != null
|
||||
? ImageData(
|
||||
path: getOriginalSize
|
||||
? ref.read(imageUtilityProvider).getItemsOrigImageUrl(
|
||||
item.id!,
|
||||
type: enums.ImageType.logo,
|
||||
)
|
||||
: ref.read(imageUtilityProvider).getItemsImageUrl(
|
||||
(item.id!),
|
||||
type: enums.ImageType.logo,
|
||||
maxHeight: logo.height.toInt(),
|
||||
maxWidth: logo.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: item.imageTags?['Logo'],
|
||||
hash: item.imageBlurHashes?.logo?[item.imageTags?['Logo']] as String? ?? "")
|
||||
: null,
|
||||
backDrop: (item.backdropImageTags ?? []).mapIndexed(
|
||||
(index, backdrop) {
|
||||
final image = ImageData(
|
||||
path: getOriginalSize
|
||||
? ref.read(imageUtilityProvider).getBackdropOrigImage(
|
||||
item.id!,
|
||||
index,
|
||||
backdrop,
|
||||
)
|
||||
: ref.read(imageUtilityProvider).getBackdropImage(
|
||||
(item.id!),
|
||||
index,
|
||||
backdrop,
|
||||
maxHeight: backDrop.height.toInt(),
|
||||
maxWidth: backDrop.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: backdrop,
|
||||
hash: item.imageBlurHashes?.backdrop?[backdrop] ?? jellyId,
|
||||
);
|
||||
return image;
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
return newImgesData;
|
||||
}
|
||||
|
||||
static ImagesData? fromBaseItemParent(
|
||||
dto.BaseItemDto item,
|
||||
Ref ref, {
|
||||
Size backDrop = const Size(2000, 2000),
|
||||
Size logo = const Size(1000, 1000),
|
||||
Size primary = const Size(600, 600),
|
||||
int quality = 90,
|
||||
}) {
|
||||
if (item.seriesId == null && item.parentId == null) return null;
|
||||
final newImgesData = ImagesData(
|
||||
primary: (item.seriesPrimaryImageTag != null)
|
||||
? ImageData(
|
||||
path: ref.read(imageUtilityProvider).getItemsImageUrl(
|
||||
(item.seriesId!),
|
||||
type: enums.ImageType.primary,
|
||||
maxHeight: primary.height.toInt(),
|
||||
maxWidth: primary.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: item.seriesPrimaryImageTag ?? "",
|
||||
hash: item.imageBlurHashes?.primary?[item.seriesPrimaryImageTag] as String? ?? "")
|
||||
: null,
|
||||
logo: (item.parentLogoImageTag != null)
|
||||
? ImageData(
|
||||
path: ref.read(imageUtilityProvider).getItemsImageUrl(
|
||||
(item.seriesId!),
|
||||
type: enums.ImageType.logo,
|
||||
maxHeight: logo.height.toInt(),
|
||||
maxWidth: logo.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: item.parentLogoImageTag ?? "",
|
||||
hash: item.imageBlurHashes?.logo?[item.parentLogoImageTag] as String? ?? "")
|
||||
: null,
|
||||
backDrop: (item.backdropImageTags ?? []).mapIndexed(
|
||||
(index, backdrop) {
|
||||
final image = ImageData(
|
||||
path: ref.read(imageUtilityProvider).getBackdropImage(
|
||||
((item.seriesId ?? item.parentId)!),
|
||||
index,
|
||||
backdrop,
|
||||
maxHeight: backDrop.height.toInt(),
|
||||
maxWidth: backDrop.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: backdrop,
|
||||
hash: item.imageBlurHashes?.backdrop?[backdrop],
|
||||
);
|
||||
return image;
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
return newImgesData;
|
||||
}
|
||||
|
||||
static ImagesData? fromPersonDto(
|
||||
dto.BaseItemPerson item,
|
||||
Ref ref, {
|
||||
Size backDrop = const Size(2000, 2000),
|
||||
Size logo = const Size(1000, 1000),
|
||||
Size primary = const Size(2000, 2000),
|
||||
int quality = 90,
|
||||
}) {
|
||||
return ImagesData(
|
||||
primary: (item.primaryImageTag != null && item.imageBlurHashes != null)
|
||||
? ImageData(
|
||||
path: ref.read(imageUtilityProvider).getItemsImageUrl(
|
||||
item.id ?? "",
|
||||
type: enums.ImageType.primary,
|
||||
maxHeight: primary.height.toInt(),
|
||||
maxWidth: primary.width.toInt(),
|
||||
quality: quality,
|
||||
),
|
||||
key: item.primaryImageTag ?? "",
|
||||
hash: item.imageBlurHashes?.primary?[item.primaryImageTag] as String? ?? jellyId)
|
||||
: null,
|
||||
logo: null,
|
||||
backDrop: null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'ImagesData(primary: $primary, backDrop: $backDrop, logo: $logo)';
|
||||
|
||||
ImagesData copyWith({
|
||||
ValueGetter<ImageData?>? primary,
|
||||
ValueGetter<List<ImageData>?>? backDrop,
|
||||
ValueGetter<ImageData?>? logo,
|
||||
}) {
|
||||
return ImagesData(
|
||||
primary: primary != null ? primary() : this.primary,
|
||||
backDrop: backDrop != null ? backDrop() : this.backDrop,
|
||||
logo: logo != null ? logo() : this.logo,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'primary': primary?.toMap(),
|
||||
'backDrop': backDrop?.map((x) => x.toMap()).toList(),
|
||||
'logo': logo?.toMap(),
|
||||
};
|
||||
}
|
||||
|
||||
factory ImagesData.fromMap(Map<String, dynamic> map) {
|
||||
return ImagesData(
|
||||
primary: map['primary'] != null ? ImageData.fromMap(map['primary']) : null,
|
||||
backDrop:
|
||||
map['backDrop'] != null ? List<ImageData>.from(map['backDrop']?.map((x) => ImageData.fromMap(x))) : null,
|
||||
logo: map['logo'] != null ? ImageData.fromMap(map['logo']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory ImagesData.fromJson(String source) => ImagesData.fromMap(json.decode(source));
|
||||
}
|
||||
|
||||
class ImageData {
|
||||
final String path;
|
||||
final String hash;
|
||||
final String key;
|
||||
ImageData({
|
||||
this.path = '',
|
||||
this.hash = '',
|
||||
this.key = '',
|
||||
});
|
||||
|
||||
ImageProvider get imageProvider {
|
||||
if (path.startsWith("http")) {
|
||||
return CachedNetworkImageProvider(
|
||||
cacheKey: key,
|
||||
cacheManager: CustomCacheManager.instance,
|
||||
path,
|
||||
);
|
||||
} else {
|
||||
return Image.file(
|
||||
key: Key(key),
|
||||
File(path),
|
||||
).image;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'ImageData(path: $path, hash: $hash, key: $key)';
|
||||
|
||||
ImageData copyWith({
|
||||
String? path,
|
||||
String? hash,
|
||||
String? key,
|
||||
}) {
|
||||
return ImageData(
|
||||
path: path ?? this.path,
|
||||
hash: hash ?? this.hash,
|
||||
key: key ?? this.key,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'path': path,
|
||||
'hash': hash,
|
||||
'key': key,
|
||||
};
|
||||
}
|
||||
|
||||
factory ImageData.fromMap(Map<String, dynamic> map) {
|
||||
return ImageData(
|
||||
path: map['path'] ?? '',
|
||||
hash: map['hash'] ?? '',
|
||||
key: map['key'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory ImageData.fromJson(String source) => ImageData.fromMap(json.decode(source));
|
||||
}
|
||||
47
lib/models/items/intro_skip_model.dart
Normal file
47
lib/models/items/intro_skip_model.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first, invalid_annotation_target
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'intro_skip_model.freezed.dart';
|
||||
part 'intro_skip_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class IntroOutSkipModel with _$IntroOutSkipModel {
|
||||
const IntroOutSkipModel._();
|
||||
factory IntroOutSkipModel({
|
||||
IntroSkipModel? intro,
|
||||
IntroSkipModel? credits,
|
||||
}) = _IntroOutSkipModel;
|
||||
|
||||
factory IntroOutSkipModel.fromJson(Map<String, dynamic> json) => _$IntroOutSkipModelFromJson(json);
|
||||
|
||||
bool introInRange(Duration position) {
|
||||
if (intro == null) return false;
|
||||
return (position.compareTo(intro!.showTime) >= 0 && position.compareTo(intro!.hideTime) <= 0);
|
||||
}
|
||||
|
||||
bool creditsInRange(Duration position) {
|
||||
if (credits == null) return false;
|
||||
return (position.compareTo(credits!.showTime) >= 0 && position.compareTo(credits!.hideTime) <= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class IntroSkipModel with _$IntroSkipModel {
|
||||
factory IntroSkipModel({
|
||||
@JsonKey(name: "EpisodeId") required String id,
|
||||
@JsonKey(name: "Valid") required bool valid,
|
||||
@JsonKey(name: "IntroStart", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
|
||||
required Duration start,
|
||||
@JsonKey(name: "IntroEnd", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
|
||||
required Duration end,
|
||||
@JsonKey(name: "ShowSkipPromptAt", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
|
||||
required Duration showTime,
|
||||
@JsonKey(name: "HideSkipPromptAt", fromJson: _durationFromMilliseconds, toJson: _durationToMilliseconds)
|
||||
required Duration hideTime,
|
||||
}) = _IntroSkipModel;
|
||||
|
||||
factory IntroSkipModel.fromJson(Map<String, dynamic> json) => _$IntroSkipModelFromJson(json);
|
||||
}
|
||||
|
||||
Duration _durationFromMilliseconds(num milliseconds) => Duration(milliseconds: (milliseconds * 1000).toInt());
|
||||
num _durationToMilliseconds(Duration duration) => duration.inMilliseconds.toDouble() / 1000.0;
|
||||
565
lib/models/items/intro_skip_model.freezed.dart
Normal file
565
lib/models/items/intro_skip_model.freezed.dart
Normal file
|
|
@ -0,0 +1,565 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'intro_skip_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
IntroOutSkipModel _$IntroOutSkipModelFromJson(Map<String, dynamic> json) {
|
||||
return _IntroOutSkipModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$IntroOutSkipModel {
|
||||
IntroSkipModel? get intro => throw _privateConstructorUsedError;
|
||||
IntroSkipModel? get credits => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IntroOutSkipModelCopyWith<IntroOutSkipModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IntroOutSkipModelCopyWith<$Res> {
|
||||
factory $IntroOutSkipModelCopyWith(
|
||||
IntroOutSkipModel value, $Res Function(IntroOutSkipModel) then) =
|
||||
_$IntroOutSkipModelCopyWithImpl<$Res, IntroOutSkipModel>;
|
||||
@useResult
|
||||
$Res call({IntroSkipModel? intro, IntroSkipModel? credits});
|
||||
|
||||
$IntroSkipModelCopyWith<$Res>? get intro;
|
||||
$IntroSkipModelCopyWith<$Res>? get credits;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$IntroOutSkipModelCopyWithImpl<$Res, $Val extends IntroOutSkipModel>
|
||||
implements $IntroOutSkipModelCopyWith<$Res> {
|
||||
_$IntroOutSkipModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? intro = freezed,
|
||||
Object? credits = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
intro: freezed == intro
|
||||
? _value.intro
|
||||
: intro // ignore: cast_nullable_to_non_nullable
|
||||
as IntroSkipModel?,
|
||||
credits: freezed == credits
|
||||
? _value.credits
|
||||
: credits // ignore: cast_nullable_to_non_nullable
|
||||
as IntroSkipModel?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$IntroSkipModelCopyWith<$Res>? get intro {
|
||||
if (_value.intro == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $IntroSkipModelCopyWith<$Res>(_value.intro!, (value) {
|
||||
return _then(_value.copyWith(intro: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$IntroSkipModelCopyWith<$Res>? get credits {
|
||||
if (_value.credits == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $IntroSkipModelCopyWith<$Res>(_value.credits!, (value) {
|
||||
return _then(_value.copyWith(credits: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$IntroOutSkipModelImplCopyWith<$Res>
|
||||
implements $IntroOutSkipModelCopyWith<$Res> {
|
||||
factory _$$IntroOutSkipModelImplCopyWith(_$IntroOutSkipModelImpl value,
|
||||
$Res Function(_$IntroOutSkipModelImpl) then) =
|
||||
__$$IntroOutSkipModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({IntroSkipModel? intro, IntroSkipModel? credits});
|
||||
|
||||
@override
|
||||
$IntroSkipModelCopyWith<$Res>? get intro;
|
||||
@override
|
||||
$IntroSkipModelCopyWith<$Res>? get credits;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$IntroOutSkipModelImplCopyWithImpl<$Res>
|
||||
extends _$IntroOutSkipModelCopyWithImpl<$Res, _$IntroOutSkipModelImpl>
|
||||
implements _$$IntroOutSkipModelImplCopyWith<$Res> {
|
||||
__$$IntroOutSkipModelImplCopyWithImpl(_$IntroOutSkipModelImpl _value,
|
||||
$Res Function(_$IntroOutSkipModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? intro = freezed,
|
||||
Object? credits = freezed,
|
||||
}) {
|
||||
return _then(_$IntroOutSkipModelImpl(
|
||||
intro: freezed == intro
|
||||
? _value.intro
|
||||
: intro // ignore: cast_nullable_to_non_nullable
|
||||
as IntroSkipModel?,
|
||||
credits: freezed == credits
|
||||
? _value.credits
|
||||
: credits // ignore: cast_nullable_to_non_nullable
|
||||
as IntroSkipModel?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$IntroOutSkipModelImpl extends _IntroOutSkipModel {
|
||||
_$IntroOutSkipModelImpl({this.intro, this.credits}) : super._();
|
||||
|
||||
factory _$IntroOutSkipModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$IntroOutSkipModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final IntroSkipModel? intro;
|
||||
@override
|
||||
final IntroSkipModel? credits;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'IntroOutSkipModel(intro: $intro, credits: $credits)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$IntroOutSkipModelImpl &&
|
||||
(identical(other.intro, intro) || other.intro == intro) &&
|
||||
(identical(other.credits, credits) || other.credits == credits));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, intro, credits);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$IntroOutSkipModelImplCopyWith<_$IntroOutSkipModelImpl> get copyWith =>
|
||||
__$$IntroOutSkipModelImplCopyWithImpl<_$IntroOutSkipModelImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$IntroOutSkipModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IntroOutSkipModel extends IntroOutSkipModel {
|
||||
factory _IntroOutSkipModel(
|
||||
{final IntroSkipModel? intro,
|
||||
final IntroSkipModel? credits}) = _$IntroOutSkipModelImpl;
|
||||
_IntroOutSkipModel._() : super._();
|
||||
|
||||
factory _IntroOutSkipModel.fromJson(Map<String, dynamic> json) =
|
||||
_$IntroOutSkipModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
IntroSkipModel? get intro;
|
||||
@override
|
||||
IntroSkipModel? get credits;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$IntroOutSkipModelImplCopyWith<_$IntroOutSkipModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
IntroSkipModel _$IntroSkipModelFromJson(Map<String, dynamic> json) {
|
||||
return _IntroSkipModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$IntroSkipModel {
|
||||
@JsonKey(name: "EpisodeId")
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "Valid")
|
||||
bool get valid => throw _privateConstructorUsedError;
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get start => throw _privateConstructorUsedError;
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get end => throw _privateConstructorUsedError;
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get showTime => throw _privateConstructorUsedError;
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get hideTime => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IntroSkipModelCopyWith<IntroSkipModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IntroSkipModelCopyWith<$Res> {
|
||||
factory $IntroSkipModelCopyWith(
|
||||
IntroSkipModel value, $Res Function(IntroSkipModel) then) =
|
||||
_$IntroSkipModelCopyWithImpl<$Res, IntroSkipModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "EpisodeId") String id,
|
||||
@JsonKey(name: "Valid") bool valid,
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration start,
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration end,
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration showTime,
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration hideTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$IntroSkipModelCopyWithImpl<$Res, $Val extends IntroSkipModel>
|
||||
implements $IntroSkipModelCopyWith<$Res> {
|
||||
_$IntroSkipModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? valid = null,
|
||||
Object? start = null,
|
||||
Object? end = null,
|
||||
Object? showTime = null,
|
||||
Object? hideTime = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
valid: null == valid
|
||||
? _value.valid
|
||||
: valid // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
start: null == start
|
||||
? _value.start
|
||||
: start // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
end: null == end
|
||||
? _value.end
|
||||
: end // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
showTime: null == showTime
|
||||
? _value.showTime
|
||||
: showTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
hideTime: null == hideTime
|
||||
? _value.hideTime
|
||||
: hideTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$IntroSkipModelImplCopyWith<$Res>
|
||||
implements $IntroSkipModelCopyWith<$Res> {
|
||||
factory _$$IntroSkipModelImplCopyWith(_$IntroSkipModelImpl value,
|
||||
$Res Function(_$IntroSkipModelImpl) then) =
|
||||
__$$IntroSkipModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "EpisodeId") String id,
|
||||
@JsonKey(name: "Valid") bool valid,
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration start,
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration end,
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration showTime,
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration hideTime});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$IntroSkipModelImplCopyWithImpl<$Res>
|
||||
extends _$IntroSkipModelCopyWithImpl<$Res, _$IntroSkipModelImpl>
|
||||
implements _$$IntroSkipModelImplCopyWith<$Res> {
|
||||
__$$IntroSkipModelImplCopyWithImpl(
|
||||
_$IntroSkipModelImpl _value, $Res Function(_$IntroSkipModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? valid = null,
|
||||
Object? start = null,
|
||||
Object? end = null,
|
||||
Object? showTime = null,
|
||||
Object? hideTime = null,
|
||||
}) {
|
||||
return _then(_$IntroSkipModelImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
valid: null == valid
|
||||
? _value.valid
|
||||
: valid // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
start: null == start
|
||||
? _value.start
|
||||
: start // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
end: null == end
|
||||
? _value.end
|
||||
: end // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
showTime: null == showTime
|
||||
? _value.showTime
|
||||
: showTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
hideTime: null == hideTime
|
||||
? _value.hideTime
|
||||
: hideTime // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$IntroSkipModelImpl implements _IntroSkipModel {
|
||||
_$IntroSkipModelImpl(
|
||||
{@JsonKey(name: "EpisodeId") required this.id,
|
||||
@JsonKey(name: "Valid") required this.valid,
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required this.start,
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required this.end,
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required this.showTime,
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required this.hideTime});
|
||||
|
||||
factory _$IntroSkipModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$IntroSkipModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: "EpisodeId")
|
||||
final String id;
|
||||
@override
|
||||
@JsonKey(name: "Valid")
|
||||
final bool valid;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
final Duration start;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
final Duration end;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
final Duration showTime;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
final Duration hideTime;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'IntroSkipModel(id: $id, valid: $valid, start: $start, end: $end, showTime: $showTime, hideTime: $hideTime)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$IntroSkipModelImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.valid, valid) || other.valid == valid) &&
|
||||
(identical(other.start, start) || other.start == start) &&
|
||||
(identical(other.end, end) || other.end == end) &&
|
||||
(identical(other.showTime, showTime) ||
|
||||
other.showTime == showTime) &&
|
||||
(identical(other.hideTime, hideTime) ||
|
||||
other.hideTime == hideTime));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, id, valid, start, end, showTime, hideTime);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$IntroSkipModelImplCopyWith<_$IntroSkipModelImpl> get copyWith =>
|
||||
__$$IntroSkipModelImplCopyWithImpl<_$IntroSkipModelImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$IntroSkipModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IntroSkipModel implements IntroSkipModel {
|
||||
factory _IntroSkipModel(
|
||||
{@JsonKey(name: "EpisodeId") required final String id,
|
||||
@JsonKey(name: "Valid") required final bool valid,
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required final Duration start,
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required final Duration end,
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required final Duration showTime,
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
required final Duration hideTime}) = _$IntroSkipModelImpl;
|
||||
|
||||
factory _IntroSkipModel.fromJson(Map<String, dynamic> json) =
|
||||
_$IntroSkipModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: "EpisodeId")
|
||||
String get id;
|
||||
@override
|
||||
@JsonKey(name: "Valid")
|
||||
bool get valid;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "IntroStart",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get start;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "IntroEnd",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get end;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "ShowSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get showTime;
|
||||
@override
|
||||
@JsonKey(
|
||||
name: "HideSkipPromptAt",
|
||||
fromJson: _durationFromMilliseconds,
|
||||
toJson: _durationToMilliseconds)
|
||||
Duration get hideTime;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$IntroSkipModelImplCopyWith<_$IntroSkipModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
46
lib/models/items/intro_skip_model.g.dart
Normal file
46
lib/models/items/intro_skip_model.g.dart
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'intro_skip_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$IntroOutSkipModelImpl _$$IntroOutSkipModelImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$IntroOutSkipModelImpl(
|
||||
intro: json['intro'] == null
|
||||
? null
|
||||
: IntroSkipModel.fromJson(json['intro'] as Map<String, dynamic>),
|
||||
credits: json['credits'] == null
|
||||
? null
|
||||
: IntroSkipModel.fromJson(json['credits'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$IntroOutSkipModelImplToJson(
|
||||
_$IntroOutSkipModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'intro': instance.intro,
|
||||
'credits': instance.credits,
|
||||
};
|
||||
|
||||
_$IntroSkipModelImpl _$$IntroSkipModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$IntroSkipModelImpl(
|
||||
id: json['EpisodeId'] as String,
|
||||
valid: json['Valid'] as bool,
|
||||
start: _durationFromMilliseconds(json['IntroStart'] as num),
|
||||
end: _durationFromMilliseconds(json['IntroEnd'] as num),
|
||||
showTime: _durationFromMilliseconds(json['ShowSkipPromptAt'] as num),
|
||||
hideTime: _durationFromMilliseconds(json['HideSkipPromptAt'] as num),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$IntroSkipModelImplToJson(
|
||||
_$IntroSkipModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'EpisodeId': instance.id,
|
||||
'Valid': instance.valid,
|
||||
'IntroStart': _durationToMilliseconds(instance.start),
|
||||
'IntroEnd': _durationToMilliseconds(instance.end),
|
||||
'ShowSkipPromptAt': _durationToMilliseconds(instance.showTime),
|
||||
'HideSkipPromptAt': _durationToMilliseconds(instance.hideTime),
|
||||
};
|
||||
21
lib/models/items/item_properties_model.dart
Normal file
21
lib/models/items/item_properties_model.dart
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'item_properties_model.freezed.dart';
|
||||
|
||||
@Freezed(fromJson: false, toJson: false)
|
||||
class ItemPropertiesModel with _$ItemPropertiesModel {
|
||||
const ItemPropertiesModel._();
|
||||
|
||||
factory ItemPropertiesModel._internal({
|
||||
required bool canDelete,
|
||||
required bool canDownload,
|
||||
}) = _ItemPropertiesModel;
|
||||
|
||||
factory ItemPropertiesModel.fromBaseDto(dto.BaseItemDto dtoItem) {
|
||||
return ItemPropertiesModel._internal(
|
||||
canDelete: dtoItem.canDelete ?? false,
|
||||
canDownload: dtoItem.canDownload ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
156
lib/models/items/item_properties_model.freezed.dart
Normal file
156
lib/models/items/item_properties_model.freezed.dart
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'item_properties_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ItemPropertiesModel {
|
||||
bool get canDelete => throw _privateConstructorUsedError;
|
||||
bool get canDownload => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ItemPropertiesModelCopyWith<ItemPropertiesModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ItemPropertiesModelCopyWith<$Res> {
|
||||
factory $ItemPropertiesModelCopyWith(
|
||||
ItemPropertiesModel value, $Res Function(ItemPropertiesModel) then) =
|
||||
_$ItemPropertiesModelCopyWithImpl<$Res, ItemPropertiesModel>;
|
||||
@useResult
|
||||
$Res call({bool canDelete, bool canDownload});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ItemPropertiesModelCopyWithImpl<$Res, $Val extends ItemPropertiesModel>
|
||||
implements $ItemPropertiesModelCopyWith<$Res> {
|
||||
_$ItemPropertiesModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? canDelete = null,
|
||||
Object? canDownload = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
canDelete: null == canDelete
|
||||
? _value.canDelete
|
||||
: canDelete // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
canDownload: null == canDownload
|
||||
? _value.canDownload
|
||||
: canDownload // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ItemPropertiesModelImplCopyWith<$Res>
|
||||
implements $ItemPropertiesModelCopyWith<$Res> {
|
||||
factory _$$ItemPropertiesModelImplCopyWith(_$ItemPropertiesModelImpl value,
|
||||
$Res Function(_$ItemPropertiesModelImpl) then) =
|
||||
__$$ItemPropertiesModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool canDelete, bool canDownload});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ItemPropertiesModelImplCopyWithImpl<$Res>
|
||||
extends _$ItemPropertiesModelCopyWithImpl<$Res, _$ItemPropertiesModelImpl>
|
||||
implements _$$ItemPropertiesModelImplCopyWith<$Res> {
|
||||
__$$ItemPropertiesModelImplCopyWithImpl(_$ItemPropertiesModelImpl _value,
|
||||
$Res Function(_$ItemPropertiesModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? canDelete = null,
|
||||
Object? canDownload = null,
|
||||
}) {
|
||||
return _then(_$ItemPropertiesModelImpl(
|
||||
canDelete: null == canDelete
|
||||
? _value.canDelete
|
||||
: canDelete // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
canDownload: null == canDownload
|
||||
? _value.canDownload
|
||||
: canDownload // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ItemPropertiesModelImpl extends _ItemPropertiesModel {
|
||||
_$ItemPropertiesModelImpl(
|
||||
{required this.canDelete, required this.canDownload})
|
||||
: super._();
|
||||
|
||||
@override
|
||||
final bool canDelete;
|
||||
@override
|
||||
final bool canDownload;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ItemPropertiesModel._internal(canDelete: $canDelete, canDownload: $canDownload)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ItemPropertiesModelImpl &&
|
||||
(identical(other.canDelete, canDelete) ||
|
||||
other.canDelete == canDelete) &&
|
||||
(identical(other.canDownload, canDownload) ||
|
||||
other.canDownload == canDownload));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, canDelete, canDownload);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith =>
|
||||
__$$ItemPropertiesModelImplCopyWithImpl<_$ItemPropertiesModelImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ItemPropertiesModel extends ItemPropertiesModel {
|
||||
factory _ItemPropertiesModel(
|
||||
{required final bool canDelete,
|
||||
required final bool canDownload}) = _$ItemPropertiesModelImpl;
|
||||
_ItemPropertiesModel._() : super._();
|
||||
|
||||
@override
|
||||
bool get canDelete;
|
||||
@override
|
||||
bool get canDownload;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ItemPropertiesModelImplCopyWith<_$ItemPropertiesModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
380
lib/models/items/item_shared_models.dart
Normal file
380
lib/models/items/item_shared_models.dart
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'item_shared_models.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class UserData with UserDataMappable {
|
||||
final bool isFavourite;
|
||||
final int playCount;
|
||||
final int? unPlayedItemCount;
|
||||
final int playbackPositionTicks;
|
||||
final double progress;
|
||||
final bool played;
|
||||
final DateTime? lastPlayed;
|
||||
const UserData({
|
||||
this.isFavourite = false,
|
||||
this.playCount = 0,
|
||||
this.unPlayedItemCount,
|
||||
this.playbackPositionTicks = 0,
|
||||
this.progress = 0,
|
||||
this.lastPlayed,
|
||||
this.played = false,
|
||||
});
|
||||
|
||||
factory UserData.fromDto(dto.UserItemDataDto? dto) {
|
||||
if (dto == null) {
|
||||
return UserData();
|
||||
}
|
||||
return UserData(
|
||||
isFavourite: dto.isFavorite ?? false,
|
||||
playCount: dto.playCount ?? 0,
|
||||
playbackPositionTicks: dto.playbackPositionTicks ?? 0,
|
||||
played: dto.played ?? false,
|
||||
unPlayedItemCount: dto.unplayedItemCount ?? 0,
|
||||
lastPlayed: dto.lastPlayedDate,
|
||||
progress: dto.playedPercentage ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
|
||||
|
||||
factory UserData.fromMap(Map<String, dynamic> map) => UserDataMapper.fromMap(map);
|
||||
factory UserData.fromJson(String json) => UserDataMapper.fromJson(json);
|
||||
}
|
||||
|
||||
class UserDataJsonSerializer extends JsonConverter<UserData, String> {
|
||||
const UserDataJsonSerializer();
|
||||
|
||||
@override
|
||||
UserData fromJson(String json) {
|
||||
return UserData.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson(UserData object) {
|
||||
return object.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
enum EditorLockedFields {
|
||||
name("Name"),
|
||||
overView("Overview"),
|
||||
genres("Genres"),
|
||||
officialRating("OfficialRating"),
|
||||
cast("Cast"),
|
||||
productionLocations("ProductionLocations"),
|
||||
runTime("Runtime"),
|
||||
studios("Studios"),
|
||||
tags("Tags"),
|
||||
;
|
||||
|
||||
const EditorLockedFields(this.value);
|
||||
|
||||
static Map<EditorLockedFields, bool> enabled(List<String> fromStrings) => Map.fromEntries(
|
||||
EditorLockedFields.values.map(
|
||||
(e) => MapEntry(e, fromStrings.contains(e.value)),
|
||||
),
|
||||
);
|
||||
|
||||
final String value;
|
||||
}
|
||||
|
||||
enum DisplayOrder {
|
||||
empty(""),
|
||||
aired("aired"),
|
||||
originalAirDate("originalAirDate"),
|
||||
absolute("absolute"),
|
||||
dvd("dvd"),
|
||||
digital("digital"),
|
||||
storyArc("storyArc"),
|
||||
production("production"),
|
||||
tv("tv"),
|
||||
;
|
||||
|
||||
const DisplayOrder(this.value);
|
||||
|
||||
static DisplayOrder? fromMap(String? value) {
|
||||
return DisplayOrder.values.firstWhereOrNull((element) => element.value == value) ?? DisplayOrder.empty;
|
||||
}
|
||||
|
||||
final String value;
|
||||
}
|
||||
|
||||
enum ShowStatus {
|
||||
empty(""),
|
||||
ended("Ended"),
|
||||
continuing("Continuing");
|
||||
|
||||
const ShowStatus(this.value);
|
||||
|
||||
static ShowStatus? fromMap(String? value) {
|
||||
return ShowStatus.values.firstWhereOrNull((element) => element.value == value) ?? ShowStatus.empty;
|
||||
}
|
||||
|
||||
final String value;
|
||||
}
|
||||
|
||||
class ExternalUrls {
|
||||
final String name;
|
||||
final String url;
|
||||
ExternalUrls({
|
||||
required this.name,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
static List<ExternalUrls> fromDto(List<dto.ExternalUrl> dto) {
|
||||
return dto.map((e) => ExternalUrls(name: e.name ?? "", url: e.url ?? "")).toList();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'Name': name,
|
||||
'Url': url,
|
||||
};
|
||||
}
|
||||
|
||||
factory ExternalUrls.fromMap(Map<String, dynamic> map) {
|
||||
return ExternalUrls(
|
||||
name: map['Name'] ?? '',
|
||||
url: map['Url'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory ExternalUrls.fromJson(String source) => ExternalUrls.fromMap(json.decode(source));
|
||||
}
|
||||
|
||||
class GenreItems {
|
||||
final String id;
|
||||
final String name;
|
||||
GenreItems({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() => 'GenreItems(id: $id, name: $name)';
|
||||
}
|
||||
|
||||
class Person {
|
||||
final String id;
|
||||
final String name;
|
||||
final ImageData? image;
|
||||
final String role;
|
||||
final PersonKind? type;
|
||||
Person({
|
||||
required this.id,
|
||||
this.name = "",
|
||||
this.image,
|
||||
this.role = "",
|
||||
this.type,
|
||||
});
|
||||
|
||||
static Person fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return Person(
|
||||
id: item.id ?? "",
|
||||
name: item.name ?? "",
|
||||
image: ImagesData.fromBaseItem(item, ref).primary,
|
||||
);
|
||||
}
|
||||
|
||||
static Person fromBasePerson(dto.BaseItemPerson person, Ref ref) {
|
||||
return Person(
|
||||
id: person.id ?? "",
|
||||
name: person.name ?? "",
|
||||
image: ImagesData.fromPersonDto(person, ref)?.primary,
|
||||
role: person.role ?? "",
|
||||
type: person.type);
|
||||
}
|
||||
|
||||
dto.BaseItemPerson toPerson() {
|
||||
return dto.BaseItemPerson(
|
||||
id: id,
|
||||
name: name,
|
||||
type: type,
|
||||
role: role,
|
||||
);
|
||||
}
|
||||
|
||||
static List<Person> peopleFromDto(List<dto.BaseItemPerson>? people, Ref ref) {
|
||||
return people
|
||||
?.mapIndexed(
|
||||
(index, person) => fromBasePerson(person, ref),
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'People(id: $id, name: $name, imageUrl: $image, role: $role, type: $type)';
|
||||
}
|
||||
}
|
||||
|
||||
class Studio {
|
||||
final String id;
|
||||
final String name;
|
||||
Studio({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
Studio copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
ValueGetter<String?>? image,
|
||||
}) {
|
||||
return Studio(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'Studio(name: $name, id: $id)';
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
|
||||
factory Studio.fromMap(Map<String, dynamic> map) {
|
||||
return Studio(
|
||||
id: map['id'] ?? map['Id'] ?? '',
|
||||
name: map['name'] ?? map['Name'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Studio.fromJson(String source) => Studio.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is Studio && other.id == id && other.name == name;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode ^ name.hashCode;
|
||||
}
|
||||
|
||||
// class UserData {
|
||||
// final bool isFavourite;
|
||||
// final int playCount;
|
||||
// final int? unPlayedItemCount;
|
||||
// final int playbackPositionTicks;
|
||||
// final double progress;
|
||||
// final bool played;
|
||||
// UserData({
|
||||
// this.isFavourite = false,
|
||||
// this.playCount = 0,
|
||||
// this.unPlayedItemCount,
|
||||
// this.playbackPositionTicks = 0,
|
||||
// this.progress = 0,
|
||||
// this.played = false,
|
||||
// });
|
||||
|
||||
// factory UserData.fromDto(dto.UserItemDataDto? dto) {
|
||||
// if (dto == null) {
|
||||
// return UserData();
|
||||
// }
|
||||
// return UserData(
|
||||
// isFavourite: dto.isFavorite ?? false,
|
||||
// playCount: dto.playCount ?? 0,
|
||||
// playbackPositionTicks: dto.playbackPositionTicks ?? 0,
|
||||
// played: dto.played ?? false,
|
||||
// unPlayedItemCount: dto.unplayedItemCount ?? 0,
|
||||
// progress: dto.playedPercentage ?? 0,
|
||||
// );
|
||||
// }
|
||||
|
||||
// Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
|
||||
|
||||
// @override
|
||||
// String toString() {
|
||||
// return 'UserData(isFavourite: $isFavourite, playCount: $playCount, unPlayedItemCount: $unPlayedItemCount, playbackPositionTicks: $playbackPositionTicks, progress: $progress, played: $played)';
|
||||
// }
|
||||
|
||||
// UserData copyWith({
|
||||
// bool? isFavourite,
|
||||
// int? playCount,
|
||||
// int? unPlayedItemCount,
|
||||
// int? playbackPositionTicks,
|
||||
// double? progress,
|
||||
// bool? played,
|
||||
// }) {
|
||||
// return UserData(
|
||||
// isFavourite: isFavourite ?? this.isFavourite,
|
||||
// playCount: playCount ?? this.playCount,
|
||||
// unPlayedItemCount: unPlayedItemCount ?? this.unPlayedItemCount,
|
||||
// playbackPositionTicks: playbackPositionTicks ?? this.playbackPositionTicks,
|
||||
// progress: progress ?? this.progress,
|
||||
// played: played ?? this.played,
|
||||
// );
|
||||
// }
|
||||
|
||||
// Map<String, dynamic> toMap() {
|
||||
// return <String, dynamic>{
|
||||
// 'isFavourite': isFavourite,
|
||||
// 'playCount': playCount,
|
||||
// 'unPlayedItemCount': unPlayedItemCount,
|
||||
// 'playbackPositionTicks': playbackPositionTicks,
|
||||
// 'progress': progress,
|
||||
// 'played': played,
|
||||
// };
|
||||
// }
|
||||
|
||||
// factory UserData.fromMap(Map<String, dynamic> map) {
|
||||
// return UserData(
|
||||
// isFavourite: (map['isFavourite'] ?? false) as bool,
|
||||
// playCount: (map['playCount'] ?? 0) as int,
|
||||
// unPlayedItemCount: (map['unPlayedItemCount'] ?? 0) as int,
|
||||
// playbackPositionTicks: (map['playbackPositionTicks'] ?? 0) as int,
|
||||
// progress: (map['progress'] ?? 0.0) as double,
|
||||
// played: (map['played'] ?? false) as bool,
|
||||
// );
|
||||
// }
|
||||
|
||||
// String toJson() => json.encode(toMap());
|
||||
|
||||
// factory UserData.fromJson(String source) => UserData.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
// @override
|
||||
// bool operator ==(covariant UserData other) {
|
||||
// if (identical(this, other)) return true;
|
||||
|
||||
// return other.isFavourite == isFavourite &&
|
||||
// other.playCount == playCount &&
|
||||
// other.unPlayedItemCount == unPlayedItemCount &&
|
||||
// other.playbackPositionTicks == playbackPositionTicks &&
|
||||
// other.progress == progress &&
|
||||
// other.played == played;
|
||||
// }
|
||||
|
||||
// @override
|
||||
// int get hashCode {
|
||||
// return isFavourite.hashCode ^
|
||||
// playCount.hashCode ^
|
||||
// unPlayedItemCount.hashCode ^
|
||||
// playbackPositionTicks.hashCode ^
|
||||
// progress.hashCode ^
|
||||
// played.hashCode;
|
||||
// }
|
||||
// }
|
||||
162
lib/models/items/item_shared_models.mapper.dart
Normal file
162
lib/models/items/item_shared_models.mapper.dart
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'item_shared_models.dart';
|
||||
|
||||
class UserDataMapper extends ClassMapperBase<UserData> {
|
||||
UserDataMapper._();
|
||||
|
||||
static UserDataMapper? _instance;
|
||||
static UserDataMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = UserDataMapper._());
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'UserData';
|
||||
|
||||
static bool _$isFavourite(UserData v) => v.isFavourite;
|
||||
static const Field<UserData, bool> _f$isFavourite =
|
||||
Field('isFavourite', _$isFavourite, opt: true, def: false);
|
||||
static int _$playCount(UserData v) => v.playCount;
|
||||
static const Field<UserData, int> _f$playCount =
|
||||
Field('playCount', _$playCount, opt: true, def: 0);
|
||||
static int? _$unPlayedItemCount(UserData v) => v.unPlayedItemCount;
|
||||
static const Field<UserData, int> _f$unPlayedItemCount =
|
||||
Field('unPlayedItemCount', _$unPlayedItemCount, opt: true);
|
||||
static int _$playbackPositionTicks(UserData v) => v.playbackPositionTicks;
|
||||
static const Field<UserData, int> _f$playbackPositionTicks = Field(
|
||||
'playbackPositionTicks', _$playbackPositionTicks,
|
||||
opt: true, def: 0);
|
||||
static double _$progress(UserData v) => v.progress;
|
||||
static const Field<UserData, double> _f$progress =
|
||||
Field('progress', _$progress, opt: true, def: 0);
|
||||
static DateTime? _$lastPlayed(UserData v) => v.lastPlayed;
|
||||
static const Field<UserData, DateTime> _f$lastPlayed =
|
||||
Field('lastPlayed', _$lastPlayed, opt: true);
|
||||
static bool _$played(UserData v) => v.played;
|
||||
static const Field<UserData, bool> _f$played =
|
||||
Field('played', _$played, opt: true, def: false);
|
||||
|
||||
@override
|
||||
final MappableFields<UserData> fields = const {
|
||||
#isFavourite: _f$isFavourite,
|
||||
#playCount: _f$playCount,
|
||||
#unPlayedItemCount: _f$unPlayedItemCount,
|
||||
#playbackPositionTicks: _f$playbackPositionTicks,
|
||||
#progress: _f$progress,
|
||||
#lastPlayed: _f$lastPlayed,
|
||||
#played: _f$played,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
static UserData _instantiate(DecodingData data) {
|
||||
return UserData(
|
||||
isFavourite: data.dec(_f$isFavourite),
|
||||
playCount: data.dec(_f$playCount),
|
||||
unPlayedItemCount: data.dec(_f$unPlayedItemCount),
|
||||
playbackPositionTicks: data.dec(_f$playbackPositionTicks),
|
||||
progress: data.dec(_f$progress),
|
||||
lastPlayed: data.dec(_f$lastPlayed),
|
||||
played: data.dec(_f$played));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static UserData fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<UserData>(map);
|
||||
}
|
||||
|
||||
static UserData fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<UserData>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin UserDataMappable {
|
||||
String toJson() {
|
||||
return UserDataMapper.ensureInitialized()
|
||||
.encodeJson<UserData>(this as UserData);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return UserDataMapper.ensureInitialized()
|
||||
.encodeMap<UserData>(this as UserData);
|
||||
}
|
||||
|
||||
UserDataCopyWith<UserData, UserData, UserData> get copyWith =>
|
||||
_UserDataCopyWithImpl(this as UserData, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return UserDataMapper.ensureInitialized().stringifyValue(this as UserData);
|
||||
}
|
||||
}
|
||||
|
||||
extension UserDataValueCopy<$R, $Out> on ObjectCopyWith<$R, UserData, $Out> {
|
||||
UserDataCopyWith<$R, UserData, $Out> get $asUserData =>
|
||||
$base.as((v, t, t2) => _UserDataCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class UserDataCopyWith<$R, $In extends UserData, $Out>
|
||||
implements ClassCopyWith<$R, $In, $Out> {
|
||||
$R call(
|
||||
{bool? isFavourite,
|
||||
int? playCount,
|
||||
int? unPlayedItemCount,
|
||||
int? playbackPositionTicks,
|
||||
double? progress,
|
||||
DateTime? lastPlayed,
|
||||
bool? played});
|
||||
UserDataCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _UserDataCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, UserData, $Out>
|
||||
implements UserDataCopyWith<$R, UserData, $Out> {
|
||||
_UserDataCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<UserData> $mapper =
|
||||
UserDataMapper.ensureInitialized();
|
||||
@override
|
||||
$R call(
|
||||
{bool? isFavourite,
|
||||
int? playCount,
|
||||
Object? unPlayedItemCount = $none,
|
||||
int? playbackPositionTicks,
|
||||
double? progress,
|
||||
Object? lastPlayed = $none,
|
||||
bool? played}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (isFavourite != null) #isFavourite: isFavourite,
|
||||
if (playCount != null) #playCount: playCount,
|
||||
if (unPlayedItemCount != $none) #unPlayedItemCount: unPlayedItemCount,
|
||||
if (playbackPositionTicks != null)
|
||||
#playbackPositionTicks: playbackPositionTicks,
|
||||
if (progress != null) #progress: progress,
|
||||
if (lastPlayed != $none) #lastPlayed: lastPlayed,
|
||||
if (played != null) #played: played
|
||||
}));
|
||||
@override
|
||||
UserData $make(CopyWithData data) => UserData(
|
||||
isFavourite: data.get(#isFavourite, or: $value.isFavourite),
|
||||
playCount: data.get(#playCount, or: $value.playCount),
|
||||
unPlayedItemCount:
|
||||
data.get(#unPlayedItemCount, or: $value.unPlayedItemCount),
|
||||
playbackPositionTicks:
|
||||
data.get(#playbackPositionTicks, or: $value.playbackPositionTicks),
|
||||
progress: data.get(#progress, or: $value.progress),
|
||||
lastPlayed: data.get(#lastPlayed, or: $value.lastPlayed),
|
||||
played: data.get(#played, or: $value.played));
|
||||
|
||||
@override
|
||||
UserDataCopyWith<$R2, UserData, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_UserDataCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
67
lib/models/items/item_stream_model.dart
Normal file
67
lib/models/items/item_stream_model.dart
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'item_stream_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class ItemStreamModel extends ItemBaseModel with ItemStreamModelMappable {
|
||||
final ImagesData? parentImages;
|
||||
final MediaStreamsModel mediaStreams;
|
||||
const ItemStreamModel({
|
||||
required this.parentImages,
|
||||
required this.mediaStreams,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.canDelete,
|
||||
required super.canDownload,
|
||||
super.jellyType,
|
||||
});
|
||||
factory ItemStreamModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return switch (item.type) {
|
||||
BaseItemKind.episode => EpisodeModel.fromBaseDto(item, ref),
|
||||
BaseItemKind.movie => MovieModel.fromBaseDto(item, ref),
|
||||
_ => ItemStreamModel._fromBaseDto(item, ref)
|
||||
};
|
||||
}
|
||||
|
||||
factory ItemStreamModel._fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return ItemStreamModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
parentImages: ImagesData.fromBaseItemParent(item, ref),
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
mediaStreams:
|
||||
MediaStreamsModel.fromMediaStreamsList(item.mediaSources?.firstOrNull, item.mediaStreams ?? [], ref),
|
||||
);
|
||||
}
|
||||
|
||||
String? get videoPropertiesLabel {
|
||||
if (mediaStreams.displayProfile == null && mediaStreams.resolution == null) return null;
|
||||
return "${mediaStreams.displayProfile?.value}";
|
||||
}
|
||||
}
|
||||
245
lib/models/items/item_stream_model.mapper.dart
Normal file
245
lib/models/items/item_stream_model.mapper.dart
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'item_stream_model.dart';
|
||||
|
||||
class ItemStreamModelMapper extends SubClassMapperBase<ItemStreamModel> {
|
||||
ItemStreamModelMapper._();
|
||||
|
||||
static ItemStreamModelMapper? _instance;
|
||||
static ItemStreamModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = ItemStreamModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'ItemStreamModel';
|
||||
|
||||
static ImagesData? _$parentImages(ItemStreamModel v) => v.parentImages;
|
||||
static const Field<ItemStreamModel, ImagesData> _f$parentImages =
|
||||
Field('parentImages', _$parentImages);
|
||||
static MediaStreamsModel _$mediaStreams(ItemStreamModel v) => v.mediaStreams;
|
||||
static const Field<ItemStreamModel, MediaStreamsModel> _f$mediaStreams =
|
||||
Field('mediaStreams', _$mediaStreams);
|
||||
static String _$name(ItemStreamModel v) => v.name;
|
||||
static const Field<ItemStreamModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(ItemStreamModel v) => v.id;
|
||||
static const Field<ItemStreamModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(ItemStreamModel v) => v.overview;
|
||||
static const Field<ItemStreamModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(ItemStreamModel v) => v.parentId;
|
||||
static const Field<ItemStreamModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(ItemStreamModel v) => v.playlistId;
|
||||
static const Field<ItemStreamModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(ItemStreamModel v) => v.images;
|
||||
static const Field<ItemStreamModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(ItemStreamModel v) => v.childCount;
|
||||
static const Field<ItemStreamModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(ItemStreamModel v) => v.primaryRatio;
|
||||
static const Field<ItemStreamModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(ItemStreamModel v) => v.userData;
|
||||
static const Field<ItemStreamModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDelete(ItemStreamModel v) => v.canDelete;
|
||||
static const Field<ItemStreamModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete);
|
||||
static bool? _$canDownload(ItemStreamModel v) => v.canDownload;
|
||||
static const Field<ItemStreamModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload);
|
||||
static dto.BaseItemKind? _$jellyType(ItemStreamModel v) => v.jellyType;
|
||||
static const Field<ItemStreamModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<ItemStreamModel> fields = const {
|
||||
#parentImages: _f$parentImages,
|
||||
#mediaStreams: _f$mediaStreams,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDelete: _f$canDelete,
|
||||
#canDownload: _f$canDownload,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'ItemStreamModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static ItemStreamModel _instantiate(DecodingData data) {
|
||||
return ItemStreamModel(
|
||||
parentImages: data.dec(_f$parentImages),
|
||||
mediaStreams: data.dec(_f$mediaStreams),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static ItemStreamModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<ItemStreamModel>(map);
|
||||
}
|
||||
|
||||
static ItemStreamModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<ItemStreamModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin ItemStreamModelMappable {
|
||||
String toJson() {
|
||||
return ItemStreamModelMapper.ensureInitialized()
|
||||
.encodeJson<ItemStreamModel>(this as ItemStreamModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return ItemStreamModelMapper.ensureInitialized()
|
||||
.encodeMap<ItemStreamModel>(this as ItemStreamModel);
|
||||
}
|
||||
|
||||
ItemStreamModelCopyWith<ItemStreamModel, ItemStreamModel, ItemStreamModel>
|
||||
get copyWith => _ItemStreamModelCopyWithImpl(
|
||||
this as ItemStreamModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return ItemStreamModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as ItemStreamModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemStreamModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, ItemStreamModel, $Out> {
|
||||
ItemStreamModelCopyWith<$R, ItemStreamModel, $Out> get $asItemStreamModel =>
|
||||
$base.as((v, t, t2) => _ItemStreamModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class ItemStreamModelCopyWith<$R, $In extends ItemStreamModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{ImagesData? parentImages,
|
||||
MediaStreamsModel? mediaStreams,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDelete,
|
||||
bool? canDownload,
|
||||
dto.BaseItemKind? jellyType});
|
||||
ItemStreamModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _ItemStreamModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, ItemStreamModel, $Out>
|
||||
implements ItemStreamModelCopyWith<$R, ItemStreamModel, $Out> {
|
||||
_ItemStreamModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<ItemStreamModel> $mapper =
|
||||
ItemStreamModelMapper.ensureInitialized();
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? parentImages = $none,
|
||||
MediaStreamsModel? mediaStreams,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDelete = $none,
|
||||
Object? canDownload = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (parentImages != $none) #parentImages: parentImages,
|
||||
if (mediaStreams != null) #mediaStreams: mediaStreams,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
ItemStreamModel $make(CopyWithData data) => ItemStreamModel(
|
||||
parentImages: data.get(#parentImages, or: $value.parentImages),
|
||||
mediaStreams: data.get(#mediaStreams, or: $value.mediaStreams),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
ItemStreamModelCopyWith<$R2, ItemStreamModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_ItemStreamModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
372
lib/models/items/media_streams_model.dart
Normal file
372
lib/models/items/media_streams_model.dart
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/util/video_properties.dart';
|
||||
|
||||
class MediaStreamsModel {
|
||||
final int? defaultAudioStreamIndex;
|
||||
final int? defaultSubStreamIndex;
|
||||
final List<VideoStreamModel> videoStreams;
|
||||
final List<AudioStreamModel> audioStreams;
|
||||
final List<SubStreamModel> subStreams;
|
||||
MediaStreamsModel({
|
||||
this.defaultAudioStreamIndex,
|
||||
this.defaultSubStreamIndex,
|
||||
required this.videoStreams,
|
||||
required this.audioStreams,
|
||||
required this.subStreams,
|
||||
});
|
||||
|
||||
bool get isNull {
|
||||
return defaultAudioStreamIndex == null ||
|
||||
defaultSubStreamIndex == null ||
|
||||
audioStreams.isEmpty ||
|
||||
subStreams.isEmpty;
|
||||
}
|
||||
|
||||
bool get isNotEmpty {
|
||||
return audioStreams.isNotEmpty && subStreams.isNotEmpty;
|
||||
}
|
||||
|
||||
AudioStreamModel? get currentAudioStream {
|
||||
if (defaultAudioStreamIndex == -1) {
|
||||
return AudioStreamModel.no();
|
||||
}
|
||||
return audioStreams.firstWhereOrNull((element) => element.index == defaultAudioStreamIndex) ??
|
||||
audioStreams.firstOrNull;
|
||||
}
|
||||
|
||||
SubStreamModel? get currentSubStream {
|
||||
if (defaultSubStreamIndex == -1) {
|
||||
return SubStreamModel.no();
|
||||
}
|
||||
return subStreams.firstWhereOrNull((element) => element.index == defaultSubStreamIndex) ?? subStreams.firstOrNull;
|
||||
}
|
||||
|
||||
DisplayProfile? get displayProfile {
|
||||
return DisplayProfile.fromVideoStreams(videoStreams);
|
||||
}
|
||||
|
||||
Resolution? get resolution {
|
||||
return Resolution.fromVideoStream(videoStreams.firstOrNull);
|
||||
}
|
||||
|
||||
String? get resolutionText {
|
||||
final stream = videoStreams.firstOrNull;
|
||||
if (stream == null) return null;
|
||||
return "${stream.width}x${stream.height}";
|
||||
}
|
||||
|
||||
Widget? audioIcon(
|
||||
BuildContext context,
|
||||
Function()? onTap,
|
||||
) {
|
||||
final audioStream = audioStreams.firstWhereOrNull((element) => element.isDefault) ?? audioStreams.firstOrNull;
|
||||
if (audioStream == null) return null;
|
||||
return DefaultVideoInformationBox(
|
||||
onTap: onTap,
|
||||
child: Text(
|
||||
audioStream.title,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget subtitleIcon(
|
||||
BuildContext context,
|
||||
Function()? onTap,
|
||||
) {
|
||||
return DefaultVideoInformationBox(
|
||||
onTap: onTap,
|
||||
child: Icon(
|
||||
subStreams.isNotEmpty ? Icons.subtitles_rounded : Icons.subtitles_off_outlined,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static MediaStreamsModel fromMediaStreamsList(
|
||||
dto.MediaSourceInfo? mediaSource, List<dto.MediaStream> streams, Ref ref) {
|
||||
return MediaStreamsModel(
|
||||
defaultAudioStreamIndex: mediaSource?.defaultAudioStreamIndex,
|
||||
defaultSubStreamIndex: mediaSource?.defaultSubtitleStreamIndex,
|
||||
videoStreams: streams
|
||||
.where((element) => element.type == dto.MediaStreamType.video)
|
||||
.map(
|
||||
(e) => VideoStreamModel.fromMediaStream(e),
|
||||
)
|
||||
.sortByExternal(),
|
||||
audioStreams: streams
|
||||
.where((element) => element.type == dto.MediaStreamType.audio)
|
||||
.map(
|
||||
(e) => AudioStreamModel.fromMediaStream(e),
|
||||
)
|
||||
.sortByExternal(),
|
||||
subStreams: streams
|
||||
.where((element) => element.type == dto.MediaStreamType.subtitle)
|
||||
.map(
|
||||
(sub) => SubStreamModel.fromMediaStream(sub, ref),
|
||||
)
|
||||
.sortByExternal(),
|
||||
);
|
||||
}
|
||||
|
||||
MediaStreamsModel copyWith({
|
||||
int? defaultAudioStreamIndex,
|
||||
int? defaultSubStreamIndex,
|
||||
List<VideoStreamModel>? videoStreams,
|
||||
List<AudioStreamModel>? audioStreams,
|
||||
List<SubStreamModel>? subStreams,
|
||||
}) {
|
||||
return MediaStreamsModel(
|
||||
defaultAudioStreamIndex: defaultAudioStreamIndex ?? this.defaultAudioStreamIndex,
|
||||
defaultSubStreamIndex: defaultSubStreamIndex ?? this.defaultSubStreamIndex,
|
||||
videoStreams: videoStreams ?? this.videoStreams,
|
||||
audioStreams: audioStreams ?? this.audioStreams,
|
||||
subStreams: subStreams ?? this.subStreams,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'MediaStreamsModel(defaultAudioStreamIndex: $defaultAudioStreamIndex, defaultSubStreamIndex: $defaultSubStreamIndex, videoStreams: $videoStreams, audioStreams: $audioStreams, subStreams: $subStreams)';
|
||||
}
|
||||
}
|
||||
|
||||
class StreamModel {
|
||||
final String name;
|
||||
final String codec;
|
||||
final bool isDefault;
|
||||
final bool isExternal;
|
||||
final int index;
|
||||
StreamModel({
|
||||
required this.name,
|
||||
required this.codec,
|
||||
required this.isDefault,
|
||||
required this.isExternal,
|
||||
required this.index,
|
||||
});
|
||||
}
|
||||
|
||||
class VideoStreamModel extends StreamModel {
|
||||
final int width;
|
||||
final int height;
|
||||
final double frameRate;
|
||||
final String? videoDoViTitle;
|
||||
final VideoRangeType? videoRangeType;
|
||||
VideoStreamModel({
|
||||
required super.name,
|
||||
required super.codec,
|
||||
required super.isDefault,
|
||||
required super.isExternal,
|
||||
required super.index,
|
||||
required this.videoDoViTitle,
|
||||
required this.videoRangeType,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.frameRate,
|
||||
});
|
||||
|
||||
factory VideoStreamModel.fromMediaStream(dto.MediaStream stream) {
|
||||
return VideoStreamModel(
|
||||
name: stream.title ?? "",
|
||||
isDefault: stream.isDefault ?? false,
|
||||
codec: stream.codec ?? "",
|
||||
videoDoViTitle: stream.videoDoViTitle,
|
||||
videoRangeType: stream.videoRangeType,
|
||||
width: stream.width ?? 0,
|
||||
height: stream.height ?? 0,
|
||||
frameRate: stream.realFrameRate ?? 24,
|
||||
isExternal: stream.isExternal ?? false,
|
||||
index: stream.index ?? -1,
|
||||
);
|
||||
}
|
||||
String get prettyName {
|
||||
return "${Resolution.fromVideoStream(this)?.value} - ${DisplayProfile.fromVideoStream(this).value} - (${codec.toUpperCase()})";
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VideoStreamModel(width: $width, height: $height, frameRate: $frameRate, videoDoViTitle: $videoDoViTitle, videoRangeType: $videoRangeType)';
|
||||
}
|
||||
}
|
||||
|
||||
//Instead of using sortBy(a.isExternal etc..) this one seems to be more consistent for some reason
|
||||
extension SortByExternalExtension<T extends StreamModel> on Iterable<T> {
|
||||
List<T> sortByExternal() {
|
||||
return [...where((element) => !element.isExternal), ...where((element) => element.isExternal)];
|
||||
}
|
||||
}
|
||||
|
||||
class AudioStreamModel extends StreamModel {
|
||||
final String displayTitle;
|
||||
final String language;
|
||||
final String channelLayout;
|
||||
|
||||
AudioStreamModel({
|
||||
required this.displayTitle,
|
||||
required super.name,
|
||||
required super.codec,
|
||||
required super.isDefault,
|
||||
required super.isExternal,
|
||||
required super.index,
|
||||
required this.language,
|
||||
required this.channelLayout,
|
||||
});
|
||||
|
||||
factory AudioStreamModel.fromMediaStream(dto.MediaStream stream) {
|
||||
return AudioStreamModel(
|
||||
displayTitle: stream.displayTitle ?? "",
|
||||
name: stream.title ?? "",
|
||||
isDefault: stream.isDefault ?? false,
|
||||
codec: stream.codec ?? "",
|
||||
language: stream.language ?? "Unknown",
|
||||
channelLayout: stream.channelLayout ?? "",
|
||||
isExternal: stream.isExternal ?? false,
|
||||
index: stream.index ?? -1,
|
||||
);
|
||||
}
|
||||
|
||||
String get title =>
|
||||
[name, language, codec, channelLayout].whereNotNull().where((element) => element.isNotEmpty).join(' - ');
|
||||
|
||||
AudioStreamModel.no({
|
||||
super.name = 'Off',
|
||||
this.displayTitle = 'Off',
|
||||
this.language = '',
|
||||
super.codec = '',
|
||||
this.channelLayout = '',
|
||||
super.isDefault = false,
|
||||
super.isExternal = false,
|
||||
super.index = -1,
|
||||
});
|
||||
}
|
||||
|
||||
class SubStreamModel extends StreamModel {
|
||||
String id;
|
||||
String title;
|
||||
String displayTitle;
|
||||
String language;
|
||||
String? url;
|
||||
bool supportsExternalStream;
|
||||
SubStreamModel({
|
||||
required super.name,
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.displayTitle,
|
||||
required this.language,
|
||||
this.url,
|
||||
required super.codec,
|
||||
required super.isDefault,
|
||||
required super.isExternal,
|
||||
required super.index,
|
||||
this.supportsExternalStream = false,
|
||||
});
|
||||
|
||||
SubStreamModel.no({
|
||||
super.name = 'Off',
|
||||
this.id = 'Off',
|
||||
this.title = 'Off',
|
||||
this.displayTitle = 'Off',
|
||||
this.language = '',
|
||||
this.url = '',
|
||||
super.codec = '',
|
||||
super.isDefault = false,
|
||||
super.isExternal = false,
|
||||
super.index = -1,
|
||||
this.supportsExternalStream = false,
|
||||
});
|
||||
|
||||
factory SubStreamModel.fromMediaStream(dto.MediaStream stream, Ref ref) {
|
||||
return SubStreamModel(
|
||||
name: stream.title ?? "",
|
||||
title: stream.title ?? "",
|
||||
displayTitle: stream.displayTitle ?? "",
|
||||
language: stream.language ?? "Unknown",
|
||||
isDefault: stream.isDefault ?? false,
|
||||
codec: stream.codec ?? "",
|
||||
id: stream.hashCode.toString(),
|
||||
supportsExternalStream: stream.supportsExternalStream ?? false,
|
||||
url: stream.deliveryUrl != null
|
||||
? "${ref.read(userProvider)?.server ?? ""}${stream.deliveryUrl}}".replaceAll(".vtt", ".srt")
|
||||
: null,
|
||||
isExternal: stream.isExternal ?? false,
|
||||
index: stream.index ?? -1,
|
||||
);
|
||||
}
|
||||
|
||||
SubStreamModel copyWith({
|
||||
String? name,
|
||||
String? id,
|
||||
String? title,
|
||||
String? displayTitle,
|
||||
String? language,
|
||||
ValueGetter<String?>? url,
|
||||
String? codec,
|
||||
bool? isDefault,
|
||||
bool? isExternal,
|
||||
int? index,
|
||||
bool? supportsExternalStream,
|
||||
}) {
|
||||
return SubStreamModel(
|
||||
name: name ?? this.name,
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
displayTitle: displayTitle ?? this.displayTitle,
|
||||
language: language ?? this.language,
|
||||
url: url != null ? url() : this.url,
|
||||
supportsExternalStream: supportsExternalStream ?? this.supportsExternalStream,
|
||||
codec: codec ?? this.codec,
|
||||
isDefault: isDefault ?? this.isDefault,
|
||||
isExternal: isExternal ?? this.isExternal,
|
||||
index: index ?? this.index,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
'id': id,
|
||||
'title': title,
|
||||
'displayTitle': displayTitle,
|
||||
'language': language,
|
||||
'url': url,
|
||||
'supportsExternalStream': supportsExternalStream,
|
||||
'codec': codec,
|
||||
'isExternal': isExternal,
|
||||
'isDefault': isDefault,
|
||||
'index': index,
|
||||
};
|
||||
}
|
||||
|
||||
factory SubStreamModel.fromMap(Map<String, dynamic> map) {
|
||||
return SubStreamModel(
|
||||
name: map['name'] ?? '',
|
||||
id: map['id'] ?? '',
|
||||
title: map['title'] ?? '',
|
||||
displayTitle: map['displayTitle'] ?? '',
|
||||
language: map['language'] ?? '',
|
||||
url: map['url'],
|
||||
supportsExternalStream: map['supportsExternalStream'] ?? false,
|
||||
codec: map['codec'] ?? '',
|
||||
isDefault: map['isDefault'] ?? false,
|
||||
isExternal: map['isExternal'] ?? false,
|
||||
index: map['index'] ?? -1,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory SubStreamModel.fromJson(String source) => SubStreamModel.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SubFile(title: $title, displayTitle: $displayTitle, language: $language, url: $url, isExternal: $isExternal)';
|
||||
}
|
||||
}
|
||||
107
lib/models/items/movie_model.dart
Normal file
107
lib/models/items/movie_model.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
|
||||
import 'package:fladder/util/humanize_duration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/item_stream_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/screens/details_screens/movie_detail_screen.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'movie_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class MovieModel extends ItemStreamModel with MovieModelMappable {
|
||||
final String originalTitle;
|
||||
final String? path;
|
||||
final DateTime premiereDate;
|
||||
final String sortName;
|
||||
final String status;
|
||||
final List<ItemBaseModel> related;
|
||||
final List<Chapter> chapters;
|
||||
const MovieModel({
|
||||
required this.originalTitle,
|
||||
this.path,
|
||||
this.chapters = const [],
|
||||
required this.premiereDate,
|
||||
required this.sortName,
|
||||
required this.status,
|
||||
this.related = const [],
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.parentImages,
|
||||
required super.mediaStreams,
|
||||
required super.canDownload,
|
||||
required super.canDelete,
|
||||
super.jellyType,
|
||||
});
|
||||
@override
|
||||
String? detailedName(BuildContext context) => "$name${overview.yearAired != null ? " (${overview.yearAired})" : ""}";
|
||||
|
||||
@override
|
||||
Widget get detailScreenWidget => MovieDetailScreen(item: this);
|
||||
|
||||
@override
|
||||
ItemBaseModel get parentBaseModel => this;
|
||||
|
||||
@override
|
||||
String? get subText => overview.yearAired?.toString() ?? overview.runTime.humanize;
|
||||
|
||||
@override
|
||||
bool get playAble => true;
|
||||
|
||||
@override
|
||||
bool get identifiable => true;
|
||||
|
||||
@override
|
||||
String? label(BuildContext context) =>
|
||||
overview.yearAired == null ? overview.runTime.humanize : "$name (${overview.yearAired})";
|
||||
|
||||
@override
|
||||
ImageData? get bannerImage => images?.backDrop?.firstOrNull ?? images?.primary ?? getPosters?.primary;
|
||||
|
||||
@override
|
||||
MediaStreamsModel? get streamModel => mediaStreams;
|
||||
|
||||
@override
|
||||
bool get syncAble => true;
|
||||
|
||||
factory MovieModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return MovieModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
sortName: item.sortName ?? "",
|
||||
status: item.status ?? "",
|
||||
originalTitle: item.originalTitle ?? "",
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
chapters: Chapter.chaptersFromInfo(item.id ?? "", item.chapters ?? [], ref),
|
||||
premiereDate: item.premiereDate ?? DateTime.now(),
|
||||
parentImages: ImagesData.fromBaseItemParent(item, ref),
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
mediaStreams:
|
||||
MediaStreamsModel.fromMediaStreamsList(item.mediaSources?.firstOrNull, item.mediaStreams ?? [], ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
318
lib/models/items/movie_model.mapper.dart
Normal file
318
lib/models/items/movie_model.mapper.dart
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'movie_model.dart';
|
||||
|
||||
class MovieModelMapper extends SubClassMapperBase<MovieModel> {
|
||||
MovieModelMapper._();
|
||||
|
||||
static MovieModelMapper? _instance;
|
||||
static MovieModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = MovieModelMapper._());
|
||||
ItemStreamModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'MovieModel';
|
||||
|
||||
static String _$originalTitle(MovieModel v) => v.originalTitle;
|
||||
static const Field<MovieModel, String> _f$originalTitle =
|
||||
Field('originalTitle', _$originalTitle);
|
||||
static String? _$path(MovieModel v) => v.path;
|
||||
static const Field<MovieModel, String> _f$path =
|
||||
Field('path', _$path, opt: true);
|
||||
static List<Chapter> _$chapters(MovieModel v) => v.chapters;
|
||||
static const Field<MovieModel, List<Chapter>> _f$chapters =
|
||||
Field('chapters', _$chapters, opt: true, def: const []);
|
||||
static DateTime _$premiereDate(MovieModel v) => v.premiereDate;
|
||||
static const Field<MovieModel, DateTime> _f$premiereDate =
|
||||
Field('premiereDate', _$premiereDate);
|
||||
static String _$sortName(MovieModel v) => v.sortName;
|
||||
static const Field<MovieModel, String> _f$sortName =
|
||||
Field('sortName', _$sortName);
|
||||
static String _$status(MovieModel v) => v.status;
|
||||
static const Field<MovieModel, String> _f$status = Field('status', _$status);
|
||||
static List<ItemBaseModel> _$related(MovieModel v) => v.related;
|
||||
static const Field<MovieModel, List<ItemBaseModel>> _f$related =
|
||||
Field('related', _$related, opt: true, def: const []);
|
||||
static String _$name(MovieModel v) => v.name;
|
||||
static const Field<MovieModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(MovieModel v) => v.id;
|
||||
static const Field<MovieModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(MovieModel v) => v.overview;
|
||||
static const Field<MovieModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(MovieModel v) => v.parentId;
|
||||
static const Field<MovieModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(MovieModel v) => v.playlistId;
|
||||
static const Field<MovieModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(MovieModel v) => v.images;
|
||||
static const Field<MovieModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(MovieModel v) => v.childCount;
|
||||
static const Field<MovieModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(MovieModel v) => v.primaryRatio;
|
||||
static const Field<MovieModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(MovieModel v) => v.userData;
|
||||
static const Field<MovieModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static ImagesData? _$parentImages(MovieModel v) => v.parentImages;
|
||||
static const Field<MovieModel, ImagesData> _f$parentImages =
|
||||
Field('parentImages', _$parentImages);
|
||||
static MediaStreamsModel _$mediaStreams(MovieModel v) => v.mediaStreams;
|
||||
static const Field<MovieModel, MediaStreamsModel> _f$mediaStreams =
|
||||
Field('mediaStreams', _$mediaStreams);
|
||||
static bool? _$canDownload(MovieModel v) => v.canDownload;
|
||||
static const Field<MovieModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload);
|
||||
static bool? _$canDelete(MovieModel v) => v.canDelete;
|
||||
static const Field<MovieModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete);
|
||||
static dto.BaseItemKind? _$jellyType(MovieModel v) => v.jellyType;
|
||||
static const Field<MovieModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<MovieModel> fields = const {
|
||||
#originalTitle: _f$originalTitle,
|
||||
#path: _f$path,
|
||||
#chapters: _f$chapters,
|
||||
#premiereDate: _f$premiereDate,
|
||||
#sortName: _f$sortName,
|
||||
#status: _f$status,
|
||||
#related: _f$related,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#parentImages: _f$parentImages,
|
||||
#mediaStreams: _f$mediaStreams,
|
||||
#canDownload: _f$canDownload,
|
||||
#canDelete: _f$canDelete,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'MovieModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemStreamModelMapper.ensureInitialized();
|
||||
|
||||
static MovieModel _instantiate(DecodingData data) {
|
||||
return MovieModel(
|
||||
originalTitle: data.dec(_f$originalTitle),
|
||||
path: data.dec(_f$path),
|
||||
chapters: data.dec(_f$chapters),
|
||||
premiereDate: data.dec(_f$premiereDate),
|
||||
sortName: data.dec(_f$sortName),
|
||||
status: data.dec(_f$status),
|
||||
related: data.dec(_f$related),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
parentImages: data.dec(_f$parentImages),
|
||||
mediaStreams: data.dec(_f$mediaStreams),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static MovieModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<MovieModel>(map);
|
||||
}
|
||||
|
||||
static MovieModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<MovieModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin MovieModelMappable {
|
||||
String toJson() {
|
||||
return MovieModelMapper.ensureInitialized()
|
||||
.encodeJson<MovieModel>(this as MovieModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return MovieModelMapper.ensureInitialized()
|
||||
.encodeMap<MovieModel>(this as MovieModel);
|
||||
}
|
||||
|
||||
MovieModelCopyWith<MovieModel, MovieModel, MovieModel> get copyWith =>
|
||||
_MovieModelCopyWithImpl(this as MovieModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return MovieModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as MovieModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension MovieModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, MovieModel, $Out> {
|
||||
MovieModelCopyWith<$R, MovieModel, $Out> get $asMovieModel =>
|
||||
$base.as((v, t, t2) => _MovieModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class MovieModelCopyWith<$R, $In extends MovieModel, $Out>
|
||||
implements ItemStreamModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>> get chapters;
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get related;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{String? originalTitle,
|
||||
String? path,
|
||||
List<Chapter>? chapters,
|
||||
DateTime? premiereDate,
|
||||
String? sortName,
|
||||
String? status,
|
||||
List<ItemBaseModel>? related,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
ImagesData? parentImages,
|
||||
MediaStreamsModel? mediaStreams,
|
||||
bool? canDownload,
|
||||
bool? canDelete,
|
||||
dto.BaseItemKind? jellyType});
|
||||
MovieModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _MovieModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, MovieModel, $Out>
|
||||
implements MovieModelCopyWith<$R, MovieModel, $Out> {
|
||||
_MovieModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<MovieModel> $mapper =
|
||||
MovieModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>
|
||||
get chapters => ListCopyWith($value.chapters,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(chapters: v));
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get related => ListCopyWith($value.related,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(related: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{String? originalTitle,
|
||||
Object? path = $none,
|
||||
List<Chapter>? chapters,
|
||||
DateTime? premiereDate,
|
||||
String? sortName,
|
||||
String? status,
|
||||
List<ItemBaseModel>? related,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? parentImages = $none,
|
||||
MediaStreamsModel? mediaStreams,
|
||||
Object? canDownload = $none,
|
||||
Object? canDelete = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (originalTitle != null) #originalTitle: originalTitle,
|
||||
if (path != $none) #path: path,
|
||||
if (chapters != null) #chapters: chapters,
|
||||
if (premiereDate != null) #premiereDate: premiereDate,
|
||||
if (sortName != null) #sortName: sortName,
|
||||
if (status != null) #status: status,
|
||||
if (related != null) #related: related,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (parentImages != $none) #parentImages: parentImages,
|
||||
if (mediaStreams != null) #mediaStreams: mediaStreams,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
MovieModel $make(CopyWithData data) => MovieModel(
|
||||
originalTitle: data.get(#originalTitle, or: $value.originalTitle),
|
||||
path: data.get(#path, or: $value.path),
|
||||
chapters: data.get(#chapters, or: $value.chapters),
|
||||
premiereDate: data.get(#premiereDate, or: $value.premiereDate),
|
||||
sortName: data.get(#sortName, or: $value.sortName),
|
||||
status: data.get(#status, or: $value.status),
|
||||
related: data.get(#related, or: $value.related),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
parentImages: data.get(#parentImages, or: $value.parentImages),
|
||||
mediaStreams: data.get(#mediaStreams, or: $value.mediaStreams),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
MovieModelCopyWith<$R2, MovieModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_MovieModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
82
lib/models/items/overview_model.dart
Normal file
82
lib/models/items/overview_model.dart
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'overview_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class OverviewModel with OverviewModelMappable {
|
||||
final Duration? runTime;
|
||||
final String summary;
|
||||
final int? yearAired;
|
||||
final DateTime? dateAdded;
|
||||
final String? parentalRating;
|
||||
final int? productionYear;
|
||||
final double? criticRating;
|
||||
final double? communityRating;
|
||||
final Map<String, TrickPlayModel>? trickPlayInfo;
|
||||
final List<Chapter>? chapters;
|
||||
final List<ExternalUrls>? externalUrls;
|
||||
final List<Studio> studios;
|
||||
final List<String> genres;
|
||||
final List<GenreItems> genreItems;
|
||||
final List<String> tags;
|
||||
final List<Person> people;
|
||||
const OverviewModel({
|
||||
this.runTime,
|
||||
this.summary = "",
|
||||
this.yearAired,
|
||||
this.dateAdded,
|
||||
this.parentalRating,
|
||||
this.productionYear,
|
||||
this.criticRating,
|
||||
this.communityRating,
|
||||
this.trickPlayInfo,
|
||||
this.chapters,
|
||||
this.externalUrls,
|
||||
this.studios = const [],
|
||||
this.genres = const [],
|
||||
this.genreItems = const [],
|
||||
this.tags = const [],
|
||||
this.people = const [],
|
||||
});
|
||||
|
||||
List<Person> get directors {
|
||||
return people.where((element) => element.type == PersonKind.director).toList();
|
||||
}
|
||||
|
||||
List<Person> get writers {
|
||||
return people.where((element) => element.type == PersonKind.writer).toList();
|
||||
}
|
||||
|
||||
factory OverviewModel.fromBaseItemDto(BaseItemDto item, Ref ref) {
|
||||
final trickPlayItem = item.trickplay;
|
||||
return OverviewModel(
|
||||
runTime: item.runTimeDuration,
|
||||
yearAired: item.productionYear,
|
||||
parentalRating: item.officialRating,
|
||||
summary: item.overview ?? "",
|
||||
genres: item.genres ?? [],
|
||||
criticRating: item.criticRating,
|
||||
communityRating: item.communityRating,
|
||||
tags: item.tags ?? [],
|
||||
dateAdded: item.dateCreated,
|
||||
trickPlayInfo:
|
||||
trickPlayItem != null && trickPlayItem.isNotEmpty ? TrickPlayModel.toTrickPlayMap(trickPlayItem) : null,
|
||||
chapters: item.id != null ? Chapter.chaptersFromInfo(item.id ?? "", item.chapters ?? [], ref) : null,
|
||||
studios: item.studios?.map((e) => Studio(id: e.id ?? "", name: e.name ?? "")).toList() ?? [],
|
||||
genreItems: item.genreItems?.map((e) => GenreItems(id: e.id ?? "", name: e.name ?? "")).toList() ?? [],
|
||||
externalUrls: ExternalUrls.fromDto(item.externalUrls ?? []),
|
||||
people: Person.peopleFromDto(item.people ?? [], ref),
|
||||
);
|
||||
}
|
||||
|
||||
factory OverviewModel.fromMap(Map<String, dynamic> map) => OverviewModelMapper.fromMap(map);
|
||||
factory OverviewModel.fromJson(String json) => OverviewModelMapper.fromJson(json);
|
||||
}
|
||||
302
lib/models/items/overview_model.mapper.dart
Normal file
302
lib/models/items/overview_model.mapper.dart
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'overview_model.dart';
|
||||
|
||||
class OverviewModelMapper extends ClassMapperBase<OverviewModel> {
|
||||
OverviewModelMapper._();
|
||||
|
||||
static OverviewModelMapper? _instance;
|
||||
static OverviewModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = OverviewModelMapper._());
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'OverviewModel';
|
||||
|
||||
static Duration? _$runTime(OverviewModel v) => v.runTime;
|
||||
static const Field<OverviewModel, Duration> _f$runTime =
|
||||
Field('runTime', _$runTime, opt: true);
|
||||
static String _$summary(OverviewModel v) => v.summary;
|
||||
static const Field<OverviewModel, String> _f$summary =
|
||||
Field('summary', _$summary, opt: true, def: "");
|
||||
static int? _$yearAired(OverviewModel v) => v.yearAired;
|
||||
static const Field<OverviewModel, int> _f$yearAired =
|
||||
Field('yearAired', _$yearAired, opt: true);
|
||||
static DateTime? _$dateAdded(OverviewModel v) => v.dateAdded;
|
||||
static const Field<OverviewModel, DateTime> _f$dateAdded =
|
||||
Field('dateAdded', _$dateAdded, opt: true);
|
||||
static String? _$parentalRating(OverviewModel v) => v.parentalRating;
|
||||
static const Field<OverviewModel, String> _f$parentalRating =
|
||||
Field('parentalRating', _$parentalRating, opt: true);
|
||||
static int? _$productionYear(OverviewModel v) => v.productionYear;
|
||||
static const Field<OverviewModel, int> _f$productionYear =
|
||||
Field('productionYear', _$productionYear, opt: true);
|
||||
static double? _$criticRating(OverviewModel v) => v.criticRating;
|
||||
static const Field<OverviewModel, double> _f$criticRating =
|
||||
Field('criticRating', _$criticRating, opt: true);
|
||||
static double? _$communityRating(OverviewModel v) => v.communityRating;
|
||||
static const Field<OverviewModel, double> _f$communityRating =
|
||||
Field('communityRating', _$communityRating, opt: true);
|
||||
static Map<String, TrickPlayModel>? _$trickPlayInfo(OverviewModel v) =>
|
||||
v.trickPlayInfo;
|
||||
static const Field<OverviewModel, Map<String, TrickPlayModel>>
|
||||
_f$trickPlayInfo = Field('trickPlayInfo', _$trickPlayInfo, opt: true);
|
||||
static List<Chapter>? _$chapters(OverviewModel v) => v.chapters;
|
||||
static const Field<OverviewModel, List<Chapter>> _f$chapters =
|
||||
Field('chapters', _$chapters, opt: true);
|
||||
static List<ExternalUrls>? _$externalUrls(OverviewModel v) => v.externalUrls;
|
||||
static const Field<OverviewModel, List<ExternalUrls>> _f$externalUrls =
|
||||
Field('externalUrls', _$externalUrls, opt: true);
|
||||
static List<Studio> _$studios(OverviewModel v) => v.studios;
|
||||
static const Field<OverviewModel, List<Studio>> _f$studios =
|
||||
Field('studios', _$studios, opt: true, def: const []);
|
||||
static List<String> _$genres(OverviewModel v) => v.genres;
|
||||
static const Field<OverviewModel, List<String>> _f$genres =
|
||||
Field('genres', _$genres, opt: true, def: const []);
|
||||
static List<GenreItems> _$genreItems(OverviewModel v) => v.genreItems;
|
||||
static const Field<OverviewModel, List<GenreItems>> _f$genreItems =
|
||||
Field('genreItems', _$genreItems, opt: true, def: const []);
|
||||
static List<String> _$tags(OverviewModel v) => v.tags;
|
||||
static const Field<OverviewModel, List<String>> _f$tags =
|
||||
Field('tags', _$tags, opt: true, def: const []);
|
||||
static List<Person> _$people(OverviewModel v) => v.people;
|
||||
static const Field<OverviewModel, List<Person>> _f$people =
|
||||
Field('people', _$people, opt: true, def: const []);
|
||||
|
||||
@override
|
||||
final MappableFields<OverviewModel> fields = const {
|
||||
#runTime: _f$runTime,
|
||||
#summary: _f$summary,
|
||||
#yearAired: _f$yearAired,
|
||||
#dateAdded: _f$dateAdded,
|
||||
#parentalRating: _f$parentalRating,
|
||||
#productionYear: _f$productionYear,
|
||||
#criticRating: _f$criticRating,
|
||||
#communityRating: _f$communityRating,
|
||||
#trickPlayInfo: _f$trickPlayInfo,
|
||||
#chapters: _f$chapters,
|
||||
#externalUrls: _f$externalUrls,
|
||||
#studios: _f$studios,
|
||||
#genres: _f$genres,
|
||||
#genreItems: _f$genreItems,
|
||||
#tags: _f$tags,
|
||||
#people: _f$people,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
static OverviewModel _instantiate(DecodingData data) {
|
||||
return OverviewModel(
|
||||
runTime: data.dec(_f$runTime),
|
||||
summary: data.dec(_f$summary),
|
||||
yearAired: data.dec(_f$yearAired),
|
||||
dateAdded: data.dec(_f$dateAdded),
|
||||
parentalRating: data.dec(_f$parentalRating),
|
||||
productionYear: data.dec(_f$productionYear),
|
||||
criticRating: data.dec(_f$criticRating),
|
||||
communityRating: data.dec(_f$communityRating),
|
||||
trickPlayInfo: data.dec(_f$trickPlayInfo),
|
||||
chapters: data.dec(_f$chapters),
|
||||
externalUrls: data.dec(_f$externalUrls),
|
||||
studios: data.dec(_f$studios),
|
||||
genres: data.dec(_f$genres),
|
||||
genreItems: data.dec(_f$genreItems),
|
||||
tags: data.dec(_f$tags),
|
||||
people: data.dec(_f$people));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static OverviewModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<OverviewModel>(map);
|
||||
}
|
||||
|
||||
static OverviewModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<OverviewModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin OverviewModelMappable {
|
||||
String toJson() {
|
||||
return OverviewModelMapper.ensureInitialized()
|
||||
.encodeJson<OverviewModel>(this as OverviewModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return OverviewModelMapper.ensureInitialized()
|
||||
.encodeMap<OverviewModel>(this as OverviewModel);
|
||||
}
|
||||
|
||||
OverviewModelCopyWith<OverviewModel, OverviewModel, OverviewModel>
|
||||
get copyWith => _OverviewModelCopyWithImpl(
|
||||
this as OverviewModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return OverviewModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as OverviewModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension OverviewModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, OverviewModel, $Out> {
|
||||
OverviewModelCopyWith<$R, OverviewModel, $Out> get $asOverviewModel =>
|
||||
$base.as((v, t, t2) => _OverviewModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class OverviewModelCopyWith<$R, $In extends OverviewModel, $Out>
|
||||
implements ClassCopyWith<$R, $In, $Out> {
|
||||
MapCopyWith<$R, String, TrickPlayModel,
|
||||
ObjectCopyWith<$R, TrickPlayModel, TrickPlayModel>>? get trickPlayInfo;
|
||||
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>? get chapters;
|
||||
ListCopyWith<$R, ExternalUrls,
|
||||
ObjectCopyWith<$R, ExternalUrls, ExternalUrls>>? get externalUrls;
|
||||
ListCopyWith<$R, Studio, ObjectCopyWith<$R, Studio, Studio>> get studios;
|
||||
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get genres;
|
||||
ListCopyWith<$R, GenreItems, ObjectCopyWith<$R, GenreItems, GenreItems>>
|
||||
get genreItems;
|
||||
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get tags;
|
||||
ListCopyWith<$R, Person, ObjectCopyWith<$R, Person, Person>> get people;
|
||||
$R call(
|
||||
{Duration? runTime,
|
||||
String? summary,
|
||||
int? yearAired,
|
||||
DateTime? dateAdded,
|
||||
String? parentalRating,
|
||||
int? productionYear,
|
||||
double? criticRating,
|
||||
double? communityRating,
|
||||
Map<String, TrickPlayModel>? trickPlayInfo,
|
||||
List<Chapter>? chapters,
|
||||
List<ExternalUrls>? externalUrls,
|
||||
List<Studio>? studios,
|
||||
List<String>? genres,
|
||||
List<GenreItems>? genreItems,
|
||||
List<String>? tags,
|
||||
List<Person>? people});
|
||||
OverviewModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _OverviewModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, OverviewModel, $Out>
|
||||
implements OverviewModelCopyWith<$R, OverviewModel, $Out> {
|
||||
_OverviewModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<OverviewModel> $mapper =
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
@override
|
||||
MapCopyWith<$R, String, TrickPlayModel,
|
||||
ObjectCopyWith<$R, TrickPlayModel, TrickPlayModel>>?
|
||||
get trickPlayInfo => $value.trickPlayInfo != null
|
||||
? MapCopyWith(
|
||||
$value.trickPlayInfo!,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(trickPlayInfo: v))
|
||||
: null;
|
||||
@override
|
||||
ListCopyWith<$R, Chapter, ObjectCopyWith<$R, Chapter, Chapter>>?
|
||||
get chapters => $value.chapters != null
|
||||
? ListCopyWith(
|
||||
$value.chapters!,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(chapters: v))
|
||||
: null;
|
||||
@override
|
||||
ListCopyWith<$R, ExternalUrls,
|
||||
ObjectCopyWith<$R, ExternalUrls, ExternalUrls>>?
|
||||
get externalUrls => $value.externalUrls != null
|
||||
? ListCopyWith(
|
||||
$value.externalUrls!,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(externalUrls: v))
|
||||
: null;
|
||||
@override
|
||||
ListCopyWith<$R, Studio, ObjectCopyWith<$R, Studio, Studio>> get studios =>
|
||||
ListCopyWith($value.studios, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(studios: v));
|
||||
@override
|
||||
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get genres =>
|
||||
ListCopyWith($value.genres, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(genres: v));
|
||||
@override
|
||||
ListCopyWith<$R, GenreItems, ObjectCopyWith<$R, GenreItems, GenreItems>>
|
||||
get genreItems => ListCopyWith(
|
||||
$value.genreItems,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(genreItems: v));
|
||||
@override
|
||||
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get tags =>
|
||||
ListCopyWith($value.tags, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(tags: v));
|
||||
@override
|
||||
ListCopyWith<$R, Person, ObjectCopyWith<$R, Person, Person>> get people =>
|
||||
ListCopyWith($value.people, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(people: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? runTime = $none,
|
||||
String? summary,
|
||||
Object? yearAired = $none,
|
||||
Object? dateAdded = $none,
|
||||
Object? parentalRating = $none,
|
||||
Object? productionYear = $none,
|
||||
Object? criticRating = $none,
|
||||
Object? communityRating = $none,
|
||||
Object? trickPlayInfo = $none,
|
||||
Object? chapters = $none,
|
||||
Object? externalUrls = $none,
|
||||
List<Studio>? studios,
|
||||
List<String>? genres,
|
||||
List<GenreItems>? genreItems,
|
||||
List<String>? tags,
|
||||
List<Person>? people}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (runTime != $none) #runTime: runTime,
|
||||
if (summary != null) #summary: summary,
|
||||
if (yearAired != $none) #yearAired: yearAired,
|
||||
if (dateAdded != $none) #dateAdded: dateAdded,
|
||||
if (parentalRating != $none) #parentalRating: parentalRating,
|
||||
if (productionYear != $none) #productionYear: productionYear,
|
||||
if (criticRating != $none) #criticRating: criticRating,
|
||||
if (communityRating != $none) #communityRating: communityRating,
|
||||
if (trickPlayInfo != $none) #trickPlayInfo: trickPlayInfo,
|
||||
if (chapters != $none) #chapters: chapters,
|
||||
if (externalUrls != $none) #externalUrls: externalUrls,
|
||||
if (studios != null) #studios: studios,
|
||||
if (genres != null) #genres: genres,
|
||||
if (genreItems != null) #genreItems: genreItems,
|
||||
if (tags != null) #tags: tags,
|
||||
if (people != null) #people: people
|
||||
}));
|
||||
@override
|
||||
OverviewModel $make(CopyWithData data) => OverviewModel(
|
||||
runTime: data.get(#runTime, or: $value.runTime),
|
||||
summary: data.get(#summary, or: $value.summary),
|
||||
yearAired: data.get(#yearAired, or: $value.yearAired),
|
||||
dateAdded: data.get(#dateAdded, or: $value.dateAdded),
|
||||
parentalRating: data.get(#parentalRating, or: $value.parentalRating),
|
||||
productionYear: data.get(#productionYear, or: $value.productionYear),
|
||||
criticRating: data.get(#criticRating, or: $value.criticRating),
|
||||
communityRating: data.get(#communityRating, or: $value.communityRating),
|
||||
trickPlayInfo: data.get(#trickPlayInfo, or: $value.trickPlayInfo),
|
||||
chapters: data.get(#chapters, or: $value.chapters),
|
||||
externalUrls: data.get(#externalUrls, or: $value.externalUrls),
|
||||
studios: data.get(#studios, or: $value.studios),
|
||||
genres: data.get(#genres, or: $value.genres),
|
||||
genreItems: data.get(#genreItems, or: $value.genreItems),
|
||||
tags: data.get(#tags, or: $value.tags),
|
||||
people: data.get(#people, or: $value.people));
|
||||
|
||||
@override
|
||||
OverviewModelCopyWith<$R2, OverviewModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_OverviewModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
68
lib/models/items/person_model.dart
Normal file
68
lib/models/items/person_model.dart
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/movie_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
part 'person_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class PersonModel extends ItemBaseModel with PersonModelMappable {
|
||||
final DateTime? dateOfBirth;
|
||||
final List<String> birthPlace;
|
||||
final List<MovieModel> movies;
|
||||
final List<SeriesModel> series;
|
||||
const PersonModel({
|
||||
this.dateOfBirth,
|
||||
required this.birthPlace,
|
||||
required this.movies,
|
||||
required this.series,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
super.canDownload,
|
||||
super.canDelete,
|
||||
super.jellyType,
|
||||
});
|
||||
|
||||
static PersonModel fromBaseDto(BaseItemDto item, Ref ref) {
|
||||
return PersonModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
dateOfBirth: item.premiereDate,
|
||||
birthPlace: item.productionLocations ?? [],
|
||||
movies: [],
|
||||
series: [],
|
||||
);
|
||||
}
|
||||
|
||||
int? get age {
|
||||
if (dateOfBirth == null) return null;
|
||||
final today = DateTime.now();
|
||||
final months = today.month - dateOfBirth!.month;
|
||||
if (months < 0) {
|
||||
return (dateOfBirth!.year - (DateTime.now().year - 1)).abs();
|
||||
} else {
|
||||
return (dateOfBirth!.year - DateTime.now().year).abs();
|
||||
}
|
||||
}
|
||||
}
|
||||
281
lib/models/items/person_model.mapper.dart
Normal file
281
lib/models/items/person_model.mapper.dart
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'person_model.dart';
|
||||
|
||||
class PersonModelMapper extends SubClassMapperBase<PersonModel> {
|
||||
PersonModelMapper._();
|
||||
|
||||
static PersonModelMapper? _instance;
|
||||
static PersonModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = PersonModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
MovieModelMapper.ensureInitialized();
|
||||
SeriesModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'PersonModel';
|
||||
|
||||
static DateTime? _$dateOfBirth(PersonModel v) => v.dateOfBirth;
|
||||
static const Field<PersonModel, DateTime> _f$dateOfBirth =
|
||||
Field('dateOfBirth', _$dateOfBirth, opt: true);
|
||||
static List<String> _$birthPlace(PersonModel v) => v.birthPlace;
|
||||
static const Field<PersonModel, List<String>> _f$birthPlace =
|
||||
Field('birthPlace', _$birthPlace);
|
||||
static List<MovieModel> _$movies(PersonModel v) => v.movies;
|
||||
static const Field<PersonModel, List<MovieModel>> _f$movies =
|
||||
Field('movies', _$movies);
|
||||
static List<SeriesModel> _$series(PersonModel v) => v.series;
|
||||
static const Field<PersonModel, List<SeriesModel>> _f$series =
|
||||
Field('series', _$series);
|
||||
static String _$name(PersonModel v) => v.name;
|
||||
static const Field<PersonModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(PersonModel v) => v.id;
|
||||
static const Field<PersonModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(PersonModel v) => v.overview;
|
||||
static const Field<PersonModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(PersonModel v) => v.parentId;
|
||||
static const Field<PersonModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(PersonModel v) => v.playlistId;
|
||||
static const Field<PersonModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(PersonModel v) => v.images;
|
||||
static const Field<PersonModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(PersonModel v) => v.childCount;
|
||||
static const Field<PersonModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(PersonModel v) => v.primaryRatio;
|
||||
static const Field<PersonModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(PersonModel v) => v.userData;
|
||||
static const Field<PersonModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDownload(PersonModel v) => v.canDownload;
|
||||
static const Field<PersonModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload, opt: true);
|
||||
static bool? _$canDelete(PersonModel v) => v.canDelete;
|
||||
static const Field<PersonModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete, opt: true);
|
||||
static BaseItemKind? _$jellyType(PersonModel v) => v.jellyType;
|
||||
static const Field<PersonModel, BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<PersonModel> fields = const {
|
||||
#dateOfBirth: _f$dateOfBirth,
|
||||
#birthPlace: _f$birthPlace,
|
||||
#movies: _f$movies,
|
||||
#series: _f$series,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDownload: _f$canDownload,
|
||||
#canDelete: _f$canDelete,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'PersonModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static PersonModel _instantiate(DecodingData data) {
|
||||
return PersonModel(
|
||||
dateOfBirth: data.dec(_f$dateOfBirth),
|
||||
birthPlace: data.dec(_f$birthPlace),
|
||||
movies: data.dec(_f$movies),
|
||||
series: data.dec(_f$series),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static PersonModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<PersonModel>(map);
|
||||
}
|
||||
|
||||
static PersonModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<PersonModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin PersonModelMappable {
|
||||
String toJson() {
|
||||
return PersonModelMapper.ensureInitialized()
|
||||
.encodeJson<PersonModel>(this as PersonModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return PersonModelMapper.ensureInitialized()
|
||||
.encodeMap<PersonModel>(this as PersonModel);
|
||||
}
|
||||
|
||||
PersonModelCopyWith<PersonModel, PersonModel, PersonModel> get copyWith =>
|
||||
_PersonModelCopyWithImpl(this as PersonModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return PersonModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as PersonModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension PersonModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, PersonModel, $Out> {
|
||||
PersonModelCopyWith<$R, PersonModel, $Out> get $asPersonModel =>
|
||||
$base.as((v, t, t2) => _PersonModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class PersonModelCopyWith<$R, $In extends PersonModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get birthPlace;
|
||||
ListCopyWith<$R, MovieModel, MovieModelCopyWith<$R, MovieModel, MovieModel>>
|
||||
get movies;
|
||||
ListCopyWith<$R, SeriesModel,
|
||||
SeriesModelCopyWith<$R, SeriesModel, SeriesModel>> get series;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{DateTime? dateOfBirth,
|
||||
List<String>? birthPlace,
|
||||
List<MovieModel>? movies,
|
||||
List<SeriesModel>? series,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDownload,
|
||||
bool? canDelete,
|
||||
BaseItemKind? jellyType});
|
||||
PersonModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _PersonModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, PersonModel, $Out>
|
||||
implements PersonModelCopyWith<$R, PersonModel, $Out> {
|
||||
_PersonModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<PersonModel> $mapper =
|
||||
PersonModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get birthPlace =>
|
||||
ListCopyWith($value.birthPlace, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(birthPlace: v));
|
||||
@override
|
||||
ListCopyWith<$R, MovieModel, MovieModelCopyWith<$R, MovieModel, MovieModel>>
|
||||
get movies => ListCopyWith($value.movies, (v, t) => v.copyWith.$chain(t),
|
||||
(v) => call(movies: v));
|
||||
@override
|
||||
ListCopyWith<$R, SeriesModel,
|
||||
SeriesModelCopyWith<$R, SeriesModel, SeriesModel>>
|
||||
get series => ListCopyWith($value.series, (v, t) => v.copyWith.$chain(t),
|
||||
(v) => call(series: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? dateOfBirth = $none,
|
||||
List<String>? birthPlace,
|
||||
List<MovieModel>? movies,
|
||||
List<SeriesModel>? series,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDownload = $none,
|
||||
Object? canDelete = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (dateOfBirth != $none) #dateOfBirth: dateOfBirth,
|
||||
if (birthPlace != null) #birthPlace: birthPlace,
|
||||
if (movies != null) #movies: movies,
|
||||
if (series != null) #series: series,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
PersonModel $make(CopyWithData data) => PersonModel(
|
||||
dateOfBirth: data.get(#dateOfBirth, or: $value.dateOfBirth),
|
||||
birthPlace: data.get(#birthPlace, or: $value.birthPlace),
|
||||
movies: data.get(#movies, or: $value.movies),
|
||||
series: data.get(#series, or: $value.series),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
PersonModelCopyWith<$R2, PersonModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_PersonModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
154
lib/models/items/photos_model.dart
Normal file
154
lib/models/items/photos_model.dart
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
||||
import 'package:fladder/util/refresh_state.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'photos_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class PhotoAlbumModel extends ItemBaseModel with PhotoAlbumModelMappable {
|
||||
final List<ItemBaseModel> photos;
|
||||
|
||||
const PhotoAlbumModel({
|
||||
required this.photos,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
super.canDelete,
|
||||
super.canDownload,
|
||||
super.jellyType,
|
||||
});
|
||||
@override
|
||||
bool get unWatched => userData.unPlayedItemCount != 0;
|
||||
|
||||
@override
|
||||
bool get playAble => true;
|
||||
|
||||
@override
|
||||
ItemBaseModel get parentBaseModel => copyWith(id: parentId);
|
||||
|
||||
factory PhotoAlbumModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return PhotoAlbumModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
photos: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@MappableClass()
|
||||
class PhotoModel extends ItemBaseModel with PhotoModelMappable {
|
||||
final String? albumId;
|
||||
final DateTime? dateTaken;
|
||||
final ImagesData? thumbnail;
|
||||
final FladderItemType internalType;
|
||||
|
||||
const PhotoModel({
|
||||
required this.albumId,
|
||||
required this.dateTaken,
|
||||
required this.thumbnail,
|
||||
required this.internalType,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.canDownload,
|
||||
required super.canDelete,
|
||||
super.jellyType,
|
||||
});
|
||||
|
||||
@override
|
||||
PhotoAlbumModel get parentBaseModel => PhotoAlbumModel(
|
||||
photos: [],
|
||||
name: "",
|
||||
id: parentId ?? "",
|
||||
overview: overview,
|
||||
parentId: parentId,
|
||||
playlistId: playlistId,
|
||||
images: images,
|
||||
childCount: childCount,
|
||||
primaryRatio: primaryRatio,
|
||||
userData: userData,
|
||||
);
|
||||
|
||||
@override
|
||||
ImagesData? get getPosters => thumbnail;
|
||||
|
||||
@override
|
||||
bool get galleryItem => switch (internalType) {
|
||||
FladderItemType.photo => albumId?.isNotEmpty == true,
|
||||
FladderItemType.video => parentId?.isNotEmpty == true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@override
|
||||
bool get unWatched => false;
|
||||
|
||||
factory PhotoModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return PhotoModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
primaryRatio: double.tryParse(item.aspectRatio ?? "") ?? item.primaryImageAspectRatio ?? 1.0,
|
||||
dateTaken: item.dateCreated,
|
||||
albumId: item.albumId,
|
||||
thumbnail: ImagesData.fromBaseItem(item, ref),
|
||||
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
internalType: switch (item.type) {
|
||||
BaseItemKind.video => FladderItemType.video,
|
||||
_ => FladderItemType.photo,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String downloadPath(WidgetRef ref) {
|
||||
return "${ref.read(userProvider)?.server}/Items/$id/Download?api_key=${ref.read(userProvider)?.credentials.token}";
|
||||
}
|
||||
|
||||
Future<void> navigateToAlbum(BuildContext context) async {
|
||||
if ((albumId ?? parentId) == null) {
|
||||
fladderSnackbar(context, title: context.localized.notPartOfAlbum);
|
||||
return;
|
||||
}
|
||||
await parentBaseModel.navigateTo(context);
|
||||
if (context.mounted) context.refreshData();
|
||||
}
|
||||
}
|
||||
498
lib/models/items/photos_model.mapper.dart
Normal file
498
lib/models/items/photos_model.mapper.dart
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'photos_model.dart';
|
||||
|
||||
class PhotoAlbumModelMapper extends SubClassMapperBase<PhotoAlbumModel> {
|
||||
PhotoAlbumModelMapper._();
|
||||
|
||||
static PhotoAlbumModelMapper? _instance;
|
||||
static PhotoAlbumModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = PhotoAlbumModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'PhotoAlbumModel';
|
||||
|
||||
static List<ItemBaseModel> _$photos(PhotoAlbumModel v) => v.photos;
|
||||
static const Field<PhotoAlbumModel, List<ItemBaseModel>> _f$photos =
|
||||
Field('photos', _$photos);
|
||||
static String _$name(PhotoAlbumModel v) => v.name;
|
||||
static const Field<PhotoAlbumModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(PhotoAlbumModel v) => v.id;
|
||||
static const Field<PhotoAlbumModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(PhotoAlbumModel v) => v.overview;
|
||||
static const Field<PhotoAlbumModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(PhotoAlbumModel v) => v.parentId;
|
||||
static const Field<PhotoAlbumModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(PhotoAlbumModel v) => v.playlistId;
|
||||
static const Field<PhotoAlbumModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(PhotoAlbumModel v) => v.images;
|
||||
static const Field<PhotoAlbumModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(PhotoAlbumModel v) => v.childCount;
|
||||
static const Field<PhotoAlbumModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(PhotoAlbumModel v) => v.primaryRatio;
|
||||
static const Field<PhotoAlbumModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(PhotoAlbumModel v) => v.userData;
|
||||
static const Field<PhotoAlbumModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDelete(PhotoAlbumModel v) => v.canDelete;
|
||||
static const Field<PhotoAlbumModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete, opt: true);
|
||||
static bool? _$canDownload(PhotoAlbumModel v) => v.canDownload;
|
||||
static const Field<PhotoAlbumModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload, opt: true);
|
||||
static dto.BaseItemKind? _$jellyType(PhotoAlbumModel v) => v.jellyType;
|
||||
static const Field<PhotoAlbumModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<PhotoAlbumModel> fields = const {
|
||||
#photos: _f$photos,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDelete: _f$canDelete,
|
||||
#canDownload: _f$canDownload,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'PhotoAlbumModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static PhotoAlbumModel _instantiate(DecodingData data) {
|
||||
return PhotoAlbumModel(
|
||||
photos: data.dec(_f$photos),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static PhotoAlbumModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<PhotoAlbumModel>(map);
|
||||
}
|
||||
|
||||
static PhotoAlbumModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<PhotoAlbumModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin PhotoAlbumModelMappable {
|
||||
String toJson() {
|
||||
return PhotoAlbumModelMapper.ensureInitialized()
|
||||
.encodeJson<PhotoAlbumModel>(this as PhotoAlbumModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return PhotoAlbumModelMapper.ensureInitialized()
|
||||
.encodeMap<PhotoAlbumModel>(this as PhotoAlbumModel);
|
||||
}
|
||||
|
||||
PhotoAlbumModelCopyWith<PhotoAlbumModel, PhotoAlbumModel, PhotoAlbumModel>
|
||||
get copyWith => _PhotoAlbumModelCopyWithImpl(
|
||||
this as PhotoAlbumModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return PhotoAlbumModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as PhotoAlbumModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoAlbumModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, PhotoAlbumModel, $Out> {
|
||||
PhotoAlbumModelCopyWith<$R, PhotoAlbumModel, $Out> get $asPhotoAlbumModel =>
|
||||
$base.as((v, t, t2) => _PhotoAlbumModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class PhotoAlbumModelCopyWith<$R, $In extends PhotoAlbumModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get photos;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{List<ItemBaseModel>? photos,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDelete,
|
||||
bool? canDownload,
|
||||
dto.BaseItemKind? jellyType});
|
||||
PhotoAlbumModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _PhotoAlbumModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, PhotoAlbumModel, $Out>
|
||||
implements PhotoAlbumModelCopyWith<$R, PhotoAlbumModel, $Out> {
|
||||
_PhotoAlbumModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<PhotoAlbumModel> $mapper =
|
||||
PhotoAlbumModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get photos => ListCopyWith($value.photos, (v, t) => v.copyWith.$chain(t),
|
||||
(v) => call(photos: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{List<ItemBaseModel>? photos,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDelete = $none,
|
||||
Object? canDownload = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (photos != null) #photos: photos,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
PhotoAlbumModel $make(CopyWithData data) => PhotoAlbumModel(
|
||||
photos: data.get(#photos, or: $value.photos),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
PhotoAlbumModelCopyWith<$R2, PhotoAlbumModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_PhotoAlbumModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
|
||||
class PhotoModelMapper extends SubClassMapperBase<PhotoModel> {
|
||||
PhotoModelMapper._();
|
||||
|
||||
static PhotoModelMapper? _instance;
|
||||
static PhotoModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = PhotoModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'PhotoModel';
|
||||
|
||||
static String? _$albumId(PhotoModel v) => v.albumId;
|
||||
static const Field<PhotoModel, String> _f$albumId =
|
||||
Field('albumId', _$albumId);
|
||||
static DateTime? _$dateTaken(PhotoModel v) => v.dateTaken;
|
||||
static const Field<PhotoModel, DateTime> _f$dateTaken =
|
||||
Field('dateTaken', _$dateTaken);
|
||||
static ImagesData? _$thumbnail(PhotoModel v) => v.thumbnail;
|
||||
static const Field<PhotoModel, ImagesData> _f$thumbnail =
|
||||
Field('thumbnail', _$thumbnail);
|
||||
static FladderItemType _$internalType(PhotoModel v) => v.internalType;
|
||||
static const Field<PhotoModel, FladderItemType> _f$internalType =
|
||||
Field('internalType', _$internalType);
|
||||
static String _$name(PhotoModel v) => v.name;
|
||||
static const Field<PhotoModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(PhotoModel v) => v.id;
|
||||
static const Field<PhotoModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(PhotoModel v) => v.overview;
|
||||
static const Field<PhotoModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(PhotoModel v) => v.parentId;
|
||||
static const Field<PhotoModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(PhotoModel v) => v.playlistId;
|
||||
static const Field<PhotoModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(PhotoModel v) => v.images;
|
||||
static const Field<PhotoModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(PhotoModel v) => v.childCount;
|
||||
static const Field<PhotoModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(PhotoModel v) => v.primaryRatio;
|
||||
static const Field<PhotoModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(PhotoModel v) => v.userData;
|
||||
static const Field<PhotoModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDownload(PhotoModel v) => v.canDownload;
|
||||
static const Field<PhotoModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload);
|
||||
static bool? _$canDelete(PhotoModel v) => v.canDelete;
|
||||
static const Field<PhotoModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete);
|
||||
static dto.BaseItemKind? _$jellyType(PhotoModel v) => v.jellyType;
|
||||
static const Field<PhotoModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<PhotoModel> fields = const {
|
||||
#albumId: _f$albumId,
|
||||
#dateTaken: _f$dateTaken,
|
||||
#thumbnail: _f$thumbnail,
|
||||
#internalType: _f$internalType,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDownload: _f$canDownload,
|
||||
#canDelete: _f$canDelete,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'PhotoModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static PhotoModel _instantiate(DecodingData data) {
|
||||
return PhotoModel(
|
||||
albumId: data.dec(_f$albumId),
|
||||
dateTaken: data.dec(_f$dateTaken),
|
||||
thumbnail: data.dec(_f$thumbnail),
|
||||
internalType: data.dec(_f$internalType),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static PhotoModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<PhotoModel>(map);
|
||||
}
|
||||
|
||||
static PhotoModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<PhotoModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin PhotoModelMappable {
|
||||
String toJson() {
|
||||
return PhotoModelMapper.ensureInitialized()
|
||||
.encodeJson<PhotoModel>(this as PhotoModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return PhotoModelMapper.ensureInitialized()
|
||||
.encodeMap<PhotoModel>(this as PhotoModel);
|
||||
}
|
||||
|
||||
PhotoModelCopyWith<PhotoModel, PhotoModel, PhotoModel> get copyWith =>
|
||||
_PhotoModelCopyWithImpl(this as PhotoModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return PhotoModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as PhotoModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, PhotoModel, $Out> {
|
||||
PhotoModelCopyWith<$R, PhotoModel, $Out> get $asPhotoModel =>
|
||||
$base.as((v, t, t2) => _PhotoModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class PhotoModelCopyWith<$R, $In extends PhotoModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{String? albumId,
|
||||
DateTime? dateTaken,
|
||||
ImagesData? thumbnail,
|
||||
FladderItemType? internalType,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDownload,
|
||||
bool? canDelete,
|
||||
dto.BaseItemKind? jellyType});
|
||||
PhotoModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _PhotoModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, PhotoModel, $Out>
|
||||
implements PhotoModelCopyWith<$R, PhotoModel, $Out> {
|
||||
_PhotoModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<PhotoModel> $mapper =
|
||||
PhotoModelMapper.ensureInitialized();
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? albumId = $none,
|
||||
Object? dateTaken = $none,
|
||||
Object? thumbnail = $none,
|
||||
FladderItemType? internalType,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDownload = $none,
|
||||
Object? canDelete = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (albumId != $none) #albumId: albumId,
|
||||
if (dateTaken != $none) #dateTaken: dateTaken,
|
||||
if (thumbnail != $none) #thumbnail: thumbnail,
|
||||
if (internalType != null) #internalType: internalType,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
PhotoModel $make(CopyWithData data) => PhotoModel(
|
||||
albumId: data.get(#albumId, or: $value.albumId),
|
||||
dateTaken: data.get(#dateTaken, or: $value.dateTaken),
|
||||
thumbnail: data.get(#thumbnail, or: $value.thumbnail),
|
||||
internalType: data.get(#internalType, or: $value.internalType),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
PhotoModelCopyWith<$R2, PhotoModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_PhotoModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
98
lib/models/items/season_model.dart
Normal file
98
lib/models/items/season_model.dart
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'season_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class SeasonModel extends ItemBaseModel with SeasonModelMappable {
|
||||
final ImagesData? parentImages;
|
||||
final String seasonName;
|
||||
final List<EpisodeModel> episodes;
|
||||
final int episodeCount;
|
||||
final String seriesId;
|
||||
final String seriesName;
|
||||
const SeasonModel({
|
||||
required this.parentImages,
|
||||
required this.seasonName,
|
||||
this.episodes = const [],
|
||||
required this.episodeCount,
|
||||
required this.seriesId,
|
||||
required this.seriesName,
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
required super.canDelete,
|
||||
required super.canDownload,
|
||||
super.jellyType,
|
||||
});
|
||||
factory SeasonModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return SeasonModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.seasonId ?? item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
seasonName: item.seasonName ?? "",
|
||||
episodeCount: item.episodeCount ?? 0,
|
||||
parentImages: ImagesData.fromBaseItemParent(item, ref, primary: const Size(2000, 2000)),
|
||||
seriesId: item.seriesId ?? item.parentId ?? item.id ?? "",
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
seriesName: item.seriesName ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
EpisodeModel? get nextUp {
|
||||
return episodes.lastWhereOrNull((element) => element.userData.progress > 0) ??
|
||||
episodes.firstWhereOrNull((element) => element.userData.played == false);
|
||||
}
|
||||
|
||||
@override
|
||||
ImagesData? get getPosters => images ?? parentImages;
|
||||
|
||||
String localizedName(BuildContext context) => name.replaceFirst("Season", context.localized.season(1));
|
||||
|
||||
@override
|
||||
SeriesModel get parentBaseModel => SeriesModel(
|
||||
originalTitle: '',
|
||||
sortName: '',
|
||||
status: "",
|
||||
name: seriesName,
|
||||
id: parentId ?? "",
|
||||
playlistId: playlistId,
|
||||
overview: overview,
|
||||
parentId: parentId,
|
||||
images: images,
|
||||
childCount: childCount,
|
||||
primaryRatio: primaryRatio,
|
||||
userData: UserData(),
|
||||
);
|
||||
|
||||
static List<SeasonModel> seasonsFromDto(List<dto.BaseItemDto>? dto, Ref ref) {
|
||||
return dto?.map((e) => SeasonModel.fromBaseDto(e, ref)).toList() ?? [];
|
||||
}
|
||||
}
|
||||
287
lib/models/items/season_model.mapper.dart
Normal file
287
lib/models/items/season_model.mapper.dart
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'season_model.dart';
|
||||
|
||||
class SeasonModelMapper extends SubClassMapperBase<SeasonModel> {
|
||||
SeasonModelMapper._();
|
||||
|
||||
static SeasonModelMapper? _instance;
|
||||
static SeasonModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = SeasonModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
EpisodeModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'SeasonModel';
|
||||
|
||||
static ImagesData? _$parentImages(SeasonModel v) => v.parentImages;
|
||||
static const Field<SeasonModel, ImagesData> _f$parentImages =
|
||||
Field('parentImages', _$parentImages);
|
||||
static String _$seasonName(SeasonModel v) => v.seasonName;
|
||||
static const Field<SeasonModel, String> _f$seasonName =
|
||||
Field('seasonName', _$seasonName);
|
||||
static List<EpisodeModel> _$episodes(SeasonModel v) => v.episodes;
|
||||
static const Field<SeasonModel, List<EpisodeModel>> _f$episodes =
|
||||
Field('episodes', _$episodes, opt: true, def: const []);
|
||||
static int _$episodeCount(SeasonModel v) => v.episodeCount;
|
||||
static const Field<SeasonModel, int> _f$episodeCount =
|
||||
Field('episodeCount', _$episodeCount);
|
||||
static String _$seriesId(SeasonModel v) => v.seriesId;
|
||||
static const Field<SeasonModel, String> _f$seriesId =
|
||||
Field('seriesId', _$seriesId);
|
||||
static String _$seriesName(SeasonModel v) => v.seriesName;
|
||||
static const Field<SeasonModel, String> _f$seriesName =
|
||||
Field('seriesName', _$seriesName);
|
||||
static String _$name(SeasonModel v) => v.name;
|
||||
static const Field<SeasonModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(SeasonModel v) => v.id;
|
||||
static const Field<SeasonModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(SeasonModel v) => v.overview;
|
||||
static const Field<SeasonModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(SeasonModel v) => v.parentId;
|
||||
static const Field<SeasonModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(SeasonModel v) => v.playlistId;
|
||||
static const Field<SeasonModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(SeasonModel v) => v.images;
|
||||
static const Field<SeasonModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(SeasonModel v) => v.childCount;
|
||||
static const Field<SeasonModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(SeasonModel v) => v.primaryRatio;
|
||||
static const Field<SeasonModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(SeasonModel v) => v.userData;
|
||||
static const Field<SeasonModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDelete(SeasonModel v) => v.canDelete;
|
||||
static const Field<SeasonModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete);
|
||||
static bool? _$canDownload(SeasonModel v) => v.canDownload;
|
||||
static const Field<SeasonModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload);
|
||||
static dto.BaseItemKind? _$jellyType(SeasonModel v) => v.jellyType;
|
||||
static const Field<SeasonModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<SeasonModel> fields = const {
|
||||
#parentImages: _f$parentImages,
|
||||
#seasonName: _f$seasonName,
|
||||
#episodes: _f$episodes,
|
||||
#episodeCount: _f$episodeCount,
|
||||
#seriesId: _f$seriesId,
|
||||
#seriesName: _f$seriesName,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDelete: _f$canDelete,
|
||||
#canDownload: _f$canDownload,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'SeasonModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static SeasonModel _instantiate(DecodingData data) {
|
||||
return SeasonModel(
|
||||
parentImages: data.dec(_f$parentImages),
|
||||
seasonName: data.dec(_f$seasonName),
|
||||
episodes: data.dec(_f$episodes),
|
||||
episodeCount: data.dec(_f$episodeCount),
|
||||
seriesId: data.dec(_f$seriesId),
|
||||
seriesName: data.dec(_f$seriesName),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static SeasonModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<SeasonModel>(map);
|
||||
}
|
||||
|
||||
static SeasonModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<SeasonModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin SeasonModelMappable {
|
||||
String toJson() {
|
||||
return SeasonModelMapper.ensureInitialized()
|
||||
.encodeJson<SeasonModel>(this as SeasonModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return SeasonModelMapper.ensureInitialized()
|
||||
.encodeMap<SeasonModel>(this as SeasonModel);
|
||||
}
|
||||
|
||||
SeasonModelCopyWith<SeasonModel, SeasonModel, SeasonModel> get copyWith =>
|
||||
_SeasonModelCopyWithImpl(this as SeasonModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return SeasonModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as SeasonModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension SeasonModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, SeasonModel, $Out> {
|
||||
SeasonModelCopyWith<$R, SeasonModel, $Out> get $asSeasonModel =>
|
||||
$base.as((v, t, t2) => _SeasonModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class SeasonModelCopyWith<$R, $In extends SeasonModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, EpisodeModel,
|
||||
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>> get episodes;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{ImagesData? parentImages,
|
||||
String? seasonName,
|
||||
List<EpisodeModel>? episodes,
|
||||
int? episodeCount,
|
||||
String? seriesId,
|
||||
String? seriesName,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDelete,
|
||||
bool? canDownload,
|
||||
dto.BaseItemKind? jellyType});
|
||||
SeasonModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _SeasonModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, SeasonModel, $Out>
|
||||
implements SeasonModelCopyWith<$R, SeasonModel, $Out> {
|
||||
_SeasonModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<SeasonModel> $mapper =
|
||||
SeasonModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, EpisodeModel,
|
||||
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>>
|
||||
get episodes => ListCopyWith($value.episodes,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(episodes: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? parentImages = $none,
|
||||
String? seasonName,
|
||||
List<EpisodeModel>? episodes,
|
||||
int? episodeCount,
|
||||
String? seriesId,
|
||||
String? seriesName,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDelete = $none,
|
||||
Object? canDownload = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (parentImages != $none) #parentImages: parentImages,
|
||||
if (seasonName != null) #seasonName: seasonName,
|
||||
if (episodes != null) #episodes: episodes,
|
||||
if (episodeCount != null) #episodeCount: episodeCount,
|
||||
if (seriesId != null) #seriesId: seriesId,
|
||||
if (seriesName != null) #seriesName: seriesName,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
SeasonModel $make(CopyWithData data) => SeasonModel(
|
||||
parentImages: data.get(#parentImages, or: $value.parentImages),
|
||||
seasonName: data.get(#seasonName, or: $value.seasonName),
|
||||
episodes: data.get(#episodes, or: $value.episodes),
|
||||
episodeCount: data.get(#episodeCount, or: $value.episodeCount),
|
||||
seriesId: data.get(#seriesId, or: $value.seriesId),
|
||||
seriesName: data.get(#seriesName, or: $value.seriesName),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
SeasonModelCopyWith<$R2, SeasonModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_SeasonModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
96
lib/models/items/series_model.dart
Normal file
96
lib/models/items/series_model.dart
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import 'package:fladder/screens/details_screens/series_detail_screen.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
|
||||
part 'series_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class SeriesModel extends ItemBaseModel with SeriesModelMappable {
|
||||
final List<EpisodeModel>? availableEpisodes;
|
||||
final List<SeasonModel>? seasons;
|
||||
final String originalTitle;
|
||||
final String sortName;
|
||||
final String status;
|
||||
final List<ItemBaseModel> related;
|
||||
const SeriesModel({
|
||||
this.availableEpisodes,
|
||||
this.seasons,
|
||||
required this.originalTitle,
|
||||
required this.sortName,
|
||||
required this.status,
|
||||
this.related = const [],
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
super.canDownload,
|
||||
super.canDelete,
|
||||
super.jellyType,
|
||||
});
|
||||
|
||||
EpisodeModel? get nextUp => availableEpisodes?.nextUp ?? availableEpisodes?.firstOrNull;
|
||||
|
||||
@override
|
||||
String detailedName(BuildContext context) => name;
|
||||
|
||||
@override
|
||||
ItemBaseModel get parentBaseModel => this;
|
||||
|
||||
@override
|
||||
Widget get detailScreenWidget => SeriesDetailScreen(item: this);
|
||||
|
||||
@override
|
||||
bool get emptyShow => childCount == 0;
|
||||
|
||||
@override
|
||||
bool get playAble => userData.unPlayedItemCount != 0;
|
||||
|
||||
@override
|
||||
bool get identifiable => true;
|
||||
|
||||
@override
|
||||
bool get unWatched =>
|
||||
!userData.played && userData.progress <= 0 && userData.unPlayedItemCount == 0 && childCount != 0;
|
||||
|
||||
@override
|
||||
String get subText => overview.yearAired?.toString() ?? "";
|
||||
|
||||
List<ItemBaseModel> fetchAllShows() {
|
||||
return availableEpisodes?.map((e) => e).toList() ?? [];
|
||||
}
|
||||
|
||||
@override
|
||||
bool get syncAble => true;
|
||||
|
||||
factory SeriesModel.fromBaseDto(dto.BaseItemDto item, Ref ref) => SeriesModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref, getOriginalSize: true),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
originalTitle: item.originalTitle ?? "",
|
||||
sortName: item.sortName ?? "",
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
status: item.status ?? "Continuing",
|
||||
);
|
||||
}
|
||||
309
lib/models/items/series_model.mapper.dart
Normal file
309
lib/models/items/series_model.mapper.dart
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'series_model.dart';
|
||||
|
||||
class SeriesModelMapper extends SubClassMapperBase<SeriesModel> {
|
||||
SeriesModelMapper._();
|
||||
|
||||
static SeriesModelMapper? _instance;
|
||||
static SeriesModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = SeriesModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized().addSubMapper(_instance!);
|
||||
EpisodeModelMapper.ensureInitialized();
|
||||
SeasonModelMapper.ensureInitialized();
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
OverviewModelMapper.ensureInitialized();
|
||||
UserDataMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'SeriesModel';
|
||||
|
||||
static List<EpisodeModel>? _$availableEpisodes(SeriesModel v) =>
|
||||
v.availableEpisodes;
|
||||
static const Field<SeriesModel, List<EpisodeModel>> _f$availableEpisodes =
|
||||
Field('availableEpisodes', _$availableEpisodes, opt: true);
|
||||
static List<SeasonModel>? _$seasons(SeriesModel v) => v.seasons;
|
||||
static const Field<SeriesModel, List<SeasonModel>> _f$seasons =
|
||||
Field('seasons', _$seasons, opt: true);
|
||||
static String _$originalTitle(SeriesModel v) => v.originalTitle;
|
||||
static const Field<SeriesModel, String> _f$originalTitle =
|
||||
Field('originalTitle', _$originalTitle);
|
||||
static String _$sortName(SeriesModel v) => v.sortName;
|
||||
static const Field<SeriesModel, String> _f$sortName =
|
||||
Field('sortName', _$sortName);
|
||||
static String _$status(SeriesModel v) => v.status;
|
||||
static const Field<SeriesModel, String> _f$status = Field('status', _$status);
|
||||
static List<ItemBaseModel> _$related(SeriesModel v) => v.related;
|
||||
static const Field<SeriesModel, List<ItemBaseModel>> _f$related =
|
||||
Field('related', _$related, opt: true, def: const []);
|
||||
static String _$name(SeriesModel v) => v.name;
|
||||
static const Field<SeriesModel, String> _f$name = Field('name', _$name);
|
||||
static String _$id(SeriesModel v) => v.id;
|
||||
static const Field<SeriesModel, String> _f$id = Field('id', _$id);
|
||||
static OverviewModel _$overview(SeriesModel v) => v.overview;
|
||||
static const Field<SeriesModel, OverviewModel> _f$overview =
|
||||
Field('overview', _$overview);
|
||||
static String? _$parentId(SeriesModel v) => v.parentId;
|
||||
static const Field<SeriesModel, String> _f$parentId =
|
||||
Field('parentId', _$parentId);
|
||||
static String? _$playlistId(SeriesModel v) => v.playlistId;
|
||||
static const Field<SeriesModel, String> _f$playlistId =
|
||||
Field('playlistId', _$playlistId);
|
||||
static ImagesData? _$images(SeriesModel v) => v.images;
|
||||
static const Field<SeriesModel, ImagesData> _f$images =
|
||||
Field('images', _$images);
|
||||
static int? _$childCount(SeriesModel v) => v.childCount;
|
||||
static const Field<SeriesModel, int> _f$childCount =
|
||||
Field('childCount', _$childCount);
|
||||
static double? _$primaryRatio(SeriesModel v) => v.primaryRatio;
|
||||
static const Field<SeriesModel, double> _f$primaryRatio =
|
||||
Field('primaryRatio', _$primaryRatio);
|
||||
static UserData _$userData(SeriesModel v) => v.userData;
|
||||
static const Field<SeriesModel, UserData> _f$userData =
|
||||
Field('userData', _$userData);
|
||||
static bool? _$canDownload(SeriesModel v) => v.canDownload;
|
||||
static const Field<SeriesModel, bool> _f$canDownload =
|
||||
Field('canDownload', _$canDownload, opt: true);
|
||||
static bool? _$canDelete(SeriesModel v) => v.canDelete;
|
||||
static const Field<SeriesModel, bool> _f$canDelete =
|
||||
Field('canDelete', _$canDelete, opt: true);
|
||||
static dto.BaseItemKind? _$jellyType(SeriesModel v) => v.jellyType;
|
||||
static const Field<SeriesModel, dto.BaseItemKind> _f$jellyType =
|
||||
Field('jellyType', _$jellyType, opt: true);
|
||||
|
||||
@override
|
||||
final MappableFields<SeriesModel> fields = const {
|
||||
#availableEpisodes: _f$availableEpisodes,
|
||||
#seasons: _f$seasons,
|
||||
#originalTitle: _f$originalTitle,
|
||||
#sortName: _f$sortName,
|
||||
#status: _f$status,
|
||||
#related: _f$related,
|
||||
#name: _f$name,
|
||||
#id: _f$id,
|
||||
#overview: _f$overview,
|
||||
#parentId: _f$parentId,
|
||||
#playlistId: _f$playlistId,
|
||||
#images: _f$images,
|
||||
#childCount: _f$childCount,
|
||||
#primaryRatio: _f$primaryRatio,
|
||||
#userData: _f$userData,
|
||||
#canDownload: _f$canDownload,
|
||||
#canDelete: _f$canDelete,
|
||||
#jellyType: _f$jellyType,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
@override
|
||||
final String discriminatorKey = 'type';
|
||||
@override
|
||||
final dynamic discriminatorValue = 'SeriesModel';
|
||||
@override
|
||||
late final ClassMapperBase superMapper =
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
|
||||
static SeriesModel _instantiate(DecodingData data) {
|
||||
return SeriesModel(
|
||||
availableEpisodes: data.dec(_f$availableEpisodes),
|
||||
seasons: data.dec(_f$seasons),
|
||||
originalTitle: data.dec(_f$originalTitle),
|
||||
sortName: data.dec(_f$sortName),
|
||||
status: data.dec(_f$status),
|
||||
related: data.dec(_f$related),
|
||||
name: data.dec(_f$name),
|
||||
id: data.dec(_f$id),
|
||||
overview: data.dec(_f$overview),
|
||||
parentId: data.dec(_f$parentId),
|
||||
playlistId: data.dec(_f$playlistId),
|
||||
images: data.dec(_f$images),
|
||||
childCount: data.dec(_f$childCount),
|
||||
primaryRatio: data.dec(_f$primaryRatio),
|
||||
userData: data.dec(_f$userData),
|
||||
canDownload: data.dec(_f$canDownload),
|
||||
canDelete: data.dec(_f$canDelete),
|
||||
jellyType: data.dec(_f$jellyType));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static SeriesModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<SeriesModel>(map);
|
||||
}
|
||||
|
||||
static SeriesModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<SeriesModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin SeriesModelMappable {
|
||||
String toJson() {
|
||||
return SeriesModelMapper.ensureInitialized()
|
||||
.encodeJson<SeriesModel>(this as SeriesModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return SeriesModelMapper.ensureInitialized()
|
||||
.encodeMap<SeriesModel>(this as SeriesModel);
|
||||
}
|
||||
|
||||
SeriesModelCopyWith<SeriesModel, SeriesModel, SeriesModel> get copyWith =>
|
||||
_SeriesModelCopyWithImpl(this as SeriesModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return SeriesModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as SeriesModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension SeriesModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, SeriesModel, $Out> {
|
||||
SeriesModelCopyWith<$R, SeriesModel, $Out> get $asSeriesModel =>
|
||||
$base.as((v, t, t2) => _SeriesModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class SeriesModelCopyWith<$R, $In extends SeriesModel, $Out>
|
||||
implements ItemBaseModelCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, EpisodeModel,
|
||||
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>>?
|
||||
get availableEpisodes;
|
||||
ListCopyWith<$R, SeasonModel,
|
||||
SeasonModelCopyWith<$R, SeasonModel, SeasonModel>>? get seasons;
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get related;
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview;
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData;
|
||||
@override
|
||||
$R call(
|
||||
{List<EpisodeModel>? availableEpisodes,
|
||||
List<SeasonModel>? seasons,
|
||||
String? originalTitle,
|
||||
String? sortName,
|
||||
String? status,
|
||||
List<ItemBaseModel>? related,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
String? parentId,
|
||||
String? playlistId,
|
||||
ImagesData? images,
|
||||
int? childCount,
|
||||
double? primaryRatio,
|
||||
UserData? userData,
|
||||
bool? canDownload,
|
||||
bool? canDelete,
|
||||
dto.BaseItemKind? jellyType});
|
||||
SeriesModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _SeriesModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, SeriesModel, $Out>
|
||||
implements SeriesModelCopyWith<$R, SeriesModel, $Out> {
|
||||
_SeriesModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<SeriesModel> $mapper =
|
||||
SeriesModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, EpisodeModel,
|
||||
EpisodeModelCopyWith<$R, EpisodeModel, EpisodeModel>>?
|
||||
get availableEpisodes => $value.availableEpisodes != null
|
||||
? ListCopyWith($value.availableEpisodes!,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(availableEpisodes: v))
|
||||
: null;
|
||||
@override
|
||||
ListCopyWith<$R, SeasonModel,
|
||||
SeasonModelCopyWith<$R, SeasonModel, SeasonModel>>?
|
||||
get seasons => $value.seasons != null
|
||||
? ListCopyWith($value.seasons!, (v, t) => v.copyWith.$chain(t),
|
||||
(v) => call(seasons: v))
|
||||
: null;
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get related => ListCopyWith($value.related,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(related: v));
|
||||
@override
|
||||
OverviewModelCopyWith<$R, OverviewModel, OverviewModel> get overview =>
|
||||
$value.overview.copyWith.$chain((v) => call(overview: v));
|
||||
@override
|
||||
UserDataCopyWith<$R, UserData, UserData> get userData =>
|
||||
$value.userData.copyWith.$chain((v) => call(userData: v));
|
||||
@override
|
||||
$R call(
|
||||
{Object? availableEpisodes = $none,
|
||||
Object? seasons = $none,
|
||||
String? originalTitle,
|
||||
String? sortName,
|
||||
String? status,
|
||||
List<ItemBaseModel>? related,
|
||||
String? name,
|
||||
String? id,
|
||||
OverviewModel? overview,
|
||||
Object? parentId = $none,
|
||||
Object? playlistId = $none,
|
||||
Object? images = $none,
|
||||
Object? childCount = $none,
|
||||
Object? primaryRatio = $none,
|
||||
UserData? userData,
|
||||
Object? canDownload = $none,
|
||||
Object? canDelete = $none,
|
||||
Object? jellyType = $none}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (availableEpisodes != $none) #availableEpisodes: availableEpisodes,
|
||||
if (seasons != $none) #seasons: seasons,
|
||||
if (originalTitle != null) #originalTitle: originalTitle,
|
||||
if (sortName != null) #sortName: sortName,
|
||||
if (status != null) #status: status,
|
||||
if (related != null) #related: related,
|
||||
if (name != null) #name: name,
|
||||
if (id != null) #id: id,
|
||||
if (overview != null) #overview: overview,
|
||||
if (parentId != $none) #parentId: parentId,
|
||||
if (playlistId != $none) #playlistId: playlistId,
|
||||
if (images != $none) #images: images,
|
||||
if (childCount != $none) #childCount: childCount,
|
||||
if (primaryRatio != $none) #primaryRatio: primaryRatio,
|
||||
if (userData != null) #userData: userData,
|
||||
if (canDownload != $none) #canDownload: canDownload,
|
||||
if (canDelete != $none) #canDelete: canDelete,
|
||||
if (jellyType != $none) #jellyType: jellyType
|
||||
}));
|
||||
@override
|
||||
SeriesModel $make(CopyWithData data) => SeriesModel(
|
||||
availableEpisodes:
|
||||
data.get(#availableEpisodes, or: $value.availableEpisodes),
|
||||
seasons: data.get(#seasons, or: $value.seasons),
|
||||
originalTitle: data.get(#originalTitle, or: $value.originalTitle),
|
||||
sortName: data.get(#sortName, or: $value.sortName),
|
||||
status: data.get(#status, or: $value.status),
|
||||
related: data.get(#related, or: $value.related),
|
||||
name: data.get(#name, or: $value.name),
|
||||
id: data.get(#id, or: $value.id),
|
||||
overview: data.get(#overview, or: $value.overview),
|
||||
parentId: data.get(#parentId, or: $value.parentId),
|
||||
playlistId: data.get(#playlistId, or: $value.playlistId),
|
||||
images: data.get(#images, or: $value.images),
|
||||
childCount: data.get(#childCount, or: $value.childCount),
|
||||
primaryRatio: data.get(#primaryRatio, or: $value.primaryRatio),
|
||||
userData: data.get(#userData, or: $value.userData),
|
||||
canDownload: data.get(#canDownload, or: $value.canDownload),
|
||||
canDelete: data.get(#canDelete, or: $value.canDelete),
|
||||
jellyType: data.get(#jellyType, or: $value.jellyType));
|
||||
|
||||
@override
|
||||
SeriesModelCopyWith<$R2, SeriesModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_SeriesModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
61
lib/models/items/trick_play_model.dart
Normal file
61
lib/models/items/trick_play_model.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'trick_play_model.freezed.dart';
|
||||
part 'trick_play_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class TrickPlayModel with _$TrickPlayModel {
|
||||
factory TrickPlayModel({
|
||||
required int width,
|
||||
required int height,
|
||||
required int tileWidth,
|
||||
required int tileHeight,
|
||||
required int thumbnailCount,
|
||||
required Duration interval,
|
||||
@Default([]) List<String> images,
|
||||
}) = _TrickPlayModel;
|
||||
|
||||
const TrickPlayModel._();
|
||||
|
||||
int get imagesPerTile => tileWidth * tileHeight;
|
||||
|
||||
String? getTile(Duration position) {
|
||||
final int currentIndex = (position.inMilliseconds ~/ interval.inMilliseconds).clamp(0, thumbnailCount);
|
||||
final int indexOfTile = (currentIndex ~/ imagesPerTile).clamp(0, images.length);
|
||||
return images.elementAtOrNull(indexOfTile);
|
||||
}
|
||||
|
||||
Offset offset(Duration position) {
|
||||
final int currentIndex = (position.inMilliseconds ~/ interval.inMilliseconds).clamp(0, thumbnailCount - 1);
|
||||
final int tileIndex = currentIndex % imagesPerTile;
|
||||
final int column = tileIndex % tileWidth;
|
||||
final int row = tileIndex ~/ tileWidth;
|
||||
return Offset((width * column).toDouble(), (height * row).toDouble());
|
||||
}
|
||||
|
||||
static Map<String, TrickPlayModel> toTrickPlayMap(Map<String, dynamic> map) {
|
||||
Map<String, TrickPlayModel> newMap = {};
|
||||
final firstMap = (((map.entries.first as MapEntry).value as Map<String, dynamic>));
|
||||
newMap.addEntries(firstMap.entries.map(
|
||||
(e) {
|
||||
final map = e.value as Map<String, dynamic>;
|
||||
return MapEntry(
|
||||
e.key,
|
||||
TrickPlayModel(
|
||||
width: map['Width'] as int? ?? 0,
|
||||
height: map['Height'] as int? ?? 0,
|
||||
tileWidth: map['TileWidth'] as int? ?? 0,
|
||||
tileHeight: map['TileHeight'] as int? ?? 0,
|
||||
thumbnailCount: map['ThumbnailCount'] as int? ?? 0,
|
||||
interval: Duration(milliseconds: map['Interval'] as int? ?? 0),
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
return newMap;
|
||||
}
|
||||
|
||||
factory TrickPlayModel.fromJson(Map<String, dynamic> json) => _$TrickPlayModelFromJson(json);
|
||||
}
|
||||
297
lib/models/items/trick_play_model.freezed.dart
Normal file
297
lib/models/items/trick_play_model.freezed.dart
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'trick_play_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
TrickPlayModel _$TrickPlayModelFromJson(Map<String, dynamic> json) {
|
||||
return _TrickPlayModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$TrickPlayModel {
|
||||
int get width => throw _privateConstructorUsedError;
|
||||
int get height => throw _privateConstructorUsedError;
|
||||
int get tileWidth => throw _privateConstructorUsedError;
|
||||
int get tileHeight => throw _privateConstructorUsedError;
|
||||
int get thumbnailCount => throw _privateConstructorUsedError;
|
||||
Duration get interval => throw _privateConstructorUsedError;
|
||||
List<String> get images => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$TrickPlayModelCopyWith<TrickPlayModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $TrickPlayModelCopyWith<$Res> {
|
||||
factory $TrickPlayModelCopyWith(
|
||||
TrickPlayModel value, $Res Function(TrickPlayModel) then) =
|
||||
_$TrickPlayModelCopyWithImpl<$Res, TrickPlayModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int width,
|
||||
int height,
|
||||
int tileWidth,
|
||||
int tileHeight,
|
||||
int thumbnailCount,
|
||||
Duration interval,
|
||||
List<String> images});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$TrickPlayModelCopyWithImpl<$Res, $Val extends TrickPlayModel>
|
||||
implements $TrickPlayModelCopyWith<$Res> {
|
||||
_$TrickPlayModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? width = null,
|
||||
Object? height = null,
|
||||
Object? tileWidth = null,
|
||||
Object? tileHeight = null,
|
||||
Object? thumbnailCount = null,
|
||||
Object? interval = null,
|
||||
Object? images = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
width: null == width
|
||||
? _value.width
|
||||
: width // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
height: null == height
|
||||
? _value.height
|
||||
: height // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
tileWidth: null == tileWidth
|
||||
? _value.tileWidth
|
||||
: tileWidth // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
tileHeight: null == tileHeight
|
||||
? _value.tileHeight
|
||||
: tileHeight // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
thumbnailCount: null == thumbnailCount
|
||||
? _value.thumbnailCount
|
||||
: thumbnailCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
interval: null == interval
|
||||
? _value.interval
|
||||
: interval // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
images: null == images
|
||||
? _value.images
|
||||
: images // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$TrickPlayModelImplCopyWith<$Res>
|
||||
implements $TrickPlayModelCopyWith<$Res> {
|
||||
factory _$$TrickPlayModelImplCopyWith(_$TrickPlayModelImpl value,
|
||||
$Res Function(_$TrickPlayModelImpl) then) =
|
||||
__$$TrickPlayModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int width,
|
||||
int height,
|
||||
int tileWidth,
|
||||
int tileHeight,
|
||||
int thumbnailCount,
|
||||
Duration interval,
|
||||
List<String> images});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$TrickPlayModelImplCopyWithImpl<$Res>
|
||||
extends _$TrickPlayModelCopyWithImpl<$Res, _$TrickPlayModelImpl>
|
||||
implements _$$TrickPlayModelImplCopyWith<$Res> {
|
||||
__$$TrickPlayModelImplCopyWithImpl(
|
||||
_$TrickPlayModelImpl _value, $Res Function(_$TrickPlayModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? width = null,
|
||||
Object? height = null,
|
||||
Object? tileWidth = null,
|
||||
Object? tileHeight = null,
|
||||
Object? thumbnailCount = null,
|
||||
Object? interval = null,
|
||||
Object? images = null,
|
||||
}) {
|
||||
return _then(_$TrickPlayModelImpl(
|
||||
width: null == width
|
||||
? _value.width
|
||||
: width // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
height: null == height
|
||||
? _value.height
|
||||
: height // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
tileWidth: null == tileWidth
|
||||
? _value.tileWidth
|
||||
: tileWidth // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
tileHeight: null == tileHeight
|
||||
? _value.tileHeight
|
||||
: tileHeight // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
thumbnailCount: null == thumbnailCount
|
||||
? _value.thumbnailCount
|
||||
: thumbnailCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
interval: null == interval
|
||||
? _value.interval
|
||||
: interval // ignore: cast_nullable_to_non_nullable
|
||||
as Duration,
|
||||
images: null == images
|
||||
? _value._images
|
||||
: images // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$TrickPlayModelImpl extends _TrickPlayModel {
|
||||
_$TrickPlayModelImpl(
|
||||
{required this.width,
|
||||
required this.height,
|
||||
required this.tileWidth,
|
||||
required this.tileHeight,
|
||||
required this.thumbnailCount,
|
||||
required this.interval,
|
||||
final List<String> images = const []})
|
||||
: _images = images,
|
||||
super._();
|
||||
|
||||
factory _$TrickPlayModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$TrickPlayModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final int width;
|
||||
@override
|
||||
final int height;
|
||||
@override
|
||||
final int tileWidth;
|
||||
@override
|
||||
final int tileHeight;
|
||||
@override
|
||||
final int thumbnailCount;
|
||||
@override
|
||||
final Duration interval;
|
||||
final List<String> _images;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get images {
|
||||
if (_images is EqualUnmodifiableListView) return _images;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_images);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TrickPlayModel(width: $width, height: $height, tileWidth: $tileWidth, tileHeight: $tileHeight, thumbnailCount: $thumbnailCount, interval: $interval, images: $images)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$TrickPlayModelImpl &&
|
||||
(identical(other.width, width) || other.width == width) &&
|
||||
(identical(other.height, height) || other.height == height) &&
|
||||
(identical(other.tileWidth, tileWidth) ||
|
||||
other.tileWidth == tileWidth) &&
|
||||
(identical(other.tileHeight, tileHeight) ||
|
||||
other.tileHeight == tileHeight) &&
|
||||
(identical(other.thumbnailCount, thumbnailCount) ||
|
||||
other.thumbnailCount == thumbnailCount) &&
|
||||
(identical(other.interval, interval) ||
|
||||
other.interval == interval) &&
|
||||
const DeepCollectionEquality().equals(other._images, _images));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
width,
|
||||
height,
|
||||
tileWidth,
|
||||
tileHeight,
|
||||
thumbnailCount,
|
||||
interval,
|
||||
const DeepCollectionEquality().hash(_images));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$TrickPlayModelImplCopyWith<_$TrickPlayModelImpl> get copyWith =>
|
||||
__$$TrickPlayModelImplCopyWithImpl<_$TrickPlayModelImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$TrickPlayModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _TrickPlayModel extends TrickPlayModel {
|
||||
factory _TrickPlayModel(
|
||||
{required final int width,
|
||||
required final int height,
|
||||
required final int tileWidth,
|
||||
required final int tileHeight,
|
||||
required final int thumbnailCount,
|
||||
required final Duration interval,
|
||||
final List<String> images}) = _$TrickPlayModelImpl;
|
||||
_TrickPlayModel._() : super._();
|
||||
|
||||
factory _TrickPlayModel.fromJson(Map<String, dynamic> json) =
|
||||
_$TrickPlayModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
int get width;
|
||||
@override
|
||||
int get height;
|
||||
@override
|
||||
int get tileWidth;
|
||||
@override
|
||||
int get tileHeight;
|
||||
@override
|
||||
int get thumbnailCount;
|
||||
@override
|
||||
Duration get interval;
|
||||
@override
|
||||
List<String> get images;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$TrickPlayModelImplCopyWith<_$TrickPlayModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
33
lib/models/items/trick_play_model.g.dart
Normal file
33
lib/models/items/trick_play_model.g.dart
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'trick_play_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$TrickPlayModelImpl _$$TrickPlayModelImplFromJson(Map<String, dynamic> json) =>
|
||||
_$TrickPlayModelImpl(
|
||||
width: (json['width'] as num).toInt(),
|
||||
height: (json['height'] as num).toInt(),
|
||||
tileWidth: (json['tileWidth'] as num).toInt(),
|
||||
tileHeight: (json['tileHeight'] as num).toInt(),
|
||||
thumbnailCount: (json['thumbnailCount'] as num).toInt(),
|
||||
interval: Duration(microseconds: (json['interval'] as num).toInt()),
|
||||
images: (json['images'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$TrickPlayModelImplToJson(
|
||||
_$TrickPlayModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'width': instance.width,
|
||||
'height': instance.height,
|
||||
'tileWidth': instance.tileWidth,
|
||||
'tileHeight': instance.tileHeight,
|
||||
'thumbnailCount': instance.thumbnailCount,
|
||||
'interval': instance.interval.inMicroseconds,
|
||||
'images': instance.images,
|
||||
};
|
||||
61
lib/models/library_model.dart
Normal file
61
lib/models/library_model.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart' as enums;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/recommended_model.dart';
|
||||
|
||||
class LibraryModel {
|
||||
final bool loading;
|
||||
final String id;
|
||||
final String name;
|
||||
final List<PhotoModel> timelinePhotos;
|
||||
final List<ItemBaseModel> posters;
|
||||
final List<RecommendedModel> recommendations;
|
||||
final List<String> genres;
|
||||
final List<ItemBaseModel> latest;
|
||||
final List<ItemBaseModel> nextUp;
|
||||
final List<ItemBaseModel> favourites;
|
||||
final enums.BaseItemKind type;
|
||||
|
||||
LibraryModel({
|
||||
this.loading = false,
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.timelinePhotos = const [],
|
||||
this.posters = const [],
|
||||
this.recommendations = const [],
|
||||
this.genres = const [],
|
||||
this.latest = const [],
|
||||
this.nextUp = const [],
|
||||
this.favourites = const [],
|
||||
required this.type,
|
||||
});
|
||||
|
||||
LibraryModel copyWith({
|
||||
bool? loading,
|
||||
String? id,
|
||||
String? name,
|
||||
List<PhotoModel>? timelinePhotos,
|
||||
List<ItemBaseModel>? posters,
|
||||
List<RecommendedModel>? recommendations,
|
||||
List<String>? genres,
|
||||
List<ItemBaseModel>? latest,
|
||||
List<ItemBaseModel>? nextUp,
|
||||
List<ItemBaseModel>? favourites,
|
||||
enums.BaseItemKind? type,
|
||||
}) {
|
||||
return LibraryModel(
|
||||
loading: loading ?? this.loading,
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
timelinePhotos: timelinePhotos ?? this.timelinePhotos,
|
||||
posters: posters ?? this.posters,
|
||||
recommendations: recommendations ?? this.recommendations,
|
||||
genres: genres ?? this.genres,
|
||||
latest: latest ?? this.latest,
|
||||
nextUp: nextUp ?? this.nextUp,
|
||||
favourites: favourites ?? this.favourites,
|
||||
type: type ?? this.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
223
lib/models/library_search/library_search_model.dart
Normal file
223
lib/models/library_search/library_search_model.dart
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:dart_mappable/dart_mappable.dart';
|
||||
import 'package:fladder/util/list_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/models/view_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/map_bool_helper.dart';
|
||||
|
||||
part 'library_search_model.mapper.dart';
|
||||
|
||||
@MappableClass()
|
||||
class LibrarySearchModel with LibrarySearchModelMappable {
|
||||
final bool loading;
|
||||
final bool selecteMode;
|
||||
final String searchQuery;
|
||||
final List<ItemBaseModel> folderOverwrite;
|
||||
final Map<ViewModel, bool> views;
|
||||
final List<ItemBaseModel> posters;
|
||||
final List<ItemBaseModel> selectedPosters;
|
||||
final Map<ItemFilter, bool> filters;
|
||||
final Map<String, bool> genres;
|
||||
final Map<Studio, bool> studios;
|
||||
final Map<String, bool> tags;
|
||||
final Map<int, bool> years;
|
||||
final Map<String, bool> officialRatings;
|
||||
final Map<FladderItemType, bool> types;
|
||||
final SortingOptions sortingOption;
|
||||
final SortingOrder sortOrder;
|
||||
final bool favourites;
|
||||
final bool hideEmtpyShows;
|
||||
final bool recursive;
|
||||
final GroupBy groupBy;
|
||||
final Map<String, int> lastIndices;
|
||||
final Map<String, int> libraryItemCounts;
|
||||
final bool fetchingItems;
|
||||
|
||||
const LibrarySearchModel({
|
||||
this.loading = false,
|
||||
this.selecteMode = false,
|
||||
this.folderOverwrite = const [],
|
||||
this.searchQuery = "",
|
||||
this.views = const {},
|
||||
this.posters = const [],
|
||||
this.selectedPosters = const [],
|
||||
this.filters = const {
|
||||
ItemFilter.isplayed: false,
|
||||
ItemFilter.isunplayed: false,
|
||||
ItemFilter.isresumable: false,
|
||||
},
|
||||
this.genres = const {},
|
||||
this.studios = const {},
|
||||
this.tags = const {},
|
||||
this.years = const {},
|
||||
this.officialRatings = const {},
|
||||
this.types = const {
|
||||
FladderItemType.audio: false,
|
||||
FladderItemType.boxset: false,
|
||||
FladderItemType.book: false,
|
||||
FladderItemType.collectionFolder: false,
|
||||
FladderItemType.episode: false,
|
||||
FladderItemType.folder: false,
|
||||
FladderItemType.movie: true,
|
||||
FladderItemType.musicAlbum: false,
|
||||
FladderItemType.musicVideo: false,
|
||||
FladderItemType.photo: false,
|
||||
FladderItemType.person: false,
|
||||
FladderItemType.photoalbum: false,
|
||||
FladderItemType.series: true,
|
||||
FladderItemType.video: true,
|
||||
},
|
||||
this.favourites = false,
|
||||
this.sortingOption = SortingOptions.name,
|
||||
this.sortOrder = SortingOrder.ascending,
|
||||
this.hideEmtpyShows = true,
|
||||
this.recursive = false,
|
||||
this.groupBy = GroupBy.none,
|
||||
this.lastIndices = const {},
|
||||
this.libraryItemCounts = const {},
|
||||
this.fetchingItems = false,
|
||||
});
|
||||
|
||||
bool get hasActiveFilters {
|
||||
return genres.hasEnabled ||
|
||||
studios.hasEnabled ||
|
||||
tags.hasEnabled ||
|
||||
years.hasEnabled ||
|
||||
officialRatings.hasEnabled ||
|
||||
hideEmtpyShows ||
|
||||
filters.hasEnabled ||
|
||||
favourites ||
|
||||
searchQuery.isNotEmpty;
|
||||
}
|
||||
|
||||
int get totalItemCount {
|
||||
if (libraryItemCounts.isEmpty) return posters.length;
|
||||
int totalCount = 0;
|
||||
for (var item in libraryItemCounts.values) {
|
||||
totalCount += item;
|
||||
}
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
bool get allDoneFetching {
|
||||
if (libraryItemCounts.isEmpty) return false;
|
||||
if (libraryItemCounts.length != lastIndices.length) {
|
||||
return false;
|
||||
} else {
|
||||
for (var item in libraryItemCounts.entries) {
|
||||
if (lastIndices[item.key] != item.value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String searchBarTitle(BuildContext context) {
|
||||
if (folderOverwrite.isNotEmpty) {
|
||||
return "${context.localized.search} ${folderOverwrite.last.name}...";
|
||||
}
|
||||
return views.included.length == 1
|
||||
? "${context.localized.search} ${views.included.first.name}..."
|
||||
: "${context.localized.search} ${context.localized.library(2)}...";
|
||||
}
|
||||
|
||||
ItemBaseModel? get nestedCurrentItem => folderOverwrite.lastOrNull;
|
||||
|
||||
List<ItemBaseModel> get activePosters => selectedPosters.isNotEmpty ? selectedPosters : posters;
|
||||
|
||||
bool get showPlayButtons {
|
||||
if (totalItemCount == 0) return false;
|
||||
return types.included.isEmpty ||
|
||||
types.included.containsAny(
|
||||
{...FladderItemType.playable, FladderItemType.folder},
|
||||
);
|
||||
}
|
||||
|
||||
bool get showGalleryButtons {
|
||||
if (totalItemCount == 0) return false;
|
||||
return types.included.isEmpty ||
|
||||
types.included.containsAny(
|
||||
{...FladderItemType.galleryItem, FladderItemType.photoalbum, FladderItemType.folder},
|
||||
);
|
||||
}
|
||||
|
||||
LibrarySearchModel resetLazyLoad() {
|
||||
return copyWith(
|
||||
selectedPosters: [],
|
||||
lastIndices: const {},
|
||||
libraryItemCounts: const {},
|
||||
);
|
||||
}
|
||||
|
||||
LibrarySearchModel fullReset() {
|
||||
return copyWith(
|
||||
posters: [],
|
||||
selectedPosters: [],
|
||||
lastIndices: const {},
|
||||
libraryItemCounts: const {},
|
||||
);
|
||||
}
|
||||
|
||||
LibrarySearchModel setFiltersToDefault() {
|
||||
return copyWith(
|
||||
genres: const {},
|
||||
tags: const {},
|
||||
officialRatings: const {},
|
||||
years: const {},
|
||||
searchQuery: '',
|
||||
favourites: false,
|
||||
recursive: false,
|
||||
studios: const {},
|
||||
hideEmtpyShows: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant LibrarySearchModel other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.searchQuery == searchQuery &&
|
||||
listEquals(other.folderOverwrite, folderOverwrite) &&
|
||||
mapEquals(other.views, views) &&
|
||||
mapEquals(other.filters, filters) &&
|
||||
mapEquals(other.genres, genres) &&
|
||||
mapEquals(other.studios, studios) &&
|
||||
mapEquals(other.tags, tags) &&
|
||||
mapEquals(other.years, years) &&
|
||||
mapEquals(other.officialRatings, officialRatings) &&
|
||||
mapEquals(other.types, types) &&
|
||||
other.sortingOption == sortingOption &&
|
||||
other.sortOrder == sortOrder &&
|
||||
other.favourites == favourites &&
|
||||
other.recursive == recursive;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return searchQuery.hashCode ^
|
||||
folderOverwrite.hashCode ^
|
||||
views.hashCode ^
|
||||
posters.hashCode ^
|
||||
selectedPosters.hashCode ^
|
||||
filters.hashCode ^
|
||||
genres.hashCode ^
|
||||
studios.hashCode ^
|
||||
tags.hashCode ^
|
||||
years.hashCode ^
|
||||
officialRatings.hashCode ^
|
||||
types.hashCode ^
|
||||
sortingOption.hashCode ^
|
||||
sortOrder.hashCode ^
|
||||
favourites.hashCode ^
|
||||
recursive.hashCode;
|
||||
}
|
||||
}
|
||||
418
lib/models/library_search/library_search_model.mapper.dart
Normal file
418
lib/models/library_search/library_search_model.mapper.dart
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
|
||||
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
|
||||
|
||||
part of 'library_search_model.dart';
|
||||
|
||||
class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
|
||||
LibrarySearchModelMapper._();
|
||||
|
||||
static LibrarySearchModelMapper? _instance;
|
||||
static LibrarySearchModelMapper ensureInitialized() {
|
||||
if (_instance == null) {
|
||||
MapperContainer.globals.use(_instance = LibrarySearchModelMapper._());
|
||||
ItemBaseModelMapper.ensureInitialized();
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
@override
|
||||
final String id = 'LibrarySearchModel';
|
||||
|
||||
static bool _$loading(LibrarySearchModel v) => v.loading;
|
||||
static const Field<LibrarySearchModel, bool> _f$loading =
|
||||
Field('loading', _$loading, opt: true, def: false);
|
||||
static bool _$selecteMode(LibrarySearchModel v) => v.selecteMode;
|
||||
static const Field<LibrarySearchModel, bool> _f$selecteMode =
|
||||
Field('selecteMode', _$selecteMode, opt: true, def: false);
|
||||
static List<ItemBaseModel> _$folderOverwrite(LibrarySearchModel v) =>
|
||||
v.folderOverwrite;
|
||||
static const Field<LibrarySearchModel, List<ItemBaseModel>>
|
||||
_f$folderOverwrite =
|
||||
Field('folderOverwrite', _$folderOverwrite, opt: true, def: const []);
|
||||
static String _$searchQuery(LibrarySearchModel v) => v.searchQuery;
|
||||
static const Field<LibrarySearchModel, String> _f$searchQuery =
|
||||
Field('searchQuery', _$searchQuery, opt: true, def: "");
|
||||
static Map<ViewModel, bool> _$views(LibrarySearchModel v) => v.views;
|
||||
static const Field<LibrarySearchModel, Map<ViewModel, bool>> _f$views =
|
||||
Field('views', _$views, opt: true, def: const {});
|
||||
static List<ItemBaseModel> _$posters(LibrarySearchModel v) => v.posters;
|
||||
static const Field<LibrarySearchModel, List<ItemBaseModel>> _f$posters =
|
||||
Field('posters', _$posters, opt: true, def: const []);
|
||||
static List<ItemBaseModel> _$selectedPosters(LibrarySearchModel v) =>
|
||||
v.selectedPosters;
|
||||
static const Field<LibrarySearchModel, List<ItemBaseModel>>
|
||||
_f$selectedPosters =
|
||||
Field('selectedPosters', _$selectedPosters, opt: true, def: const []);
|
||||
static Map<ItemFilter, bool> _$filters(LibrarySearchModel v) => v.filters;
|
||||
static const Field<LibrarySearchModel, Map<ItemFilter, bool>> _f$filters =
|
||||
Field('filters', _$filters, opt: true, def: const {
|
||||
ItemFilter.isplayed: false,
|
||||
ItemFilter.isunplayed: false,
|
||||
ItemFilter.isresumable: false
|
||||
});
|
||||
static Map<String, bool> _$genres(LibrarySearchModel v) => v.genres;
|
||||
static const Field<LibrarySearchModel, Map<String, bool>> _f$genres =
|
||||
Field('genres', _$genres, opt: true, def: const {});
|
||||
static Map<Studio, bool> _$studios(LibrarySearchModel v) => v.studios;
|
||||
static const Field<LibrarySearchModel, Map<Studio, bool>> _f$studios =
|
||||
Field('studios', _$studios, opt: true, def: const {});
|
||||
static Map<String, bool> _$tags(LibrarySearchModel v) => v.tags;
|
||||
static const Field<LibrarySearchModel, Map<String, bool>> _f$tags =
|
||||
Field('tags', _$tags, opt: true, def: const {});
|
||||
static Map<int, bool> _$years(LibrarySearchModel v) => v.years;
|
||||
static const Field<LibrarySearchModel, Map<int, bool>> _f$years =
|
||||
Field('years', _$years, opt: true, def: const {});
|
||||
static Map<String, bool> _$officialRatings(LibrarySearchModel v) =>
|
||||
v.officialRatings;
|
||||
static const Field<LibrarySearchModel, Map<String, bool>> _f$officialRatings =
|
||||
Field('officialRatings', _$officialRatings, opt: true, def: const {});
|
||||
static Map<FladderItemType, bool> _$types(LibrarySearchModel v) => v.types;
|
||||
static const Field<LibrarySearchModel, Map<FladderItemType, bool>> _f$types =
|
||||
Field('types', _$types, opt: true, def: const {
|
||||
FladderItemType.audio: false,
|
||||
FladderItemType.boxset: false,
|
||||
FladderItemType.book: false,
|
||||
FladderItemType.collectionFolder: false,
|
||||
FladderItemType.episode: false,
|
||||
FladderItemType.folder: false,
|
||||
FladderItemType.movie: true,
|
||||
FladderItemType.musicAlbum: false,
|
||||
FladderItemType.musicVideo: false,
|
||||
FladderItemType.photo: false,
|
||||
FladderItemType.person: false,
|
||||
FladderItemType.photoalbum: false,
|
||||
FladderItemType.series: true,
|
||||
FladderItemType.video: true
|
||||
});
|
||||
static bool _$favourites(LibrarySearchModel v) => v.favourites;
|
||||
static const Field<LibrarySearchModel, bool> _f$favourites =
|
||||
Field('favourites', _$favourites, opt: true, def: false);
|
||||
static SortingOptions _$sortingOption(LibrarySearchModel v) =>
|
||||
v.sortingOption;
|
||||
static const Field<LibrarySearchModel, SortingOptions> _f$sortingOption =
|
||||
Field('sortingOption', _$sortingOption,
|
||||
opt: true, def: SortingOptions.name);
|
||||
static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder;
|
||||
static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder =
|
||||
Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending);
|
||||
static bool _$hideEmtpyShows(LibrarySearchModel v) => v.hideEmtpyShows;
|
||||
static const Field<LibrarySearchModel, bool> _f$hideEmtpyShows =
|
||||
Field('hideEmtpyShows', _$hideEmtpyShows, opt: true, def: true);
|
||||
static bool _$recursive(LibrarySearchModel v) => v.recursive;
|
||||
static const Field<LibrarySearchModel, bool> _f$recursive =
|
||||
Field('recursive', _$recursive, opt: true, def: false);
|
||||
static GroupBy _$groupBy(LibrarySearchModel v) => v.groupBy;
|
||||
static const Field<LibrarySearchModel, GroupBy> _f$groupBy =
|
||||
Field('groupBy', _$groupBy, opt: true, def: GroupBy.none);
|
||||
static Map<String, int> _$lastIndices(LibrarySearchModel v) => v.lastIndices;
|
||||
static const Field<LibrarySearchModel, Map<String, int>> _f$lastIndices =
|
||||
Field('lastIndices', _$lastIndices, opt: true, def: const {});
|
||||
static Map<String, int> _$libraryItemCounts(LibrarySearchModel v) =>
|
||||
v.libraryItemCounts;
|
||||
static const Field<LibrarySearchModel, Map<String, int>>
|
||||
_f$libraryItemCounts =
|
||||
Field('libraryItemCounts', _$libraryItemCounts, opt: true, def: const {});
|
||||
static bool _$fetchingItems(LibrarySearchModel v) => v.fetchingItems;
|
||||
static const Field<LibrarySearchModel, bool> _f$fetchingItems =
|
||||
Field('fetchingItems', _$fetchingItems, opt: true, def: false);
|
||||
|
||||
@override
|
||||
final MappableFields<LibrarySearchModel> fields = const {
|
||||
#loading: _f$loading,
|
||||
#selecteMode: _f$selecteMode,
|
||||
#folderOverwrite: _f$folderOverwrite,
|
||||
#searchQuery: _f$searchQuery,
|
||||
#views: _f$views,
|
||||
#posters: _f$posters,
|
||||
#selectedPosters: _f$selectedPosters,
|
||||
#filters: _f$filters,
|
||||
#genres: _f$genres,
|
||||
#studios: _f$studios,
|
||||
#tags: _f$tags,
|
||||
#years: _f$years,
|
||||
#officialRatings: _f$officialRatings,
|
||||
#types: _f$types,
|
||||
#favourites: _f$favourites,
|
||||
#sortingOption: _f$sortingOption,
|
||||
#sortOrder: _f$sortOrder,
|
||||
#hideEmtpyShows: _f$hideEmtpyShows,
|
||||
#recursive: _f$recursive,
|
||||
#groupBy: _f$groupBy,
|
||||
#lastIndices: _f$lastIndices,
|
||||
#libraryItemCounts: _f$libraryItemCounts,
|
||||
#fetchingItems: _f$fetchingItems,
|
||||
};
|
||||
@override
|
||||
final bool ignoreNull = true;
|
||||
|
||||
static LibrarySearchModel _instantiate(DecodingData data) {
|
||||
return LibrarySearchModel(
|
||||
loading: data.dec(_f$loading),
|
||||
selecteMode: data.dec(_f$selecteMode),
|
||||
folderOverwrite: data.dec(_f$folderOverwrite),
|
||||
searchQuery: data.dec(_f$searchQuery),
|
||||
views: data.dec(_f$views),
|
||||
posters: data.dec(_f$posters),
|
||||
selectedPosters: data.dec(_f$selectedPosters),
|
||||
filters: data.dec(_f$filters),
|
||||
genres: data.dec(_f$genres),
|
||||
studios: data.dec(_f$studios),
|
||||
tags: data.dec(_f$tags),
|
||||
years: data.dec(_f$years),
|
||||
officialRatings: data.dec(_f$officialRatings),
|
||||
types: data.dec(_f$types),
|
||||
favourites: data.dec(_f$favourites),
|
||||
sortingOption: data.dec(_f$sortingOption),
|
||||
sortOrder: data.dec(_f$sortOrder),
|
||||
hideEmtpyShows: data.dec(_f$hideEmtpyShows),
|
||||
recursive: data.dec(_f$recursive),
|
||||
groupBy: data.dec(_f$groupBy),
|
||||
lastIndices: data.dec(_f$lastIndices),
|
||||
libraryItemCounts: data.dec(_f$libraryItemCounts),
|
||||
fetchingItems: data.dec(_f$fetchingItems));
|
||||
}
|
||||
|
||||
@override
|
||||
final Function instantiate = _instantiate;
|
||||
|
||||
static LibrarySearchModel fromMap(Map<String, dynamic> map) {
|
||||
return ensureInitialized().decodeMap<LibrarySearchModel>(map);
|
||||
}
|
||||
|
||||
static LibrarySearchModel fromJson(String json) {
|
||||
return ensureInitialized().decodeJson<LibrarySearchModel>(json);
|
||||
}
|
||||
}
|
||||
|
||||
mixin LibrarySearchModelMappable {
|
||||
String toJson() {
|
||||
return LibrarySearchModelMapper.ensureInitialized()
|
||||
.encodeJson<LibrarySearchModel>(this as LibrarySearchModel);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return LibrarySearchModelMapper.ensureInitialized()
|
||||
.encodeMap<LibrarySearchModel>(this as LibrarySearchModel);
|
||||
}
|
||||
|
||||
LibrarySearchModelCopyWith<LibrarySearchModel, LibrarySearchModel,
|
||||
LibrarySearchModel>
|
||||
get copyWith => _LibrarySearchModelCopyWithImpl(
|
||||
this as LibrarySearchModel, $identity, $identity);
|
||||
@override
|
||||
String toString() {
|
||||
return LibrarySearchModelMapper.ensureInitialized()
|
||||
.stringifyValue(this as LibrarySearchModel);
|
||||
}
|
||||
}
|
||||
|
||||
extension LibrarySearchModelValueCopy<$R, $Out>
|
||||
on ObjectCopyWith<$R, LibrarySearchModel, $Out> {
|
||||
LibrarySearchModelCopyWith<$R, LibrarySearchModel, $Out>
|
||||
get $asLibrarySearchModel =>
|
||||
$base.as((v, t, t2) => _LibrarySearchModelCopyWithImpl(v, t, t2));
|
||||
}
|
||||
|
||||
abstract class LibrarySearchModelCopyWith<$R, $In extends LibrarySearchModel,
|
||||
$Out> implements ClassCopyWith<$R, $In, $Out> {
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get folderOverwrite;
|
||||
MapCopyWith<$R, ViewModel, bool, ObjectCopyWith<$R, bool, bool>> get views;
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get posters;
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get selectedPosters;
|
||||
MapCopyWith<$R, ItemFilter, bool, ObjectCopyWith<$R, bool, bool>> get filters;
|
||||
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get genres;
|
||||
MapCopyWith<$R, Studio, bool, ObjectCopyWith<$R, bool, bool>> get studios;
|
||||
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get tags;
|
||||
MapCopyWith<$R, int, bool, ObjectCopyWith<$R, bool, bool>> get years;
|
||||
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>>
|
||||
get officialRatings;
|
||||
MapCopyWith<$R, FladderItemType, bool, ObjectCopyWith<$R, bool, bool>>
|
||||
get types;
|
||||
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get lastIndices;
|
||||
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>>
|
||||
get libraryItemCounts;
|
||||
$R call(
|
||||
{bool? loading,
|
||||
bool? selecteMode,
|
||||
List<ItemBaseModel>? folderOverwrite,
|
||||
String? searchQuery,
|
||||
Map<ViewModel, bool>? views,
|
||||
List<ItemBaseModel>? posters,
|
||||
List<ItemBaseModel>? selectedPosters,
|
||||
Map<ItemFilter, bool>? filters,
|
||||
Map<String, bool>? genres,
|
||||
Map<Studio, bool>? studios,
|
||||
Map<String, bool>? tags,
|
||||
Map<int, bool>? years,
|
||||
Map<String, bool>? officialRatings,
|
||||
Map<FladderItemType, bool>? types,
|
||||
bool? favourites,
|
||||
SortingOptions? sortingOption,
|
||||
SortingOrder? sortOrder,
|
||||
bool? hideEmtpyShows,
|
||||
bool? recursive,
|
||||
GroupBy? groupBy,
|
||||
Map<String, int>? lastIndices,
|
||||
Map<String, int>? libraryItemCounts,
|
||||
bool? fetchingItems});
|
||||
LibrarySearchModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t);
|
||||
}
|
||||
|
||||
class _LibrarySearchModelCopyWithImpl<$R, $Out>
|
||||
extends ClassCopyWithBase<$R, LibrarySearchModel, $Out>
|
||||
implements LibrarySearchModelCopyWith<$R, LibrarySearchModel, $Out> {
|
||||
_LibrarySearchModelCopyWithImpl(super.value, super.then, super.then2);
|
||||
|
||||
@override
|
||||
late final ClassMapperBase<LibrarySearchModel> $mapper =
|
||||
LibrarySearchModelMapper.ensureInitialized();
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get folderOverwrite => ListCopyWith($value.folderOverwrite,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(folderOverwrite: v));
|
||||
@override
|
||||
MapCopyWith<$R, ViewModel, bool, ObjectCopyWith<$R, bool, bool>> get views =>
|
||||
MapCopyWith($value.views, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(views: v));
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get posters => ListCopyWith($value.posters,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(posters: v));
|
||||
@override
|
||||
ListCopyWith<$R, ItemBaseModel,
|
||||
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
|
||||
get selectedPosters => ListCopyWith($value.selectedPosters,
|
||||
(v, t) => v.copyWith.$chain(t), (v) => call(selectedPosters: v));
|
||||
@override
|
||||
MapCopyWith<$R, ItemFilter, bool, ObjectCopyWith<$R, bool, bool>>
|
||||
get filters => MapCopyWith($value.filters,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(filters: v));
|
||||
@override
|
||||
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get genres =>
|
||||
MapCopyWith($value.genres, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(genres: v));
|
||||
@override
|
||||
MapCopyWith<$R, Studio, bool, ObjectCopyWith<$R, bool, bool>> get studios =>
|
||||
MapCopyWith($value.studios, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(studios: v));
|
||||
@override
|
||||
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get tags =>
|
||||
MapCopyWith($value.tags, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(tags: v));
|
||||
@override
|
||||
MapCopyWith<$R, int, bool, ObjectCopyWith<$R, bool, bool>> get years =>
|
||||
MapCopyWith($value.years, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(years: v));
|
||||
@override
|
||||
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>>
|
||||
get officialRatings => MapCopyWith(
|
||||
$value.officialRatings,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(officialRatings: v));
|
||||
@override
|
||||
MapCopyWith<$R, FladderItemType, bool, ObjectCopyWith<$R, bool, bool>>
|
||||
get types => MapCopyWith($value.types,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(types: v));
|
||||
@override
|
||||
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get lastIndices =>
|
||||
MapCopyWith($value.lastIndices, (v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(lastIndices: v));
|
||||
@override
|
||||
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>>
|
||||
get libraryItemCounts => MapCopyWith(
|
||||
$value.libraryItemCounts,
|
||||
(v, t) => ObjectCopyWith(v, $identity, t),
|
||||
(v) => call(libraryItemCounts: v));
|
||||
@override
|
||||
$R call(
|
||||
{bool? loading,
|
||||
bool? selecteMode,
|
||||
List<ItemBaseModel>? folderOverwrite,
|
||||
String? searchQuery,
|
||||
Map<ViewModel, bool>? views,
|
||||
List<ItemBaseModel>? posters,
|
||||
List<ItemBaseModel>? selectedPosters,
|
||||
Map<ItemFilter, bool>? filters,
|
||||
Map<String, bool>? genres,
|
||||
Map<Studio, bool>? studios,
|
||||
Map<String, bool>? tags,
|
||||
Map<int, bool>? years,
|
||||
Map<String, bool>? officialRatings,
|
||||
Map<FladderItemType, bool>? types,
|
||||
bool? favourites,
|
||||
SortingOptions? sortingOption,
|
||||
SortingOrder? sortOrder,
|
||||
bool? hideEmtpyShows,
|
||||
bool? recursive,
|
||||
GroupBy? groupBy,
|
||||
Map<String, int>? lastIndices,
|
||||
Map<String, int>? libraryItemCounts,
|
||||
bool? fetchingItems}) =>
|
||||
$apply(FieldCopyWithData({
|
||||
if (loading != null) #loading: loading,
|
||||
if (selecteMode != null) #selecteMode: selecteMode,
|
||||
if (folderOverwrite != null) #folderOverwrite: folderOverwrite,
|
||||
if (searchQuery != null) #searchQuery: searchQuery,
|
||||
if (views != null) #views: views,
|
||||
if (posters != null) #posters: posters,
|
||||
if (selectedPosters != null) #selectedPosters: selectedPosters,
|
||||
if (filters != null) #filters: filters,
|
||||
if (genres != null) #genres: genres,
|
||||
if (studios != null) #studios: studios,
|
||||
if (tags != null) #tags: tags,
|
||||
if (years != null) #years: years,
|
||||
if (officialRatings != null) #officialRatings: officialRatings,
|
||||
if (types != null) #types: types,
|
||||
if (favourites != null) #favourites: favourites,
|
||||
if (sortingOption != null) #sortingOption: sortingOption,
|
||||
if (sortOrder != null) #sortOrder: sortOrder,
|
||||
if (hideEmtpyShows != null) #hideEmtpyShows: hideEmtpyShows,
|
||||
if (recursive != null) #recursive: recursive,
|
||||
if (groupBy != null) #groupBy: groupBy,
|
||||
if (lastIndices != null) #lastIndices: lastIndices,
|
||||
if (libraryItemCounts != null) #libraryItemCounts: libraryItemCounts,
|
||||
if (fetchingItems != null) #fetchingItems: fetchingItems
|
||||
}));
|
||||
@override
|
||||
LibrarySearchModel $make(CopyWithData data) => LibrarySearchModel(
|
||||
loading: data.get(#loading, or: $value.loading),
|
||||
selecteMode: data.get(#selecteMode, or: $value.selecteMode),
|
||||
folderOverwrite: data.get(#folderOverwrite, or: $value.folderOverwrite),
|
||||
searchQuery: data.get(#searchQuery, or: $value.searchQuery),
|
||||
views: data.get(#views, or: $value.views),
|
||||
posters: data.get(#posters, or: $value.posters),
|
||||
selectedPosters: data.get(#selectedPosters, or: $value.selectedPosters),
|
||||
filters: data.get(#filters, or: $value.filters),
|
||||
genres: data.get(#genres, or: $value.genres),
|
||||
studios: data.get(#studios, or: $value.studios),
|
||||
tags: data.get(#tags, or: $value.tags),
|
||||
years: data.get(#years, or: $value.years),
|
||||
officialRatings: data.get(#officialRatings, or: $value.officialRatings),
|
||||
types: data.get(#types, or: $value.types),
|
||||
favourites: data.get(#favourites, or: $value.favourites),
|
||||
sortingOption: data.get(#sortingOption, or: $value.sortingOption),
|
||||
sortOrder: data.get(#sortOrder, or: $value.sortOrder),
|
||||
hideEmtpyShows: data.get(#hideEmtpyShows, or: $value.hideEmtpyShows),
|
||||
recursive: data.get(#recursive, or: $value.recursive),
|
||||
groupBy: data.get(#groupBy, or: $value.groupBy),
|
||||
lastIndices: data.get(#lastIndices, or: $value.lastIndices),
|
||||
libraryItemCounts:
|
||||
data.get(#libraryItemCounts, or: $value.libraryItemCounts),
|
||||
fetchingItems: data.get(#fetchingItems, or: $value.fetchingItems));
|
||||
|
||||
@override
|
||||
LibrarySearchModelCopyWith<$R2, LibrarySearchModel, $Out2> $chain<$R2, $Out2>(
|
||||
Then<$Out2, $R2> t) =>
|
||||
_LibrarySearchModelCopyWithImpl($value, $cast, t);
|
||||
}
|
||||
127
lib/models/library_search/library_search_options.dart
Normal file
127
lib/models/library_search/library_search_options.dart
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum SortingOptions {
|
||||
name([ItemSortBy.name]),
|
||||
communityRating([ItemSortBy.communityrating]),
|
||||
// criticsRating([ItemSortBy.criticrating]),
|
||||
parentalRating([ItemSortBy.officialrating]),
|
||||
dateAdded([ItemSortBy.datecreated]),
|
||||
dateLastContentAdded([ItemSortBy.datelastcontentadded]),
|
||||
favorite([ItemSortBy.isfavoriteorliked]),
|
||||
datePlayed([ItemSortBy.dateplayed]),
|
||||
folders([ItemSortBy.isfolder]),
|
||||
playCount([ItemSortBy.playcount]),
|
||||
releaseDate([ItemSortBy.productionyear, ItemSortBy.premieredate]),
|
||||
runTime([ItemSortBy.runtime]),
|
||||
random([ItemSortBy.random]);
|
||||
|
||||
const SortingOptions(this.value);
|
||||
final List<ItemSortBy> value;
|
||||
|
||||
List<ItemSortBy> get toSortBy => [...value, ItemSortBy.name];
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
name => context.localized.name,
|
||||
communityRating => context.localized.communityRating,
|
||||
parentalRating => context.localized.parentalRating,
|
||||
dateAdded => context.localized.dateAdded,
|
||||
dateLastContentAdded => context.localized.dateLastContentAdded,
|
||||
favorite => context.localized.favorite,
|
||||
datePlayed => context.localized.datePlayed,
|
||||
folders => context.localized.folders,
|
||||
playCount => context.localized.playCount,
|
||||
releaseDate => context.localized.releaseDate,
|
||||
runTime => context.localized.runTime,
|
||||
random => context.localized.random,
|
||||
};
|
||||
}
|
||||
|
||||
enum GroupBy {
|
||||
none,
|
||||
name,
|
||||
genres,
|
||||
dateAdded,
|
||||
tags,
|
||||
releaseDate,
|
||||
rating,
|
||||
type;
|
||||
|
||||
String value(BuildContext context) => switch (this) {
|
||||
GroupBy.none => context.localized.none,
|
||||
GroupBy.name => context.localized.name,
|
||||
GroupBy.genres => context.localized.genre(1),
|
||||
GroupBy.dateAdded => context.localized.dateAdded,
|
||||
GroupBy.tags => context.localized.tag(1),
|
||||
GroupBy.releaseDate => context.localized.releaseDate,
|
||||
GroupBy.rating => context.localized.rating(1),
|
||||
GroupBy.type => context.localized.type(1),
|
||||
};
|
||||
}
|
||||
|
||||
enum SortingOrder {
|
||||
ascending,
|
||||
descending;
|
||||
|
||||
SortOrder get sortOrder => switch (this) {
|
||||
ascending => SortOrder.ascending,
|
||||
descending => SortOrder.descending,
|
||||
};
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
ascending => context.localized.ascending,
|
||||
descending => context.localized.descending,
|
||||
};
|
||||
}
|
||||
|
||||
extension ItemFilterExtension on ItemFilter {
|
||||
String label(BuildContext context) {
|
||||
return switch (this) {
|
||||
ItemFilter.isplayed => context.localized.played,
|
||||
ItemFilter.isunplayed => context.localized.unPlayed,
|
||||
ItemFilter.isresumable => context.localized.resumable,
|
||||
_ => "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
int sortItems(ItemBaseModel a, ItemBaseModel b, SortingOptions sortingOption, SortingOrder sortingOrder) {
|
||||
for (var sortBy in sortingOption.toSortBy) {
|
||||
int comparison = 0;
|
||||
switch (sortBy) {
|
||||
case ItemSortBy.communityrating:
|
||||
comparison = (a.overview.communityRating ?? 0).compareTo(b.overview.communityRating ?? 0);
|
||||
break;
|
||||
case ItemSortBy.isfavoriteorliked:
|
||||
comparison = a.userData.isFavourite == b.userData.isFavourite
|
||||
? 0
|
||||
: a.userData.isFavourite
|
||||
? 1
|
||||
: -1;
|
||||
break;
|
||||
case ItemSortBy.dateplayed:
|
||||
comparison = (a.userData.lastPlayed ?? DateTime(0)).compareTo(b.userData.lastPlayed ?? DateTime(0));
|
||||
break;
|
||||
case ItemSortBy.playcount:
|
||||
comparison = a.userData.playCount.compareTo(b.userData.playCount);
|
||||
break;
|
||||
case ItemSortBy.premieredate:
|
||||
case ItemSortBy.productionyear:
|
||||
comparison = (a.overview.productionYear ?? 0).compareTo(b.overview.productionYear ?? 0);
|
||||
break;
|
||||
case ItemSortBy.runtime:
|
||||
comparison = (a.overview.runTime ?? Duration.zero).compareTo(b.overview.runTime ?? Duration.zero);
|
||||
break;
|
||||
default:
|
||||
comparison = a.name.compareTo(b.name);
|
||||
}
|
||||
if (comparison != 0) {
|
||||
return sortingOrder == SortingOrder.ascending ? comparison : -comparison;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
26
lib/models/login_screen_model.dart
Normal file
26
lib/models/login_screen_model.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:fladder/models/account_model.dart';
|
||||
import 'package:fladder/models/credentials_model.dart';
|
||||
|
||||
class LoginScreenModel {
|
||||
final List<AccountModel> accounts;
|
||||
final CredentialsModel tempCredentials;
|
||||
final bool loading;
|
||||
LoginScreenModel({
|
||||
required this.accounts,
|
||||
required this.tempCredentials,
|
||||
required this.loading,
|
||||
});
|
||||
|
||||
LoginScreenModel copyWith({
|
||||
List<AccountModel>? accounts,
|
||||
CredentialsModel? tempCredentials,
|
||||
bool? loading,
|
||||
}) {
|
||||
return LoginScreenModel(
|
||||
accounts: accounts ?? this.accounts,
|
||||
tempCredentials: tempCredentials ?? this.tempCredentials,
|
||||
loading: loading ?? this.loading,
|
||||
);
|
||||
}
|
||||
}
|
||||
52
lib/models/media_playback_model.dart
Normal file
52
lib/models/media_playback_model.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
enum VideoPlayerState {
|
||||
minimized,
|
||||
fullScreen,
|
||||
disposed,
|
||||
}
|
||||
|
||||
class MediaPlaybackModel {
|
||||
final VideoPlayerState state;
|
||||
final bool playing;
|
||||
final Duration position;
|
||||
final Duration lastPosition;
|
||||
final Duration duration;
|
||||
final Duration buffer;
|
||||
final bool completed;
|
||||
final bool errorPlaying;
|
||||
final bool buffering;
|
||||
MediaPlaybackModel({
|
||||
this.state = VideoPlayerState.disposed,
|
||||
this.playing = false,
|
||||
this.position = Duration.zero,
|
||||
this.lastPosition = Duration.zero,
|
||||
this.duration = Duration.zero,
|
||||
this.buffer = Duration.zero,
|
||||
this.completed = false,
|
||||
this.errorPlaying = false,
|
||||
this.buffering = false,
|
||||
});
|
||||
|
||||
MediaPlaybackModel copyWith({
|
||||
VideoPlayerState? state,
|
||||
bool? playing,
|
||||
Duration? position,
|
||||
Duration? lastPosition,
|
||||
Duration? duration,
|
||||
Duration? buffer,
|
||||
bool? completed,
|
||||
bool? errorPlaying,
|
||||
bool? buffering,
|
||||
}) {
|
||||
return MediaPlaybackModel(
|
||||
state: state ?? this.state,
|
||||
playing: playing ?? this.playing,
|
||||
position: position ?? this.position,
|
||||
lastPosition: lastPosition ?? this.lastPosition,
|
||||
duration: duration ?? this.duration,
|
||||
buffer: buffer ?? this.buffer,
|
||||
completed: completed ?? this.completed,
|
||||
errorPlaying: errorPlaying ?? this.errorPlaying,
|
||||
buffering: buffering ?? this.buffering,
|
||||
);
|
||||
}
|
||||
}
|
||||
209
lib/models/playback/direct_playback_model.dart
Normal file
209
lib/models/playback/direct_playback_model.dart
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/util/list_extensions.dart';
|
||||
import 'package:fladder/wrappers/media_control_wrapper.dart'
|
||||
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
|
||||
class DirectPlaybackModel implements PlaybackModel {
|
||||
DirectPlaybackModel({
|
||||
required this.item,
|
||||
required this.media,
|
||||
required this.playbackInfo,
|
||||
this.mediaStreams,
|
||||
this.introSkipModel,
|
||||
this.chapters,
|
||||
this.trickPlay,
|
||||
this.queue = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
final ItemBaseModel item;
|
||||
|
||||
@override
|
||||
final Media? media;
|
||||
|
||||
@override
|
||||
final PlaybackInfoResponse playbackInfo;
|
||||
|
||||
@override
|
||||
final MediaStreamsModel? mediaStreams;
|
||||
|
||||
@override
|
||||
final IntroOutSkipModel? introSkipModel;
|
||||
|
||||
@override
|
||||
final List<Chapter>? chapters;
|
||||
|
||||
@override
|
||||
final TrickPlayModel? trickPlay;
|
||||
|
||||
@override
|
||||
ItemBaseModel? get nextVideo => queue.nextOrNull(item);
|
||||
|
||||
@override
|
||||
ItemBaseModel? get previousVideo => queue.previousOrNull(item);
|
||||
|
||||
@override
|
||||
Future<Duration>? startDuration() async => item.userData.playBackPosition;
|
||||
|
||||
@override
|
||||
List<SubStreamModel> get subStreams => [SubStreamModel.no(), ...mediaStreams?.subStreams ?? []];
|
||||
|
||||
List<QueueItem> get itemsInQueue =>
|
||||
queue.mapIndexed((index, element) => QueueItem(id: element.id, playlistItemId: "playlistItem$index")).toList();
|
||||
|
||||
@override
|
||||
Future<DirectPlaybackModel> setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async {
|
||||
final wantedSubtitle =
|
||||
model ?? subStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultSubStreamIndex);
|
||||
if (wantedSubtitle == null) return this;
|
||||
if (wantedSubtitle.index == SubStreamModel.no().index) {
|
||||
await player.setSubtitleTrack(SubtitleTrack.no());
|
||||
} else {
|
||||
final subTracks = player.subTracks.getRange(2, player.subTracks.length).toList();
|
||||
final index = subStreams.sublist(1).indexWhere((element) => element.id == wantedSubtitle.id);
|
||||
final subTrack = subTracks.elementAtOrNull(index);
|
||||
if (wantedSubtitle.isExternal && wantedSubtitle.url != null && subTrack == null) {
|
||||
await player.setSubtitleTrack(SubtitleTrack.uri(wantedSubtitle.url!));
|
||||
} else if (subTrack != null) {
|
||||
await player.setSubtitleTrack(subTrack);
|
||||
}
|
||||
}
|
||||
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultSubStreamIndex: wantedSubtitle.index));
|
||||
}
|
||||
|
||||
@override
|
||||
List<AudioStreamModel> get audioStreams => [AudioStreamModel.no(), ...mediaStreams?.audioStreams ?? []];
|
||||
|
||||
@override
|
||||
Future<DirectPlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) async {
|
||||
final wantedAudioStream =
|
||||
model ?? audioStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultAudioStreamIndex);
|
||||
if (wantedAudioStream == null) return this;
|
||||
if (wantedAudioStream.index == AudioStreamModel.no().index) {
|
||||
await player.setAudioTrack(AudioTrack.no());
|
||||
} else {
|
||||
final audioTracks = player.audioTracks.getRange(2, player.audioTracks.length).toList();
|
||||
final audioTrack = audioTracks.elementAtOrNull(audioStreams.indexOf(wantedAudioStream) - 1);
|
||||
if (audioTrack != null) {
|
||||
await player.setAudioTrack(audioTrack);
|
||||
}
|
||||
}
|
||||
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultAudioStreamIndex: wantedAudioStream.index));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) async {
|
||||
await ref.read(jellyApiProvider).sessionsPlayingPost(
|
||||
body: PlaybackStartInfo(
|
||||
canSeek: true,
|
||||
itemId: item.id,
|
||||
mediaSourceId: item.id,
|
||||
playSessionId: playbackInfo.playSessionId,
|
||||
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
|
||||
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
|
||||
volumeLevel: 100,
|
||||
playbackStartTimeTicks: position.toRuntimeTicks,
|
||||
playMethod: PlayMethod.directplay,
|
||||
isMuted: false,
|
||||
isPaused: false,
|
||||
repeatMode: RepeatMode.repeatall,
|
||||
nowPlayingQueue: itemsInQueue,
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) async {
|
||||
ref.read(playBackModel.notifier).update((state) => null);
|
||||
|
||||
await ref.read(jellyApiProvider).sessionsPlayingStoppedPost(
|
||||
body: PlaybackStopInfo(
|
||||
itemId: item.id,
|
||||
mediaSourceId: item.id,
|
||||
playSessionId: playbackInfo.playSessionId,
|
||||
positionTicks: position.toRuntimeTicks,
|
||||
failed: false,
|
||||
nowPlayingQueue: itemsInQueue,
|
||||
),
|
||||
totalDuration: totalDuration,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async {
|
||||
final api = ref.read(jellyApiProvider);
|
||||
|
||||
//Check for newly generated scrubImages
|
||||
if (trickPlay == null) {
|
||||
final trickplay = await api.getTrickPlay(item: item, ref: ref);
|
||||
ref.read(playBackModel.notifier).update((state) => copyWith(trickPlay: () => trickplay?.body));
|
||||
}
|
||||
|
||||
await api.sessionsPlayingProgressPost(
|
||||
body: PlaybackProgressInfo(
|
||||
canSeek: true,
|
||||
itemId: item.id,
|
||||
mediaSourceId: item.id,
|
||||
playSessionId: playbackInfo.playSessionId,
|
||||
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
|
||||
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
|
||||
volumeLevel: 100,
|
||||
playMethod: PlayMethod.directplay,
|
||||
isPaused: !isPlaying,
|
||||
isMuted: false,
|
||||
positionTicks: position.toRuntimeTicks,
|
||||
repeatMode: RepeatMode.repeatall,
|
||||
nowPlayingQueue: itemsInQueue,
|
||||
),
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'DirectPlaybackModel(item: $item, playbackInfo: $playbackInfo)';
|
||||
|
||||
@override
|
||||
final List<ItemBaseModel> queue;
|
||||
|
||||
@override
|
||||
DirectPlaybackModel copyWith({
|
||||
ItemBaseModel? item,
|
||||
ValueGetter<Media?>? media,
|
||||
ValueGetter<Duration>? lastPosition,
|
||||
PlaybackInfoResponse? playbackInfo,
|
||||
ValueGetter<MediaStreamsModel?>? mediaStreams,
|
||||
ValueGetter<IntroOutSkipModel?>? introSkipModel,
|
||||
ValueGetter<List<Chapter>?>? chapters,
|
||||
ValueGetter<TrickPlayModel?>? trickPlay,
|
||||
List<ItemBaseModel>? queue,
|
||||
}) {
|
||||
return DirectPlaybackModel(
|
||||
item: item ?? this.item,
|
||||
media: media != null ? media() : this.media,
|
||||
playbackInfo: playbackInfo ?? this.playbackInfo,
|
||||
mediaStreams: mediaStreams != null ? mediaStreams() : this.mediaStreams,
|
||||
introSkipModel: introSkipModel != null ? introSkipModel() : this.introSkipModel,
|
||||
chapters: chapters != null ? chapters() : this.chapters,
|
||||
trickPlay: trickPlay != null ? trickPlay() : this.trickPlay,
|
||||
queue: queue ?? this.queue,
|
||||
);
|
||||
}
|
||||
}
|
||||
175
lib/models/playback/offline_playback_model.dart
Normal file
175
lib/models/playback/offline_playback_model.dart
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
import 'package:fladder/util/list_extensions.dart';
|
||||
import 'package:fladder/wrappers/media_control_wrapper.dart'
|
||||
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
|
||||
class OfflinePlaybackModel implements PlaybackModel {
|
||||
OfflinePlaybackModel({
|
||||
required this.item,
|
||||
required this.media,
|
||||
required this.syncedItem,
|
||||
this.mediaStreams,
|
||||
this.playbackInfo,
|
||||
this.introSkipModel,
|
||||
this.trickPlay,
|
||||
this.queue = const [],
|
||||
this.syncedQueue = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
final ItemBaseModel item;
|
||||
|
||||
@override
|
||||
final PlaybackInfoResponse? playbackInfo;
|
||||
|
||||
@override
|
||||
final Media? media;
|
||||
|
||||
final SyncedItem syncedItem;
|
||||
|
||||
@override
|
||||
final MediaStreamsModel? mediaStreams;
|
||||
|
||||
@override
|
||||
final IntroOutSkipModel? introSkipModel;
|
||||
|
||||
@override
|
||||
List<Chapter>? get chapters => syncedItem.chapters;
|
||||
|
||||
@override
|
||||
final TrickPlayModel? trickPlay;
|
||||
|
||||
@override
|
||||
Future<Duration>? startDuration() async => item.userData.playBackPosition;
|
||||
|
||||
@override
|
||||
ItemBaseModel? get nextVideo => queue.nextOrNull(item);
|
||||
@override
|
||||
ItemBaseModel? get previousVideo => queue.previousOrNull(item);
|
||||
|
||||
@override
|
||||
List<SubStreamModel> get subStreams => [SubStreamModel.no(), ...syncedItem.subtitles];
|
||||
|
||||
@override
|
||||
Future<OfflinePlaybackModel> setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async {
|
||||
final wantedSubtitle =
|
||||
model ?? subStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultSubStreamIndex);
|
||||
if (wantedSubtitle == null) return this;
|
||||
if (wantedSubtitle.index == SubStreamModel.no().index) {
|
||||
await player.setSubtitleTrack(SubtitleTrack.no());
|
||||
} else {
|
||||
final subTracks = player.subTracks.getRange(2, player.subTracks.length).toList();
|
||||
final index = subStreams.sublist(1).indexWhere((element) => element.id == wantedSubtitle.id);
|
||||
final subTrack = subTracks.elementAtOrNull(index);
|
||||
if (wantedSubtitle.isExternal && wantedSubtitle.url != null && subTrack == null) {
|
||||
await player.setSubtitleTrack(SubtitleTrack.uri(wantedSubtitle.url!));
|
||||
} else if (subTrack != null) {
|
||||
await player.setSubtitleTrack(subTrack);
|
||||
}
|
||||
}
|
||||
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultSubStreamIndex: wantedSubtitle.index));
|
||||
}
|
||||
|
||||
@override
|
||||
List<AudioStreamModel> get audioStreams => [AudioStreamModel.no(), ...mediaStreams?.audioStreams ?? []];
|
||||
|
||||
@override
|
||||
Future<OfflinePlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) async {
|
||||
final wantedAudioStream =
|
||||
model ?? audioStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultAudioStreamIndex);
|
||||
if (wantedAudioStream == null) return this;
|
||||
if (wantedAudioStream.index == AudioStreamModel.no().index) {
|
||||
await player.setAudioTrack(AudioTrack.no());
|
||||
} else {
|
||||
final audioTracks = player.audioTracks.getRange(2, player.audioTracks.length).toList();
|
||||
final audioTrack = audioTracks.elementAtOrNull(audioStreams.indexOf(wantedAudioStream) - 1);
|
||||
if (audioTrack != null) {
|
||||
await player.setAudioTrack(audioTrack);
|
||||
}
|
||||
}
|
||||
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultAudioStreamIndex: wantedAudioStream.index));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) async {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async {
|
||||
final progress = position.inMilliseconds / (item.overview.runTime?.inMilliseconds ?? 0) * 100;
|
||||
final newItem = syncedItem.copyWith(
|
||||
userData: syncedItem.userData?.copyWith(
|
||||
playbackPositionTicks: position.toRuntimeTicks,
|
||||
progress: progress,
|
||||
played: isPlayed(position, item.overview.runTime ?? Duration.zero),
|
||||
),
|
||||
);
|
||||
await ref.read(syncProvider.notifier).updateItem(newItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
bool isPlayed(Duration position, Duration totalDuration) {
|
||||
Duration startBuffer = totalDuration * 0.05;
|
||||
Duration endBuffer = totalDuration * 0.90;
|
||||
|
||||
Duration validStart = startBuffer;
|
||||
Duration validEnd = endBuffer;
|
||||
|
||||
if (position >= validStart && position <= validEnd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'OfflinePlaybackModel(item: $item, syncedItem: $syncedItem)';
|
||||
|
||||
@override
|
||||
final List<ItemBaseModel> queue;
|
||||
|
||||
final List<SyncedItem> syncedQueue;
|
||||
|
||||
@override
|
||||
OfflinePlaybackModel copyWith({
|
||||
ItemBaseModel? item,
|
||||
ValueGetter<Media?>? media,
|
||||
SyncedItem? syncedItem,
|
||||
ValueGetter<MediaStreamsModel?>? mediaStreams,
|
||||
ValueGetter<IntroOutSkipModel?>? introSkipModel,
|
||||
ValueGetter<TrickPlayModel?>? trickPlay,
|
||||
List<ItemBaseModel>? queue,
|
||||
List<SyncedItem>? syncedQueue,
|
||||
}) {
|
||||
return OfflinePlaybackModel(
|
||||
item: item ?? this.item,
|
||||
media: media != null ? media() : this.media,
|
||||
syncedItem: syncedItem ?? this.syncedItem,
|
||||
mediaStreams: mediaStreams != null ? mediaStreams() : this.mediaStreams,
|
||||
introSkipModel: introSkipModel != null ? introSkipModel() : this.introSkipModel,
|
||||
trickPlay: trickPlay != null ? trickPlay() : this.trickPlay,
|
||||
queue: queue ?? this.queue,
|
||||
syncedQueue: syncedQueue ?? this.syncedQueue,
|
||||
);
|
||||
}
|
||||
}
|
||||
370
lib/models/playback/playback_model.dart
Normal file
370
lib/models/playback/playback_model.dart
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/items/season_model.dart';
|
||||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/models/playback/direct_playback_model.dart';
|
||||
import 'package:fladder/models/playback/offline_playback_model.dart';
|
||||
import 'package:fladder/models/playback/transcode_playback_model.dart';
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
import 'package:fladder/models/video_stream_model.dart';
|
||||
import 'package:fladder/profiles/default_profile.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
import 'package:fladder/wrappers/media_control_wrapper.dart'
|
||||
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
|
||||
|
||||
extension PlaybackModelExtension on PlaybackModel? {
|
||||
String? get label => switch (this) {
|
||||
DirectPlaybackModel _ => PlaybackType.directStream.name,
|
||||
TranscodePlaybackModel _ => PlaybackType.transcode.name,
|
||||
OfflinePlaybackModel _ => PlaybackType.offline.name,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
abstract class PlaybackModel {
|
||||
final ItemBaseModel item = throw UnimplementedError();
|
||||
final Media? media = throw UnimplementedError();
|
||||
final List<ItemBaseModel> queue = const [];
|
||||
final IntroOutSkipModel? introSkipModel = null;
|
||||
final PlaybackInfoResponse? playbackInfo = throw UnimplementedError();
|
||||
|
||||
List<Chapter>? get chapters;
|
||||
TrickPlayModel? get trickPlay;
|
||||
|
||||
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) =>
|
||||
throw UnimplementedError();
|
||||
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) => throw UnimplementedError();
|
||||
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) =>
|
||||
throw UnimplementedError();
|
||||
|
||||
final MediaStreamsModel? mediaStreams = throw UnimplementedError();
|
||||
List<SubStreamModel>? get subStreams;
|
||||
List<AudioStreamModel>? get audioStreams;
|
||||
|
||||
Future<Duration>? startDuration() async => item.userData.playBackPosition;
|
||||
Future<PlaybackModel>? setSubtitle(SubStreamModel? model, MediaControlsWrapper player) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<PlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) => null;
|
||||
|
||||
ItemBaseModel? get nextVideo => throw UnimplementedError();
|
||||
ItemBaseModel? get previousVideo => throw UnimplementedError();
|
||||
|
||||
PlaybackModel copyWith();
|
||||
}
|
||||
|
||||
final playbackModelHelper = Provider<PlaybackModelHelper>((ref) {
|
||||
return PlaybackModelHelper(ref: ref);
|
||||
});
|
||||
|
||||
class PlaybackModelHelper {
|
||||
const PlaybackModelHelper({required this.ref});
|
||||
|
||||
final Ref ref;
|
||||
|
||||
JellyService get api => ref.read(jellyApiProvider);
|
||||
|
||||
Future<PlaybackModel?> loadNewVideo(ItemBaseModel newItem) async {
|
||||
ref.read(videoPlayerProvider).pause();
|
||||
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(buffering: true));
|
||||
final currentModel = ref.read(playBackModel);
|
||||
final newModel = (await createServerPlaybackModel(
|
||||
newItem,
|
||||
null,
|
||||
oldModel: currentModel,
|
||||
)) ??
|
||||
await createOfflinePlaybackModel(
|
||||
newItem,
|
||||
ref.read(syncProvider.notifier).getSyncedItem(newItem),
|
||||
oldModel: currentModel,
|
||||
);
|
||||
if (newModel == null) return null;
|
||||
ref.read(videoPlayerProvider.notifier).loadPlaybackItem(newModel, startPosition: Duration.zero);
|
||||
return newModel;
|
||||
}
|
||||
|
||||
Future<OfflinePlaybackModel?> createOfflinePlaybackModel(
|
||||
ItemBaseModel item,
|
||||
SyncedItem? syncedItem, {
|
||||
PlaybackModel? oldModel,
|
||||
}) async {
|
||||
final ItemBaseModel? syncedItemModel = ref.read(syncProvider.notifier).getItem(syncedItem);
|
||||
if (syncedItemModel == null || syncedItem == null || !syncedItem.dataFile.existsSync()) return null;
|
||||
|
||||
final children = ref.read(syncChildrenProvider(syncedItem));
|
||||
final syncedItems = children.where((element) => element.videoFile.existsSync()).toList();
|
||||
final itemQueue = syncedItems.map((e) => e.createItemModel(ref));
|
||||
|
||||
return OfflinePlaybackModel(
|
||||
item: syncedItemModel,
|
||||
syncedItem: syncedItem,
|
||||
trickPlay: syncedItem.trickPlayModel,
|
||||
introSkipModel: syncedItem.introOutSkipModel,
|
||||
media: Media(syncedItem.videoFile.path),
|
||||
queue: itemQueue.whereNotNull().toList(),
|
||||
syncedQueue: children,
|
||||
mediaStreams: item.streamModel ?? syncedItemModel.streamModel,
|
||||
);
|
||||
}
|
||||
|
||||
Future<EpisodeModel?> getNextUpEpisode(String itemId) async {
|
||||
final responnse = await api.showsNextUpGet(parentId: itemId, fields: [ItemFields.overview]);
|
||||
final episode = responnse.body?.items?.firstOrNull;
|
||||
if (episode == null) {
|
||||
return null;
|
||||
} else {
|
||||
return EpisodeModel.fromBaseDto(episode, ref);
|
||||
}
|
||||
}
|
||||
|
||||
Future<PlaybackModel?> createServerPlaybackModel(ItemBaseModel? item, PlaybackType? type,
|
||||
{PlaybackModel? oldModel, List<ItemBaseModel>? libraryQueue, Duration? startPosition}) async {
|
||||
try {
|
||||
if (item == null) return null;
|
||||
final userId = ref.read(userProvider)?.id;
|
||||
if (userId?.isEmpty == true) return null;
|
||||
|
||||
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
|
||||
|
||||
final firstItemToPlay = switch (item) {
|
||||
SeriesModel _ || SeasonModel _ => (await getNextUpEpisode(item.id) ?? queue.first),
|
||||
_ => item,
|
||||
};
|
||||
|
||||
final fullItem = await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id);
|
||||
|
||||
final streamModel = firstItemToPlay.streamModel;
|
||||
|
||||
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
|
||||
itemId: firstItemToPlay.id,
|
||||
enableDirectPlay: type != PlaybackType.transcode,
|
||||
enableDirectStream: type != PlaybackType.transcode,
|
||||
enableTranscoding: true,
|
||||
autoOpenLiveStream: true,
|
||||
startTimeTicks: startPosition?.toRuntimeTicks,
|
||||
audioStreamIndex: streamModel?.defaultAudioStreamIndex,
|
||||
subtitleStreamIndex: streamModel?.defaultSubStreamIndex,
|
||||
mediaSourceId: firstItemToPlay.id,
|
||||
body: PlaybackInfoDto(
|
||||
startTimeTicks: startPosition?.toRuntimeTicks,
|
||||
audioStreamIndex: streamModel?.defaultAudioStreamIndex,
|
||||
subtitleStreamIndex: streamModel?.defaultSubStreamIndex,
|
||||
enableTranscoding: true,
|
||||
autoOpenLiveStream: true,
|
||||
deviceProfile: defaultProfile,
|
||||
userId: userId,
|
||||
mediaSourceId: firstItemToPlay.id,
|
||||
),
|
||||
);
|
||||
|
||||
PlaybackInfoResponse? playbackInfo = response.body;
|
||||
if (playbackInfo == null) return null;
|
||||
|
||||
final mediaSource = playbackInfo.mediaSources?.first;
|
||||
|
||||
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(
|
||||
playbackInfo.mediaSources?.firstOrNull, playbackInfo.mediaSources?.firstOrNull?.mediaStreams ?? [], ref)
|
||||
.copyWith(
|
||||
defaultAudioStreamIndex: streamModel?.defaultAudioStreamIndex,
|
||||
defaultSubStreamIndex: streamModel?.defaultSubStreamIndex,
|
||||
);
|
||||
|
||||
final intro = await api.introSkipGet(id: item.id);
|
||||
final trickPlay = (await api.getTrickPlay(item: fullItem.body, ref: ref))?.body;
|
||||
final chapters = fullItem.body?.overview.chapters ?? [];
|
||||
|
||||
if (mediaSource == null) return null;
|
||||
|
||||
if ((mediaSource.supportsDirectStream ?? false) || (mediaSource.supportsDirectPlay ?? false)) {
|
||||
final Map<String, String?> directOptions = {
|
||||
'Static': 'true',
|
||||
'mediaSourceId': mediaSource.id,
|
||||
'api_key': ref.read(userProvider)?.credentials.token,
|
||||
};
|
||||
|
||||
if (mediaSource.eTag != null) {
|
||||
directOptions['Tag'] = mediaSource.eTag;
|
||||
}
|
||||
|
||||
if (mediaSource.liveStreamId != null) {
|
||||
directOptions['LiveStreamId'] = mediaSource.liveStreamId;
|
||||
}
|
||||
|
||||
final params = Uri(queryParameters: directOptions).query;
|
||||
|
||||
return DirectPlaybackModel(
|
||||
item: fullItem.body ?? item,
|
||||
queue: queue,
|
||||
introSkipModel: intro?.body,
|
||||
chapters: chapters,
|
||||
playbackInfo: playbackInfo,
|
||||
trickPlay: trickPlay,
|
||||
media: Media('${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params'),
|
||||
mediaStreams: mediaStreamsWithUrls,
|
||||
);
|
||||
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
|
||||
return TranscodePlaybackModel(
|
||||
item: fullItem.body ?? item,
|
||||
queue: queue,
|
||||
introSkipModel: intro?.body,
|
||||
chapters: chapters,
|
||||
trickPlay: trickPlay,
|
||||
playbackInfo: playbackInfo,
|
||||
media: Media("${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}"),
|
||||
mediaStreams: mediaStreamsWithUrls,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ItemBaseModel>> collectQueue(ItemBaseModel model) async {
|
||||
switch (model) {
|
||||
case EpisodeModel _:
|
||||
case SeriesModel _:
|
||||
case SeasonModel _:
|
||||
List<EpisodeModel> episodeList = ((await fetchEpisodesFromSeries(model.streamId)).body ?? [])
|
||||
..removeWhere((element) => element.status != EpisodeStatus.available);
|
||||
return episodeList;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<List<EpisodeModel>>> fetchEpisodesFromSeries(String seriesId) async {
|
||||
final response = await api.showsSeriesIdEpisodesGet(
|
||||
seriesId: seriesId,
|
||||
fields: [
|
||||
ItemFields.overview,
|
||||
ItemFields.originaltitle,
|
||||
ItemFields.mediastreams,
|
||||
ItemFields.mediasources,
|
||||
ItemFields.mediasourcecount,
|
||||
ItemFields.width,
|
||||
ItemFields.height,
|
||||
],
|
||||
);
|
||||
return Response(response.base, (response.body?.items?.map((e) => EpisodeModel.fromBaseDto(e, ref)).toList() ?? []));
|
||||
}
|
||||
|
||||
Future<void> shouldReload(PlaybackModel playbackModel) async {
|
||||
if (playbackModel is OfflinePlaybackModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
final item = playbackModel.item;
|
||||
|
||||
final userId = ref.read(userProvider)?.id;
|
||||
if (userId?.isEmpty == true) return;
|
||||
|
||||
final currentPosition = ref.read(mediaPlaybackProvider.select((value) => value.position));
|
||||
|
||||
final audioIndex = playbackModel.mediaStreams?.defaultAudioStreamIndex;
|
||||
final subIndex = playbackModel.mediaStreams?.defaultSubStreamIndex;
|
||||
|
||||
Response<PlaybackInfoResponse> response = await api.itemsItemIdPlaybackInfoPost(
|
||||
itemId: item.id,
|
||||
enableDirectPlay: true,
|
||||
enableDirectStream: true,
|
||||
enableTranscoding: true,
|
||||
autoOpenLiveStream: true,
|
||||
startTimeTicks: currentPosition.toRuntimeTicks,
|
||||
audioStreamIndex: audioIndex,
|
||||
subtitleStreamIndex: subIndex,
|
||||
mediaSourceId: item.id,
|
||||
body: PlaybackInfoDto(
|
||||
startTimeTicks: currentPosition.toRuntimeTicks,
|
||||
audioStreamIndex: audioIndex,
|
||||
subtitleStreamIndex: subIndex,
|
||||
enableTranscoding: true,
|
||||
autoOpenLiveStream: true,
|
||||
deviceProfile: defaultProfile,
|
||||
userId: userId,
|
||||
mediaSourceId: item.id,
|
||||
),
|
||||
);
|
||||
|
||||
PlaybackInfoResponse playbackInfo = response.bodyOrThrow;
|
||||
|
||||
final mediaSource = playbackInfo.mediaSources?.first;
|
||||
|
||||
final mediaStreamsWithUrls = MediaStreamsModel.fromMediaStreamsList(
|
||||
playbackInfo.mediaSources?.firstOrNull, playbackInfo.mediaSources?.firstOrNull?.mediaStreams ?? [], ref)
|
||||
.copyWith(
|
||||
defaultAudioStreamIndex: audioIndex,
|
||||
defaultSubStreamIndex: subIndex,
|
||||
);
|
||||
|
||||
if (mediaSource == null) return;
|
||||
|
||||
PlaybackModel? newModel;
|
||||
|
||||
if ((mediaSource.supportsDirectStream ?? false) || (mediaSource.supportsDirectPlay ?? false)) {
|
||||
final Map<String, String?> directOptions = {
|
||||
'Static': 'true',
|
||||
'mediaSourceId': mediaSource.id,
|
||||
'api_key': ref.read(userProvider)?.credentials.token,
|
||||
};
|
||||
|
||||
if (mediaSource.eTag != null) {
|
||||
directOptions['Tag'] = mediaSource.eTag;
|
||||
}
|
||||
|
||||
if (mediaSource.liveStreamId != null) {
|
||||
directOptions['LiveStreamId'] = mediaSource.liveStreamId;
|
||||
}
|
||||
|
||||
final params = Uri(queryParameters: directOptions).query;
|
||||
|
||||
final directPlay = '${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params';
|
||||
|
||||
newModel = DirectPlaybackModel(
|
||||
item: playbackModel.item,
|
||||
queue: playbackModel.queue,
|
||||
introSkipModel: playbackModel.introSkipModel,
|
||||
chapters: playbackModel.chapters,
|
||||
playbackInfo: playbackInfo,
|
||||
trickPlay: playbackModel.trickPlay,
|
||||
media: Media(directPlay),
|
||||
mediaStreams: mediaStreamsWithUrls,
|
||||
);
|
||||
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
|
||||
newModel = TranscodePlaybackModel(
|
||||
item: playbackModel.item,
|
||||
queue: playbackModel.queue,
|
||||
introSkipModel: playbackModel.introSkipModel,
|
||||
chapters: playbackModel.chapters,
|
||||
playbackInfo: playbackInfo,
|
||||
trickPlay: playbackModel.trickPlay,
|
||||
media: Media("${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}"),
|
||||
mediaStreams: mediaStreamsWithUrls,
|
||||
);
|
||||
}
|
||||
if (newModel == null) return;
|
||||
if (newModel.runtimeType != playbackModel.runtimeType || newModel is TranscodePlaybackModel) {
|
||||
ref.read(videoPlayerProvider.notifier).loadPlaybackItem(newModel, startPosition: currentPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
210
lib/models/playback/transcode_playback_model.dart
Normal file
210
lib/models/playback/transcode_playback_model.dart
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/util/list_extensions.dart';
|
||||
import 'package:fladder/wrappers/media_control_wrapper.dart'
|
||||
if (dart.library.html) 'package:fladder/wrappers/media_control_wrapper_web.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/playback/playback_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
|
||||
class TranscodePlaybackModel implements PlaybackModel {
|
||||
TranscodePlaybackModel({
|
||||
required this.item,
|
||||
required this.media,
|
||||
required this.playbackInfo,
|
||||
this.mediaStreams,
|
||||
this.introSkipModel,
|
||||
this.chapters,
|
||||
this.trickPlay,
|
||||
this.queue = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
final ItemBaseModel item;
|
||||
|
||||
@override
|
||||
final Media? media;
|
||||
|
||||
@override
|
||||
final PlaybackInfoResponse playbackInfo;
|
||||
|
||||
@override
|
||||
final MediaStreamsModel? mediaStreams;
|
||||
|
||||
@override
|
||||
final IntroOutSkipModel? introSkipModel;
|
||||
|
||||
@override
|
||||
final List<Chapter>? chapters;
|
||||
|
||||
@override
|
||||
final TrickPlayModel? trickPlay;
|
||||
|
||||
@override
|
||||
ItemBaseModel? get nextVideo => queue.nextOrNull(item);
|
||||
|
||||
@override
|
||||
ItemBaseModel? get previousVideo => queue.previousOrNull(item);
|
||||
|
||||
@override
|
||||
Future<Duration>? startDuration() async => item.userData.playBackPosition;
|
||||
|
||||
@override
|
||||
List<SubStreamModel> get subStreams => [SubStreamModel.no(), ...mediaStreams?.subStreams ?? []];
|
||||
|
||||
List<QueueItem> get itemsInQueue =>
|
||||
queue.mapIndexed((index, element) => QueueItem(id: element.id, playlistItemId: "playlistItem$index")).toList();
|
||||
|
||||
@override
|
||||
Future<TranscodePlaybackModel> setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async {
|
||||
final wantedSubtitle =
|
||||
model ?? subStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultSubStreamIndex);
|
||||
if (wantedSubtitle == null) return this;
|
||||
if (wantedSubtitle.index == SubStreamModel.no().index) {
|
||||
await player.setSubtitleTrack(SubtitleTrack.no());
|
||||
} else {
|
||||
final subTracks = player.subTracks.getRange(2, player.subTracks.length).toList();
|
||||
final index = subStreams.sublist(1).indexWhere((element) => element.id == wantedSubtitle.id);
|
||||
final subTrack = subTracks.elementAtOrNull(index);
|
||||
if (wantedSubtitle.isExternal && wantedSubtitle.url != null && subTrack == null) {
|
||||
await player.setSubtitleTrack(SubtitleTrack.uri(wantedSubtitle.url!));
|
||||
} else if (subTrack != null) {
|
||||
await player.setSubtitleTrack(subTrack);
|
||||
}
|
||||
}
|
||||
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultSubStreamIndex: wantedSubtitle.index));
|
||||
}
|
||||
|
||||
@override
|
||||
List<AudioStreamModel> get audioStreams => [AudioStreamModel.no(), ...mediaStreams?.audioStreams ?? []];
|
||||
|
||||
@override
|
||||
Future<TranscodePlaybackModel>? setAudio(AudioStreamModel? model, MediaControlsWrapper player) async {
|
||||
final wantedAudioStream =
|
||||
model ?? audioStreams.firstWhereOrNull((element) => element.index == mediaStreams?.defaultAudioStreamIndex);
|
||||
if (wantedAudioStream == null) return this;
|
||||
if (wantedAudioStream.index == AudioStreamModel.no().index) {
|
||||
await player.setAudioTrack(AudioTrack.no());
|
||||
} else {
|
||||
final audioTracks = player.audioTracks.getRange(2, player.audioTracks.length).toList();
|
||||
final audioTrack = audioTracks.elementAtOrNull(audioStreams.indexOf(wantedAudioStream) - 1);
|
||||
if (audioTrack != null) {
|
||||
await player.setAudioTrack(audioTrack);
|
||||
}
|
||||
}
|
||||
return copyWith(mediaStreams: () => mediaStreams?.copyWith(defaultAudioStreamIndex: wantedAudioStream.index));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> playbackStarted(Duration position, Ref ref) async {
|
||||
await ref.read(jellyApiProvider).sessionsPlayingPost(
|
||||
body: PlaybackStartInfo(
|
||||
canSeek: true,
|
||||
itemId: item.id,
|
||||
mediaSourceId: item.id,
|
||||
playSessionId: playbackInfo.playSessionId,
|
||||
sessionId: playbackInfo.playSessionId,
|
||||
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
|
||||
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
|
||||
volumeLevel: 100,
|
||||
playbackStartTimeTicks: position.toRuntimeTicks,
|
||||
playMethod: PlayMethod.transcode,
|
||||
isMuted: false,
|
||||
isPaused: false,
|
||||
repeatMode: RepeatMode.repeatall,
|
||||
nowPlayingQueue: itemsInQueue,
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> playbackStopped(Duration position, Duration? totalDuration, Ref ref) async {
|
||||
ref.read(playBackModel.notifier).update((state) => null);
|
||||
|
||||
await ref.read(jellyApiProvider).sessionsPlayingStoppedPost(
|
||||
body: PlaybackStopInfo(
|
||||
itemId: item.id,
|
||||
mediaSourceId: item.id,
|
||||
playSessionId: playbackInfo.playSessionId,
|
||||
positionTicks: position.toRuntimeTicks,
|
||||
failed: false,
|
||||
nowPlayingQueue: itemsInQueue,
|
||||
),
|
||||
totalDuration: totalDuration,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PlaybackModel?> updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async {
|
||||
final api = ref.read(jellyApiProvider);
|
||||
|
||||
//Check for newly generated scrubImages
|
||||
if (trickPlay == null) {
|
||||
final trickplay = await api.getTrickPlay(item: item, ref: ref);
|
||||
ref.read(playBackModel.notifier).update((state) => copyWith(trickPlay: () => trickplay?.bodyOrThrow));
|
||||
}
|
||||
|
||||
await api.sessionsPlayingProgressPost(
|
||||
body: PlaybackProgressInfo(
|
||||
canSeek: true,
|
||||
itemId: item.id,
|
||||
mediaSourceId: item.id,
|
||||
playSessionId: playbackInfo.playSessionId,
|
||||
sessionId: playbackInfo.playSessionId,
|
||||
subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex,
|
||||
audioStreamIndex: item.streamModel?.defaultAudioStreamIndex,
|
||||
volumeLevel: 100,
|
||||
positionTicks: position.toRuntimeTicks,
|
||||
playMethod: PlayMethod.transcode,
|
||||
isPaused: !isPlaying,
|
||||
isMuted: false,
|
||||
repeatMode: RepeatMode.repeatall,
|
||||
nowPlayingQueue: itemsInQueue,
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'TranscodePlaybackModel(item: $item, playbackInfo: $playbackInfo)';
|
||||
|
||||
@override
|
||||
final List<ItemBaseModel> queue;
|
||||
|
||||
@override
|
||||
TranscodePlaybackModel copyWith({
|
||||
ItemBaseModel? item,
|
||||
ValueGetter<Media?>? media,
|
||||
ValueGetter<Duration>? lastPosition,
|
||||
PlaybackInfoResponse? playbackInfo,
|
||||
ValueGetter<MediaStreamsModel?>? mediaStreams,
|
||||
ValueGetter<IntroOutSkipModel?>? introSkipModel,
|
||||
ValueGetter<List<Chapter>?>? chapters,
|
||||
ValueGetter<TrickPlayModel?>? trickPlay,
|
||||
List<ItemBaseModel>? queue,
|
||||
}) {
|
||||
return TranscodePlaybackModel(
|
||||
item: item ?? this.item,
|
||||
media: media != null ? media() : this.media,
|
||||
playbackInfo: playbackInfo ?? this.playbackInfo,
|
||||
mediaStreams: mediaStreams != null ? mediaStreams() : this.mediaStreams,
|
||||
introSkipModel: introSkipModel != null ? introSkipModel() : this.introSkipModel,
|
||||
chapters: chapters != null ? chapters() : this.chapters,
|
||||
trickPlay: trickPlay != null ? trickPlay() : this.trickPlay,
|
||||
queue: queue ?? this.queue,
|
||||
);
|
||||
}
|
||||
}
|
||||
40
lib/models/playlist_model.dart
Normal file
40
lib/models/playlist_model.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/overview_model.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class PlaylistModel extends ItemBaseModel {
|
||||
PlaylistModel({
|
||||
required super.name,
|
||||
required super.id,
|
||||
required super.overview,
|
||||
required super.parentId,
|
||||
required super.playlistId,
|
||||
required super.images,
|
||||
required super.childCount,
|
||||
required super.primaryRatio,
|
||||
required super.userData,
|
||||
super.canDelete,
|
||||
super.canDownload,
|
||||
super.jellyType,
|
||||
});
|
||||
|
||||
factory PlaylistModel.fromBaseDto(BaseItemDto item, Ref ref) {
|
||||
return PlaylistModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
childCount: item.childCount,
|
||||
overview: OverviewModel.fromBaseItemDto(item, ref),
|
||||
userData: UserData.fromDto(item.userData),
|
||||
parentId: item.parentId,
|
||||
playlistId: item.playlistItemId,
|
||||
images: ImagesData.fromBaseItem(item, ref),
|
||||
primaryRatio: item.primaryImageAspectRatio,
|
||||
canDelete: item.canDelete,
|
||||
canDownload: item.canDownload,
|
||||
jellyType: item.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
25
lib/models/recommended_model.dart
Normal file
25
lib/models/recommended_model.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
|
||||
class RecommendedModel {
|
||||
final String name;
|
||||
final List<ItemBaseModel> posters;
|
||||
final String type;
|
||||
RecommendedModel({
|
||||
required this.name,
|
||||
required this.posters,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
RecommendedModel copyWith({
|
||||
String? name,
|
||||
List<ItemBaseModel>? posters,
|
||||
String? type,
|
||||
}) {
|
||||
return RecommendedModel(
|
||||
name: name ?? this.name,
|
||||
posters: posters ?? this.posters,
|
||||
type: type ?? this.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
28
lib/models/search_model.dart
Normal file
28
lib/models/search_model.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
|
||||
class SearchModel {
|
||||
final bool loading;
|
||||
final String searchQuery;
|
||||
final int resultCount;
|
||||
final Map<FladderItemType, List<ItemBaseModel>> results;
|
||||
SearchModel({
|
||||
this.loading = false,
|
||||
this.searchQuery = "",
|
||||
this.resultCount = 0,
|
||||
this.results = const {},
|
||||
});
|
||||
|
||||
SearchModel copyWith({
|
||||
bool? loading,
|
||||
String? searchQuery,
|
||||
int? resultCount,
|
||||
Map<FladderItemType, List<ItemBaseModel>>? results,
|
||||
}) {
|
||||
return SearchModel(
|
||||
loading: loading ?? this.loading,
|
||||
searchQuery: searchQuery ?? this.searchQuery,
|
||||
resultCount: resultCount ?? this.resultCount,
|
||||
results: results ?? this.results,
|
||||
);
|
||||
}
|
||||
}
|
||||
117
lib/models/settings/client_settings_model.dart
Normal file
117
lib/models/settings/client_settings_model.dart
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/util/custom_color_themes.dart';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'client_settings_model.freezed.dart';
|
||||
part 'client_settings_model.g.dart';
|
||||
|
||||
@freezed
|
||||
class ClientSettingsModel with _$ClientSettingsModel {
|
||||
factory ClientSettingsModel({
|
||||
String? syncPath,
|
||||
@Default(Vector2(x: 0, y: 0)) Vector2 position,
|
||||
@Default(Vector2(x: 1280, y: 720)) Vector2 size,
|
||||
@Default(Duration(seconds: 30)) Duration? timeOut,
|
||||
Duration? nextUpDateCutoff,
|
||||
@Default(ThemeMode.system) ThemeMode themeMode,
|
||||
ColorThemes? themeColor,
|
||||
@Default(false) bool amoledBlack,
|
||||
@Default(false) bool blurPlaceHolders,
|
||||
@Default(false) bool blurUpcomingEpisodes,
|
||||
@LocaleConvert() Locale? selectedLocale,
|
||||
@Default(true) bool enableMediaKeys,
|
||||
@Default(1.0) double posterSize,
|
||||
@Default(false) bool pinchPosterZoom,
|
||||
@Default(false) bool mouseDragSupport,
|
||||
int? libraryPageSize,
|
||||
}) = _ClientSettingsModel;
|
||||
|
||||
factory ClientSettingsModel.fromJson(Map<String, dynamic> json) => _$ClientSettingsModelFromJson(json);
|
||||
}
|
||||
|
||||
class LocaleConvert implements JsonConverter<Locale?, String?> {
|
||||
const LocaleConvert();
|
||||
|
||||
@override
|
||||
Locale? fromJson(String? json) {
|
||||
if (json == null) return null;
|
||||
final parts = json.split('_');
|
||||
if (parts.length == 1) {
|
||||
return Locale(parts[0]);
|
||||
} else if (parts.length == 2) {
|
||||
return Locale(parts[0], parts[1]);
|
||||
} else {
|
||||
log("Invalid Locale format");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String? toJson(Locale? object) {
|
||||
if (object == null) return null;
|
||||
if (object.countryCode == null || object.countryCode?.isEmpty == true) {
|
||||
return object.languageCode;
|
||||
}
|
||||
return '${object.languageCode}_${object.countryCode}';
|
||||
}
|
||||
}
|
||||
|
||||
class Vector2 {
|
||||
final double x;
|
||||
final double y;
|
||||
const Vector2({
|
||||
required this.x,
|
||||
required this.y,
|
||||
});
|
||||
|
||||
Vector2 copyWith({
|
||||
double? x,
|
||||
double? y,
|
||||
}) {
|
||||
return Vector2(
|
||||
x: x ?? this.x,
|
||||
y: y ?? this.y,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'x': x,
|
||||
'y': y,
|
||||
};
|
||||
}
|
||||
|
||||
factory Vector2.fromMap(Map<String, dynamic> map) {
|
||||
return Vector2(
|
||||
x: map['x'] as double,
|
||||
y: map['y'] as double,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Vector2.fromJson(String source) => Vector2.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
factory Vector2.fromSize(Size size) => Vector2(x: size.width, y: size.height);
|
||||
|
||||
@override
|
||||
String toString() => 'Vector2(x: $x, y: $y)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant Vector2 other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.x == x && other.y == y;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => x.hashCode ^ y.hashCode;
|
||||
|
||||
static fromPosition(Offset windowPosition) => Vector2(x: windowPosition.dx, y: windowPosition.dy);
|
||||
}
|
||||
526
lib/models/settings/client_settings_model.freezed.dart
Normal file
526
lib/models/settings/client_settings_model.freezed.dart
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'client_settings_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
ClientSettingsModel _$ClientSettingsModelFromJson(Map<String, dynamic> json) {
|
||||
return _ClientSettingsModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ClientSettingsModel {
|
||||
String? get syncPath => throw _privateConstructorUsedError;
|
||||
Vector2 get position => throw _privateConstructorUsedError;
|
||||
Vector2 get size => throw _privateConstructorUsedError;
|
||||
Duration? get timeOut => throw _privateConstructorUsedError;
|
||||
Duration? get nextUpDateCutoff => throw _privateConstructorUsedError;
|
||||
ThemeMode get themeMode => throw _privateConstructorUsedError;
|
||||
ColorThemes? get themeColor => throw _privateConstructorUsedError;
|
||||
bool get amoledBlack => throw _privateConstructorUsedError;
|
||||
bool get blurPlaceHolders => throw _privateConstructorUsedError;
|
||||
bool get blurUpcomingEpisodes => throw _privateConstructorUsedError;
|
||||
@LocaleConvert()
|
||||
Locale? get selectedLocale => throw _privateConstructorUsedError;
|
||||
bool get enableMediaKeys => throw _privateConstructorUsedError;
|
||||
double get posterSize => throw _privateConstructorUsedError;
|
||||
bool get pinchPosterZoom => throw _privateConstructorUsedError;
|
||||
bool get mouseDragSupport => throw _privateConstructorUsedError;
|
||||
int? get libraryPageSize => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ClientSettingsModelCopyWith<ClientSettingsModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ClientSettingsModelCopyWith<$Res> {
|
||||
factory $ClientSettingsModelCopyWith(
|
||||
ClientSettingsModel value, $Res Function(ClientSettingsModel) then) =
|
||||
_$ClientSettingsModelCopyWithImpl<$Res, ClientSettingsModel>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? syncPath,
|
||||
Vector2 position,
|
||||
Vector2 size,
|
||||
Duration? timeOut,
|
||||
Duration? nextUpDateCutoff,
|
||||
ThemeMode themeMode,
|
||||
ColorThemes? themeColor,
|
||||
bool amoledBlack,
|
||||
bool blurPlaceHolders,
|
||||
bool blurUpcomingEpisodes,
|
||||
@LocaleConvert() Locale? selectedLocale,
|
||||
bool enableMediaKeys,
|
||||
double posterSize,
|
||||
bool pinchPosterZoom,
|
||||
bool mouseDragSupport,
|
||||
int? libraryPageSize});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ClientSettingsModelCopyWithImpl<$Res, $Val extends ClientSettingsModel>
|
||||
implements $ClientSettingsModelCopyWith<$Res> {
|
||||
_$ClientSettingsModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? syncPath = freezed,
|
||||
Object? position = null,
|
||||
Object? size = null,
|
||||
Object? timeOut = freezed,
|
||||
Object? nextUpDateCutoff = freezed,
|
||||
Object? themeMode = null,
|
||||
Object? themeColor = freezed,
|
||||
Object? amoledBlack = null,
|
||||
Object? blurPlaceHolders = null,
|
||||
Object? blurUpcomingEpisodes = null,
|
||||
Object? selectedLocale = freezed,
|
||||
Object? enableMediaKeys = null,
|
||||
Object? posterSize = null,
|
||||
Object? pinchPosterZoom = null,
|
||||
Object? mouseDragSupport = null,
|
||||
Object? libraryPageSize = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
syncPath: freezed == syncPath
|
||||
? _value.syncPath
|
||||
: syncPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
position: null == position
|
||||
? _value.position
|
||||
: position // ignore: cast_nullable_to_non_nullable
|
||||
as Vector2,
|
||||
size: null == size
|
||||
? _value.size
|
||||
: size // ignore: cast_nullable_to_non_nullable
|
||||
as Vector2,
|
||||
timeOut: freezed == timeOut
|
||||
? _value.timeOut
|
||||
: timeOut // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
nextUpDateCutoff: freezed == nextUpDateCutoff
|
||||
? _value.nextUpDateCutoff
|
||||
: nextUpDateCutoff // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
themeMode: null == themeMode
|
||||
? _value.themeMode
|
||||
: themeMode // ignore: cast_nullable_to_non_nullable
|
||||
as ThemeMode,
|
||||
themeColor: freezed == themeColor
|
||||
? _value.themeColor
|
||||
: themeColor // ignore: cast_nullable_to_non_nullable
|
||||
as ColorThemes?,
|
||||
amoledBlack: null == amoledBlack
|
||||
? _value.amoledBlack
|
||||
: amoledBlack // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
blurPlaceHolders: null == blurPlaceHolders
|
||||
? _value.blurPlaceHolders
|
||||
: blurPlaceHolders // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
blurUpcomingEpisodes: null == blurUpcomingEpisodes
|
||||
? _value.blurUpcomingEpisodes
|
||||
: blurUpcomingEpisodes // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
selectedLocale: freezed == selectedLocale
|
||||
? _value.selectedLocale
|
||||
: selectedLocale // ignore: cast_nullable_to_non_nullable
|
||||
as Locale?,
|
||||
enableMediaKeys: null == enableMediaKeys
|
||||
? _value.enableMediaKeys
|
||||
: enableMediaKeys // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
posterSize: null == posterSize
|
||||
? _value.posterSize
|
||||
: posterSize // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
pinchPosterZoom: null == pinchPosterZoom
|
||||
? _value.pinchPosterZoom
|
||||
: pinchPosterZoom // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
mouseDragSupport: null == mouseDragSupport
|
||||
? _value.mouseDragSupport
|
||||
: mouseDragSupport // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
libraryPageSize: freezed == libraryPageSize
|
||||
? _value.libraryPageSize
|
||||
: libraryPageSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ClientSettingsModelImplCopyWith<$Res>
|
||||
implements $ClientSettingsModelCopyWith<$Res> {
|
||||
factory _$$ClientSettingsModelImplCopyWith(_$ClientSettingsModelImpl value,
|
||||
$Res Function(_$ClientSettingsModelImpl) then) =
|
||||
__$$ClientSettingsModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? syncPath,
|
||||
Vector2 position,
|
||||
Vector2 size,
|
||||
Duration? timeOut,
|
||||
Duration? nextUpDateCutoff,
|
||||
ThemeMode themeMode,
|
||||
ColorThemes? themeColor,
|
||||
bool amoledBlack,
|
||||
bool blurPlaceHolders,
|
||||
bool blurUpcomingEpisodes,
|
||||
@LocaleConvert() Locale? selectedLocale,
|
||||
bool enableMediaKeys,
|
||||
double posterSize,
|
||||
bool pinchPosterZoom,
|
||||
bool mouseDragSupport,
|
||||
int? libraryPageSize});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ClientSettingsModelImplCopyWithImpl<$Res>
|
||||
extends _$ClientSettingsModelCopyWithImpl<$Res, _$ClientSettingsModelImpl>
|
||||
implements _$$ClientSettingsModelImplCopyWith<$Res> {
|
||||
__$$ClientSettingsModelImplCopyWithImpl(_$ClientSettingsModelImpl _value,
|
||||
$Res Function(_$ClientSettingsModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? syncPath = freezed,
|
||||
Object? position = null,
|
||||
Object? size = null,
|
||||
Object? timeOut = freezed,
|
||||
Object? nextUpDateCutoff = freezed,
|
||||
Object? themeMode = null,
|
||||
Object? themeColor = freezed,
|
||||
Object? amoledBlack = null,
|
||||
Object? blurPlaceHolders = null,
|
||||
Object? blurUpcomingEpisodes = null,
|
||||
Object? selectedLocale = freezed,
|
||||
Object? enableMediaKeys = null,
|
||||
Object? posterSize = null,
|
||||
Object? pinchPosterZoom = null,
|
||||
Object? mouseDragSupport = null,
|
||||
Object? libraryPageSize = freezed,
|
||||
}) {
|
||||
return _then(_$ClientSettingsModelImpl(
|
||||
syncPath: freezed == syncPath
|
||||
? _value.syncPath
|
||||
: syncPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
position: null == position
|
||||
? _value.position
|
||||
: position // ignore: cast_nullable_to_non_nullable
|
||||
as Vector2,
|
||||
size: null == size
|
||||
? _value.size
|
||||
: size // ignore: cast_nullable_to_non_nullable
|
||||
as Vector2,
|
||||
timeOut: freezed == timeOut
|
||||
? _value.timeOut
|
||||
: timeOut // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
nextUpDateCutoff: freezed == nextUpDateCutoff
|
||||
? _value.nextUpDateCutoff
|
||||
: nextUpDateCutoff // ignore: cast_nullable_to_non_nullable
|
||||
as Duration?,
|
||||
themeMode: null == themeMode
|
||||
? _value.themeMode
|
||||
: themeMode // ignore: cast_nullable_to_non_nullable
|
||||
as ThemeMode,
|
||||
themeColor: freezed == themeColor
|
||||
? _value.themeColor
|
||||
: themeColor // ignore: cast_nullable_to_non_nullable
|
||||
as ColorThemes?,
|
||||
amoledBlack: null == amoledBlack
|
||||
? _value.amoledBlack
|
||||
: amoledBlack // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
blurPlaceHolders: null == blurPlaceHolders
|
||||
? _value.blurPlaceHolders
|
||||
: blurPlaceHolders // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
blurUpcomingEpisodes: null == blurUpcomingEpisodes
|
||||
? _value.blurUpcomingEpisodes
|
||||
: blurUpcomingEpisodes // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
selectedLocale: freezed == selectedLocale
|
||||
? _value.selectedLocale
|
||||
: selectedLocale // ignore: cast_nullable_to_non_nullable
|
||||
as Locale?,
|
||||
enableMediaKeys: null == enableMediaKeys
|
||||
? _value.enableMediaKeys
|
||||
: enableMediaKeys // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
posterSize: null == posterSize
|
||||
? _value.posterSize
|
||||
: posterSize // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
pinchPosterZoom: null == pinchPosterZoom
|
||||
? _value.pinchPosterZoom
|
||||
: pinchPosterZoom // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
mouseDragSupport: null == mouseDragSupport
|
||||
? _value.mouseDragSupport
|
||||
: mouseDragSupport // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
libraryPageSize: freezed == libraryPageSize
|
||||
? _value.libraryPageSize
|
||||
: libraryPageSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ClientSettingsModelImpl
|
||||
with DiagnosticableTreeMixin
|
||||
implements _ClientSettingsModel {
|
||||
_$ClientSettingsModelImpl(
|
||||
{this.syncPath,
|
||||
this.position = const Vector2(x: 0, y: 0),
|
||||
this.size = const Vector2(x: 1280, y: 720),
|
||||
this.timeOut = const Duration(seconds: 30),
|
||||
this.nextUpDateCutoff,
|
||||
this.themeMode = ThemeMode.system,
|
||||
this.themeColor,
|
||||
this.amoledBlack = false,
|
||||
this.blurPlaceHolders = false,
|
||||
this.blurUpcomingEpisodes = false,
|
||||
@LocaleConvert() this.selectedLocale,
|
||||
this.enableMediaKeys = true,
|
||||
this.posterSize = 1.0,
|
||||
this.pinchPosterZoom = false,
|
||||
this.mouseDragSupport = false,
|
||||
this.libraryPageSize});
|
||||
|
||||
factory _$ClientSettingsModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ClientSettingsModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String? syncPath;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Vector2 position;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Vector2 size;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Duration? timeOut;
|
||||
@override
|
||||
final Duration? nextUpDateCutoff;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ThemeMode themeMode;
|
||||
@override
|
||||
final ColorThemes? themeColor;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool amoledBlack;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool blurPlaceHolders;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool blurUpcomingEpisodes;
|
||||
@override
|
||||
@LocaleConvert()
|
||||
final Locale? selectedLocale;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool enableMediaKeys;
|
||||
@override
|
||||
@JsonKey()
|
||||
final double posterSize;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool pinchPosterZoom;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool mouseDragSupport;
|
||||
@override
|
||||
final int? libraryPageSize;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'ClientSettingsModel(syncPath: $syncPath, position: $position, size: $size, timeOut: $timeOut, nextUpDateCutoff: $nextUpDateCutoff, themeMode: $themeMode, themeColor: $themeColor, amoledBlack: $amoledBlack, blurPlaceHolders: $blurPlaceHolders, blurUpcomingEpisodes: $blurUpcomingEpisodes, selectedLocale: $selectedLocale, enableMediaKeys: $enableMediaKeys, posterSize: $posterSize, pinchPosterZoom: $pinchPosterZoom, mouseDragSupport: $mouseDragSupport, libraryPageSize: $libraryPageSize)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'ClientSettingsModel'))
|
||||
..add(DiagnosticsProperty('syncPath', syncPath))
|
||||
..add(DiagnosticsProperty('position', position))
|
||||
..add(DiagnosticsProperty('size', size))
|
||||
..add(DiagnosticsProperty('timeOut', timeOut))
|
||||
..add(DiagnosticsProperty('nextUpDateCutoff', nextUpDateCutoff))
|
||||
..add(DiagnosticsProperty('themeMode', themeMode))
|
||||
..add(DiagnosticsProperty('themeColor', themeColor))
|
||||
..add(DiagnosticsProperty('amoledBlack', amoledBlack))
|
||||
..add(DiagnosticsProperty('blurPlaceHolders', blurPlaceHolders))
|
||||
..add(DiagnosticsProperty('blurUpcomingEpisodes', blurUpcomingEpisodes))
|
||||
..add(DiagnosticsProperty('selectedLocale', selectedLocale))
|
||||
..add(DiagnosticsProperty('enableMediaKeys', enableMediaKeys))
|
||||
..add(DiagnosticsProperty('posterSize', posterSize))
|
||||
..add(DiagnosticsProperty('pinchPosterZoom', pinchPosterZoom))
|
||||
..add(DiagnosticsProperty('mouseDragSupport', mouseDragSupport))
|
||||
..add(DiagnosticsProperty('libraryPageSize', libraryPageSize));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ClientSettingsModelImpl &&
|
||||
(identical(other.syncPath, syncPath) ||
|
||||
other.syncPath == syncPath) &&
|
||||
(identical(other.position, position) ||
|
||||
other.position == position) &&
|
||||
(identical(other.size, size) || other.size == size) &&
|
||||
(identical(other.timeOut, timeOut) || other.timeOut == timeOut) &&
|
||||
(identical(other.nextUpDateCutoff, nextUpDateCutoff) ||
|
||||
other.nextUpDateCutoff == nextUpDateCutoff) &&
|
||||
(identical(other.themeMode, themeMode) ||
|
||||
other.themeMode == themeMode) &&
|
||||
(identical(other.themeColor, themeColor) ||
|
||||
other.themeColor == themeColor) &&
|
||||
(identical(other.amoledBlack, amoledBlack) ||
|
||||
other.amoledBlack == amoledBlack) &&
|
||||
(identical(other.blurPlaceHolders, blurPlaceHolders) ||
|
||||
other.blurPlaceHolders == blurPlaceHolders) &&
|
||||
(identical(other.blurUpcomingEpisodes, blurUpcomingEpisodes) ||
|
||||
other.blurUpcomingEpisodes == blurUpcomingEpisodes) &&
|
||||
(identical(other.selectedLocale, selectedLocale) ||
|
||||
other.selectedLocale == selectedLocale) &&
|
||||
(identical(other.enableMediaKeys, enableMediaKeys) ||
|
||||
other.enableMediaKeys == enableMediaKeys) &&
|
||||
(identical(other.posterSize, posterSize) ||
|
||||
other.posterSize == posterSize) &&
|
||||
(identical(other.pinchPosterZoom, pinchPosterZoom) ||
|
||||
other.pinchPosterZoom == pinchPosterZoom) &&
|
||||
(identical(other.mouseDragSupport, mouseDragSupport) ||
|
||||
other.mouseDragSupport == mouseDragSupport) &&
|
||||
(identical(other.libraryPageSize, libraryPageSize) ||
|
||||
other.libraryPageSize == libraryPageSize));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
syncPath,
|
||||
position,
|
||||
size,
|
||||
timeOut,
|
||||
nextUpDateCutoff,
|
||||
themeMode,
|
||||
themeColor,
|
||||
amoledBlack,
|
||||
blurPlaceHolders,
|
||||
blurUpcomingEpisodes,
|
||||
selectedLocale,
|
||||
enableMediaKeys,
|
||||
posterSize,
|
||||
pinchPosterZoom,
|
||||
mouseDragSupport,
|
||||
libraryPageSize);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ClientSettingsModelImplCopyWith<_$ClientSettingsModelImpl> get copyWith =>
|
||||
__$$ClientSettingsModelImplCopyWithImpl<_$ClientSettingsModelImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ClientSettingsModelImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ClientSettingsModel implements ClientSettingsModel {
|
||||
factory _ClientSettingsModel(
|
||||
{final String? syncPath,
|
||||
final Vector2 position,
|
||||
final Vector2 size,
|
||||
final Duration? timeOut,
|
||||
final Duration? nextUpDateCutoff,
|
||||
final ThemeMode themeMode,
|
||||
final ColorThemes? themeColor,
|
||||
final bool amoledBlack,
|
||||
final bool blurPlaceHolders,
|
||||
final bool blurUpcomingEpisodes,
|
||||
@LocaleConvert() final Locale? selectedLocale,
|
||||
final bool enableMediaKeys,
|
||||
final double posterSize,
|
||||
final bool pinchPosterZoom,
|
||||
final bool mouseDragSupport,
|
||||
final int? libraryPageSize}) = _$ClientSettingsModelImpl;
|
||||
|
||||
factory _ClientSettingsModel.fromJson(Map<String, dynamic> json) =
|
||||
_$ClientSettingsModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String? get syncPath;
|
||||
@override
|
||||
Vector2 get position;
|
||||
@override
|
||||
Vector2 get size;
|
||||
@override
|
||||
Duration? get timeOut;
|
||||
@override
|
||||
Duration? get nextUpDateCutoff;
|
||||
@override
|
||||
ThemeMode get themeMode;
|
||||
@override
|
||||
ColorThemes? get themeColor;
|
||||
@override
|
||||
bool get amoledBlack;
|
||||
@override
|
||||
bool get blurPlaceHolders;
|
||||
@override
|
||||
bool get blurUpcomingEpisodes;
|
||||
@override
|
||||
@LocaleConvert()
|
||||
Locale? get selectedLocale;
|
||||
@override
|
||||
bool get enableMediaKeys;
|
||||
@override
|
||||
double get posterSize;
|
||||
@override
|
||||
bool get pinchPosterZoom;
|
||||
@override
|
||||
bool get mouseDragSupport;
|
||||
@override
|
||||
int? get libraryPageSize;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ClientSettingsModelImplCopyWith<_$ClientSettingsModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
83
lib/models/settings/client_settings_model.g.dart
Normal file
83
lib/models/settings/client_settings_model.g.dart
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'client_settings_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ClientSettingsModelImpl _$$ClientSettingsModelImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$ClientSettingsModelImpl(
|
||||
syncPath: json['syncPath'] as String?,
|
||||
position: json['position'] == null
|
||||
? const Vector2(x: 0, y: 0)
|
||||
: Vector2.fromJson(json['position'] as String),
|
||||
size: json['size'] == null
|
||||
? const Vector2(x: 1280, y: 720)
|
||||
: Vector2.fromJson(json['size'] as String),
|
||||
timeOut: json['timeOut'] == null
|
||||
? const Duration(seconds: 30)
|
||||
: Duration(microseconds: (json['timeOut'] as num).toInt()),
|
||||
nextUpDateCutoff: json['nextUpDateCutoff'] == null
|
||||
? null
|
||||
: Duration(microseconds: (json['nextUpDateCutoff'] as num).toInt()),
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
||||
ThemeMode.system,
|
||||
themeColor: $enumDecodeNullable(_$ColorThemesEnumMap, json['themeColor']),
|
||||
amoledBlack: json['amoledBlack'] as bool? ?? false,
|
||||
blurPlaceHolders: json['blurPlaceHolders'] as bool? ?? false,
|
||||
blurUpcomingEpisodes: json['blurUpcomingEpisodes'] as bool? ?? false,
|
||||
selectedLocale:
|
||||
const LocaleConvert().fromJson(json['selectedLocale'] as String?),
|
||||
enableMediaKeys: json['enableMediaKeys'] as bool? ?? true,
|
||||
posterSize: (json['posterSize'] as num?)?.toDouble() ?? 1.0,
|
||||
pinchPosterZoom: json['pinchPosterZoom'] as bool? ?? false,
|
||||
mouseDragSupport: json['mouseDragSupport'] as bool? ?? false,
|
||||
libraryPageSize: (json['libraryPageSize'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ClientSettingsModelImplToJson(
|
||||
_$ClientSettingsModelImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'syncPath': instance.syncPath,
|
||||
'position': instance.position,
|
||||
'size': instance.size,
|
||||
'timeOut': instance.timeOut?.inMicroseconds,
|
||||
'nextUpDateCutoff': instance.nextUpDateCutoff?.inMicroseconds,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'themeColor': _$ColorThemesEnumMap[instance.themeColor],
|
||||
'amoledBlack': instance.amoledBlack,
|
||||
'blurPlaceHolders': instance.blurPlaceHolders,
|
||||
'blurUpcomingEpisodes': instance.blurUpcomingEpisodes,
|
||||
'selectedLocale': const LocaleConvert().toJson(instance.selectedLocale),
|
||||
'enableMediaKeys': instance.enableMediaKeys,
|
||||
'posterSize': instance.posterSize,
|
||||
'pinchPosterZoom': instance.pinchPosterZoom,
|
||||
'mouseDragSupport': instance.mouseDragSupport,
|
||||
'libraryPageSize': instance.libraryPageSize,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
ThemeMode.light: 'light',
|
||||
ThemeMode.dark: 'dark',
|
||||
};
|
||||
|
||||
const _$ColorThemesEnumMap = {
|
||||
ColorThemes.fladder: 'fladder',
|
||||
ColorThemes.deepOrange: 'deepOrange',
|
||||
ColorThemes.amber: 'amber',
|
||||
ColorThemes.green: 'green',
|
||||
ColorThemes.lightGreen: 'lightGreen',
|
||||
ColorThemes.lime: 'lime',
|
||||
ColorThemes.cyan: 'cyan',
|
||||
ColorThemes.blue: 'blue',
|
||||
ColorThemes.lightBlue: 'lightBlue',
|
||||
ColorThemes.indigo: 'indigo',
|
||||
ColorThemes.deepBlue: 'deepBlue',
|
||||
ColorThemes.brown: 'brown',
|
||||
ColorThemes.purple: 'purple',
|
||||
ColorThemes.deepPurple: 'deepPurple',
|
||||
ColorThemes.blueGrey: 'blueGrey',
|
||||
};
|
||||
108
lib/models/settings/home_settings_model.dart
Normal file
108
lib/models/settings/home_settings_model.dart
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum HomeCarouselSettings {
|
||||
off,
|
||||
nextUp,
|
||||
cont,
|
||||
combined,
|
||||
;
|
||||
|
||||
const HomeCarouselSettings();
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
HomeCarouselSettings.off => context.localized.hide,
|
||||
HomeCarouselSettings.nextUp => context.localized.nextUp,
|
||||
HomeCarouselSettings.cont => context.localized.settingsContinue,
|
||||
HomeCarouselSettings.combined => context.localized.combined,
|
||||
};
|
||||
|
||||
String toMap() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
static HomeCarouselSettings fromMap(String value) {
|
||||
return HomeCarouselSettings.values.firstWhereOrNull((element) => element.name == value) ??
|
||||
HomeCarouselSettings.combined;
|
||||
}
|
||||
}
|
||||
|
||||
enum HomeNextUp {
|
||||
off,
|
||||
nextUp,
|
||||
cont,
|
||||
combined,
|
||||
separate,
|
||||
;
|
||||
|
||||
const HomeNextUp();
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
HomeNextUp.off => context.localized.hide,
|
||||
HomeNextUp.nextUp => context.localized.nextUp,
|
||||
HomeNextUp.cont => context.localized.settingsContinue,
|
||||
HomeNextUp.combined => context.localized.combined,
|
||||
HomeNextUp.separate => context.localized.separate,
|
||||
};
|
||||
|
||||
String toMap() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
static HomeNextUp fromMap(String value) {
|
||||
return HomeNextUp.values.firstWhereOrNull((element) => element.name == value) ?? HomeNextUp.separate;
|
||||
}
|
||||
}
|
||||
|
||||
class HomeSettingsModel {
|
||||
final HomeCarouselSettings carouselSettings;
|
||||
final HomeNextUp nextUp;
|
||||
HomeSettingsModel({
|
||||
this.carouselSettings = HomeCarouselSettings.combined,
|
||||
this.nextUp = HomeNextUp.separate,
|
||||
});
|
||||
|
||||
HomeSettingsModel copyWith({
|
||||
HomeCarouselSettings? carouselSettings,
|
||||
HomeNextUp? nextUp,
|
||||
}) {
|
||||
return HomeSettingsModel(
|
||||
carouselSettings: carouselSettings ?? this.carouselSettings,
|
||||
nextUp: nextUp ?? this.nextUp,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'carouselSettings': carouselSettings.toMap(),
|
||||
'nextUp': nextUp.toMap(),
|
||||
};
|
||||
}
|
||||
|
||||
factory HomeSettingsModel.fromMap(Map<String, dynamic> map) {
|
||||
return HomeSettingsModel(
|
||||
carouselSettings: HomeCarouselSettings.fromMap(map['carouselSettings']),
|
||||
nextUp: HomeNextUp.fromMap(map['nextUp']),
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory HomeSettingsModel.fromJson(String source) => HomeSettingsModel.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
String toString() => 'HomeSettingsModel(carouselSettings: $carouselSettings, nextUp: $nextUp)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is HomeSettingsModel && other.carouselSettings == carouselSettings && other.nextUp == nextUp;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => carouselSettings.hashCode ^ nextUp.hashCode;
|
||||
}
|
||||
254
lib/models/settings/subtitle_settings_model.dart
Normal file
254
lib/models/settings/subtitle_settings_model.dart
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class SubtitleSettingsModel {
|
||||
final double fontSize;
|
||||
final FontWeight fontWeight;
|
||||
final double verticalOffset;
|
||||
final Color color;
|
||||
final Color outlineColor;
|
||||
final double outlineSize;
|
||||
final Color backGroundColor;
|
||||
final double shadow;
|
||||
const SubtitleSettingsModel({
|
||||
this.fontSize = 60,
|
||||
this.fontWeight = FontWeight.normal,
|
||||
this.verticalOffset = 0.10,
|
||||
this.color = Colors.white,
|
||||
this.outlineColor = const Color.fromRGBO(0, 0, 0, 0.85),
|
||||
this.outlineSize = 4,
|
||||
this.backGroundColor = const Color.fromARGB(0, 0, 0, 0),
|
||||
this.shadow = 0.5,
|
||||
});
|
||||
|
||||
SubtitleSettingsModel copyWith({
|
||||
double? fontSize,
|
||||
FontWeight? fontWeight,
|
||||
double? verticalOffset,
|
||||
Color? color,
|
||||
Color? outlineColor,
|
||||
double? outlineSize,
|
||||
Color? backGroundColor,
|
||||
double? shadow,
|
||||
}) {
|
||||
return SubtitleSettingsModel(
|
||||
fontSize: fontSize ?? this.fontSize,
|
||||
fontWeight: fontWeight ?? this.fontWeight,
|
||||
verticalOffset: verticalOffset ?? this.verticalOffset,
|
||||
color: color ?? this.color,
|
||||
outlineColor: outlineColor ?? this.outlineColor,
|
||||
outlineSize: outlineSize ?? this.outlineSize,
|
||||
backGroundColor: backGroundColor ?? this.backGroundColor,
|
||||
shadow: shadow ?? this.shadow,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle get backGroundStyle {
|
||||
return style.copyWith(
|
||||
shadows: (shadow > 0.01)
|
||||
? [
|
||||
Shadow(
|
||||
blurRadius: 16,
|
||||
color: Colors.black.withOpacity(shadow),
|
||||
),
|
||||
Shadow(
|
||||
blurRadius: 8,
|
||||
color: Colors.black.withOpacity(shadow),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
foreground: Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = outlineSize * (fontSize / 30)
|
||||
..color = outlineColor
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeJoin = StrokeJoin.round,
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle get style {
|
||||
return TextStyle(
|
||||
height: 1.4,
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontFamily: GoogleFonts.openSans().fontFamily,
|
||||
letterSpacing: 0.0,
|
||||
wordSpacing: 0.0,
|
||||
color: color,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'fontSize': fontSize,
|
||||
'fontWeight': fontWeight.value,
|
||||
'verticalOffset': verticalOffset,
|
||||
'color': color.value,
|
||||
'outlineColor': outlineColor.value,
|
||||
'outlineSize': outlineSize,
|
||||
'backGroundColor': backGroundColor.value,
|
||||
'shadow': shadow,
|
||||
};
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory SubtitleSettingsModel.fromJson(String source) => SubtitleSettingsModel.fromMap(json.decode(source));
|
||||
|
||||
factory SubtitleSettingsModel.fromMap(Map<String, dynamic> map) {
|
||||
return const SubtitleSettingsModel().copyWith(
|
||||
fontSize: map['fontSize'] as double?,
|
||||
fontWeight: FontWeight.values.firstWhereOrNull((element) => element.index == map['fontWeight'] as int?),
|
||||
verticalOffset: map['verticalOffset'] as double?,
|
||||
color: map['color'] != null ? Color(map['color'] as int) : null,
|
||||
outlineColor: map['outlineColor'] != null ? Color(map['outlineColor'] as int) : null,
|
||||
outlineSize: map['outlineSize'] as double?,
|
||||
backGroundColor: map['backGroundColor'] != null ? Color(map['backGroundColor'] as int) : null,
|
||||
shadow: map['shadow'] as double?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SubtitleSettingsModel(fontSize: $fontSize, fontWeight: $fontWeight, verticalOffset: $verticalOffset, color: $color, outlineColor: $outlineColor, outlineSize: $outlineSize, backGroundColor: $backGroundColor, shadow: $shadow)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant SubtitleSettingsModel other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.fontSize == fontSize &&
|
||||
other.fontWeight == fontWeight &&
|
||||
other.verticalOffset == verticalOffset &&
|
||||
other.color == color &&
|
||||
other.outlineColor == outlineColor &&
|
||||
other.outlineSize == outlineSize &&
|
||||
other.backGroundColor == backGroundColor &&
|
||||
other.shadow == shadow;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return fontSize.hashCode ^
|
||||
fontWeight.hashCode ^
|
||||
verticalOffset.hashCode ^
|
||||
color.hashCode ^
|
||||
outlineColor.hashCode ^
|
||||
outlineSize.hashCode ^
|
||||
backGroundColor.hashCode ^
|
||||
shadow.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
class SubtitleText extends ConsumerWidget {
|
||||
final SubtitleSettingsModel subModel;
|
||||
final EdgeInsets padding;
|
||||
final String text;
|
||||
final double offset;
|
||||
const SubtitleText({
|
||||
required this.subModel,
|
||||
required this.padding,
|
||||
required this.offset,
|
||||
required this.text,
|
||||
super.key,
|
||||
});
|
||||
|
||||
// The reference width for calculating the visible text scale factor.
|
||||
static const kTextScaleFactorReferenceWidth = 1920.0;
|
||||
// The reference height for calculating the visible text scale factor.
|
||||
static const kTextScaleFactorReferenceHeight = 1080.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fillScreen = ref.watch(videoPlayerSettingsProvider.select((value) => value.fillScreen));
|
||||
return Padding(
|
||||
padding: (fillScreen ? EdgeInsets.zero : EdgeInsets.only(left: padding.left, right: padding.right))
|
||||
.add(const EdgeInsets.all(16)),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final textScale = MediaQuery.textScalerOf(context)
|
||||
.scale((ref.read(subtitleSettingsProvider.select((value) => value.fontSize)) *
|
||||
math.sqrt(
|
||||
((constraints.maxWidth * constraints.maxHeight) /
|
||||
(kTextScaleFactorReferenceWidth * kTextScaleFactorReferenceHeight))
|
||||
.clamp(0.0, 1.0),
|
||||
)));
|
||||
|
||||
// Function to calculate the height of the text
|
||||
double getTextHeight(BuildContext context, String text, TextStyle style) {
|
||||
final TextPainter textPainter = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
textDirection: TextDirection.ltr,
|
||||
textScaler: MediaQuery.textScalerOf(context),
|
||||
)..layout(minWidth: 0, maxWidth: double.infinity);
|
||||
|
||||
return textPainter.height;
|
||||
}
|
||||
|
||||
// Calculate the available height for the text alignment
|
||||
double availableHeight = constraints.maxHeight;
|
||||
|
||||
// Calculate the desired position based on the percentage
|
||||
double desiredPosition = availableHeight * offset;
|
||||
|
||||
// Get the height of the Text widget with the current font style
|
||||
double textHeight = getTextHeight(context, text, subModel.style);
|
||||
|
||||
// Calculate the position to keep the text within visible bounds
|
||||
double position = desiredPosition - textHeight / 2;
|
||||
|
||||
// Ensure the text doesn't go off-screen
|
||||
position = position.clamp(0, availableHeight - textHeight);
|
||||
|
||||
return IgnorePointer(
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: position,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight),
|
||||
decoration: BoxDecoration(
|
||||
color: subModel.backGroundColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: subModel.backGroundStyle.copyWith(fontSize: textScale),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: position,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
text,
|
||||
style: subModel.style.copyWith(fontSize: textScale),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
113
lib/models/settings/video_player_settings.dart
Normal file
113
lib/models/settings/video_player_settings.dart
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class VideoPlayerSettingsModel {
|
||||
final double? screenBrightness;
|
||||
final BoxFit videoFit;
|
||||
final bool fillScreen;
|
||||
final bool hardwareAccel;
|
||||
final bool useLibass;
|
||||
final double internalVolume;
|
||||
final String? audioDevice;
|
||||
const VideoPlayerSettingsModel({
|
||||
this.screenBrightness,
|
||||
this.videoFit = BoxFit.contain,
|
||||
this.fillScreen = false,
|
||||
this.hardwareAccel = true,
|
||||
this.useLibass = false,
|
||||
this.internalVolume = 100,
|
||||
this.audioDevice,
|
||||
});
|
||||
|
||||
double get volume => switch (defaultTargetPlatform) {
|
||||
TargetPlatform.android || TargetPlatform.iOS => 100,
|
||||
_ => internalVolume,
|
||||
};
|
||||
|
||||
VideoPlayerSettingsModel copyWith({
|
||||
ValueGetter<double?>? screenBrightness,
|
||||
BoxFit? videoFit,
|
||||
bool? fillScreen,
|
||||
bool? hardwareAccel,
|
||||
bool? useLibass,
|
||||
double? internalVolume,
|
||||
ValueGetter<String?>? audioDevice,
|
||||
}) {
|
||||
return VideoPlayerSettingsModel(
|
||||
screenBrightness: screenBrightness != null ? screenBrightness() : this.screenBrightness,
|
||||
videoFit: videoFit ?? this.videoFit,
|
||||
fillScreen: fillScreen ?? this.fillScreen,
|
||||
hardwareAccel: hardwareAccel ?? this.hardwareAccel,
|
||||
useLibass: useLibass ?? this.useLibass,
|
||||
internalVolume: internalVolume ?? this.internalVolume,
|
||||
audioDevice: audioDevice != null ? audioDevice() : this.audioDevice,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'screenBrightness': screenBrightness,
|
||||
'videoFit': videoFit.name,
|
||||
'fillScreen': fillScreen,
|
||||
'hardwareAccel': hardwareAccel,
|
||||
'useLibass': useLibass,
|
||||
'internalVolume': internalVolume,
|
||||
'audioDevice': audioDevice,
|
||||
};
|
||||
}
|
||||
|
||||
factory VideoPlayerSettingsModel.fromMap(Map<String, dynamic> map) {
|
||||
return VideoPlayerSettingsModel(
|
||||
screenBrightness: map['screenBrightness']?.toDouble(),
|
||||
videoFit: BoxFit.values.firstWhereOrNull((element) => element.name == map['videoFit']) ?? BoxFit.contain,
|
||||
fillScreen: map['fillScreen'] ?? false,
|
||||
hardwareAccel: map['hardwareAccel'] ?? false,
|
||||
useLibass: map['useLibass'] ?? false,
|
||||
internalVolume: map['internalVolume']?.toDouble() ?? 0.0,
|
||||
audioDevice: map['audioDevice'],
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory VideoPlayerSettingsModel.fromJson(String source) => VideoPlayerSettingsModel.fromMap(json.decode(source));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, internalVolume: $internalVolume, audioDevice: $audioDevice)';
|
||||
}
|
||||
|
||||
bool playerSame(VideoPlayerSettingsModel other) {
|
||||
return other.hardwareAccel == hardwareAccel && other.useLibass == useLibass;
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is VideoPlayerSettingsModel &&
|
||||
other.screenBrightness == screenBrightness &&
|
||||
other.videoFit == videoFit &&
|
||||
other.fillScreen == fillScreen &&
|
||||
other.hardwareAccel == hardwareAccel &&
|
||||
other.useLibass == useLibass &&
|
||||
other.internalVolume == internalVolume &&
|
||||
other.audioDevice == audioDevice;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return screenBrightness.hashCode ^
|
||||
videoFit.hashCode ^
|
||||
fillScreen.hashCode ^
|
||||
hardwareAccel.hashCode ^
|
||||
useLibass.hashCode ^
|
||||
internalVolume.hashCode ^
|
||||
audioDevice.hashCode;
|
||||
}
|
||||
}
|
||||
41
lib/models/syncing/download_stream.dart
Normal file
41
lib/models/syncing/download_stream.dart
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import 'package:background_downloader/background_downloader.dart' as dl;
|
||||
|
||||
class DownloadStream {
|
||||
final String id;
|
||||
final dl.DownloadTask? task;
|
||||
final double progress;
|
||||
final dl.TaskStatus status;
|
||||
DownloadStream({
|
||||
required this.id,
|
||||
this.task,
|
||||
required this.progress,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
DownloadStream.empty()
|
||||
: id = '',
|
||||
task = null,
|
||||
progress = -1,
|
||||
status = dl.TaskStatus.notFound;
|
||||
|
||||
bool get hasDownload => progress != -1.0 && status != dl.TaskStatus.notFound && status != dl.TaskStatus.complete;
|
||||
|
||||
DownloadStream copyWith({
|
||||
String? id,
|
||||
dl.DownloadTask? task,
|
||||
double? progress,
|
||||
dl.TaskStatus? status,
|
||||
}) {
|
||||
return DownloadStream(
|
||||
id: id ?? this.id,
|
||||
task: task ?? this.task,
|
||||
progress: progress ?? this.progress,
|
||||
status: status ?? this.status,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DownloadStream(id: $id, task: $task, progress: $progress, status: $status)';
|
||||
}
|
||||
}
|
||||
75
lib/models/syncing/i_synced_item.dart
Normal file
75
lib/models/syncing/i_synced_item.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'i_synced_item.g.dart';
|
||||
|
||||
// extension IsarExtensions on String? {
|
||||
// int get fastHash {
|
||||
// if (this == null) return 0;
|
||||
// var hash = 0xcbf29ce484222325;
|
||||
|
||||
// var i = 0;
|
||||
// while (i < this!.length) {
|
||||
// final codeUnit = this!.codeUnitAt(i++);
|
||||
// hash ^= codeUnit >> 8;
|
||||
// hash *= 0x100000001b3;
|
||||
// hash ^= codeUnit & 0xFF;
|
||||
// hash *= 0x100000001b3;
|
||||
// }
|
||||
|
||||
// return hash;
|
||||
// }
|
||||
// }
|
||||
|
||||
@collection
|
||||
class ISyncedItem {
|
||||
String? userId;
|
||||
String id;
|
||||
int? sortKey;
|
||||
String? parentId;
|
||||
String? path;
|
||||
int? fileSize;
|
||||
String? videoFileName;
|
||||
String? trickPlayModel;
|
||||
String? introOutroSkipModel;
|
||||
String? images;
|
||||
List<String>? chapters;
|
||||
List<String>? subtitles;
|
||||
String? userData;
|
||||
ISyncedItem({
|
||||
this.userId,
|
||||
required this.id,
|
||||
this.sortKey,
|
||||
this.parentId,
|
||||
this.path,
|
||||
this.fileSize,
|
||||
this.videoFileName,
|
||||
this.trickPlayModel,
|
||||
this.introOutroSkipModel,
|
||||
this.images,
|
||||
this.chapters,
|
||||
this.subtitles,
|
||||
this.userData,
|
||||
});
|
||||
|
||||
factory ISyncedItem.fromSynced(SyncedItem syncedItem, String? path) {
|
||||
return ISyncedItem(
|
||||
id: syncedItem.id,
|
||||
parentId: syncedItem.parentId,
|
||||
userId: syncedItem.userId,
|
||||
path: syncedItem.path?.replaceAll(path ?? "", '').substring(1),
|
||||
fileSize: syncedItem.fileSize,
|
||||
sortKey: syncedItem.sortKey,
|
||||
videoFileName: syncedItem.videoFileName,
|
||||
trickPlayModel: syncedItem.fTrickPlayModel != null ? jsonEncode(syncedItem.fTrickPlayModel?.toJson()) : null,
|
||||
introOutroSkipModel:
|
||||
syncedItem.introOutSkipModel != null ? jsonEncode(syncedItem.introOutSkipModel?.toJson()) : null,
|
||||
images: syncedItem.fImages != null ? jsonEncode(syncedItem.fImages?.toJson()) : null,
|
||||
chapters: syncedItem.fChapters.map((e) => jsonEncode(e.toJson())).toList(),
|
||||
subtitles: syncedItem.subtitles.map((e) => jsonEncode(e.toJson())).toList(),
|
||||
userData: syncedItem.userData != null ? jsonEncode(syncedItem.userData?.toJson()) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
3600
lib/models/syncing/i_synced_item.g.dart
Normal file
3600
lib/models/syncing/i_synced_item.g.dart
Normal file
File diff suppressed because it is too large
Load diff
197
lib/models/syncing/sync_item.dart
Normal file
197
lib/models/syncing/sync_item.dart
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
part 'sync_item.freezed.dart';
|
||||
part 'sync_item.g.dart';
|
||||
|
||||
@freezed
|
||||
class SyncedItem with _$SyncedItem {
|
||||
const SyncedItem._();
|
||||
|
||||
factory SyncedItem({
|
||||
required String id,
|
||||
String? parentId,
|
||||
required String userId,
|
||||
String? path,
|
||||
@Default(false) bool markedForDelete,
|
||||
int? sortKey,
|
||||
int? fileSize,
|
||||
String? videoFileName,
|
||||
IntroOutSkipModel? introOutSkipModel,
|
||||
TrickPlayModel? fTrickPlayModel,
|
||||
ImagesData? fImages,
|
||||
@Default([]) List<Chapter> fChapters,
|
||||
@Default([]) List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData,
|
||||
}) = _SyncItem;
|
||||
|
||||
static String trickPlayPath = "TrickPlay";
|
||||
|
||||
List<Chapter> get chapters => fChapters.map((e) => e.copyWith(imageUrl: joinAll({"$path", e.imageUrl}))).toList();
|
||||
|
||||
ImagesData? get images => fImages?.copyWith(
|
||||
primary: () => fImages?.primary?.copyWith(path: joinAll(["$path", "${fImages?.primary?.path}"])),
|
||||
logo: () => fImages?.logo?.copyWith(path: joinAll(["$path", "${fImages?.logo?.path}"])),
|
||||
backDrop: () => fImages?.backDrop?.map((e) => e.copyWith(path: joinAll(["$path", (e.path)]))).toList(),
|
||||
);
|
||||
|
||||
TrickPlayModel? get trickPlayModel => fTrickPlayModel?.copyWith(
|
||||
images: fTrickPlayModel?.images
|
||||
.map(
|
||||
(trickPlayPath) => joinAll(["$path", trickPlayPath]),
|
||||
)
|
||||
.toList() ??
|
||||
[]);
|
||||
|
||||
File get dataFile => File(joinAll(["$path", "data.json"]));
|
||||
Directory get trickPlayDirectory => Directory(joinAll(["$path", trickPlayPath]));
|
||||
File get videoFile => File(joinAll(["$path", "$videoFileName"]));
|
||||
Directory get directory => Directory(path ?? "");
|
||||
|
||||
SyncStatus get status => switch (videoFile.existsSync()) {
|
||||
true => SyncStatus.complete,
|
||||
_ => SyncStatus.partially,
|
||||
};
|
||||
|
||||
String? get taskId => null;
|
||||
|
||||
bool get childHasTask => false;
|
||||
|
||||
double get totalProgress => 0.0;
|
||||
|
||||
bool get hasVideoFile => videoFileName?.isNotEmpty == true && (fileSize ?? 0) > 0;
|
||||
|
||||
TaskStatus get anyStatus {
|
||||
return TaskStatus.notFound;
|
||||
}
|
||||
|
||||
double get downloadProgress => 0.0;
|
||||
TaskStatus get downloadStatus => TaskStatus.notFound;
|
||||
DownloadTask? get task => null;
|
||||
|
||||
Future<bool> deleteDatFiles(Ref ref) async {
|
||||
try {
|
||||
await videoFile.delete();
|
||||
await Directory(joinAll([directory.path, trickPlayPath])).delete(recursive: true);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
List<SyncedItem> nestedChildren(WidgetRef ref) => ref.watch(syncChildrenProvider(this));
|
||||
|
||||
List<SyncedItem> getChildren(Ref ref) => ref.read(syncProvider.notifier).getChildren(this);
|
||||
List<SyncedItem> getNestedChildren(Ref ref) => ref.read(syncProvider.notifier).getNestedChildren(this);
|
||||
|
||||
Future<int> get getDirSize async {
|
||||
var files = await directory.list(recursive: true).toList();
|
||||
var dirSize = files.fold(0, (int sum, file) => sum + file.statSync().size);
|
||||
return dirSize;
|
||||
}
|
||||
|
||||
ItemBaseModel? createItemModel(Ref ref) {
|
||||
if (!dataFile.existsSync()) return null;
|
||||
final BaseItemDto itemDto = BaseItemDto.fromJson(jsonDecode(dataFile.readAsStringSync()));
|
||||
final itemModel = ItemBaseModel.fromBaseDto(itemDto, ref);
|
||||
return itemModel.copyWith(
|
||||
images: images,
|
||||
userData: userData,
|
||||
);
|
||||
}
|
||||
|
||||
factory SyncedItem.fromJson(Map<String, dynamic> json) => _$SyncedItemFromJson(json);
|
||||
|
||||
factory SyncedItem.fromIsar(ISyncedItem isarSyncedItem, String savePath) {
|
||||
return SyncedItem(
|
||||
id: isarSyncedItem.id,
|
||||
parentId: isarSyncedItem.parentId,
|
||||
userId: isarSyncedItem.userId ?? "",
|
||||
sortKey: isarSyncedItem.sortKey,
|
||||
path: joinAll([savePath, isarSyncedItem.path ?? ""]),
|
||||
fileSize: isarSyncedItem.fileSize,
|
||||
videoFileName: isarSyncedItem.videoFileName,
|
||||
introOutSkipModel: isarSyncedItem.introOutroSkipModel != null
|
||||
? IntroOutSkipModel.fromJson(jsonDecode(isarSyncedItem.introOutroSkipModel!))
|
||||
: null,
|
||||
fTrickPlayModel: isarSyncedItem.trickPlayModel != null
|
||||
? TrickPlayModel.fromJson(jsonDecode(isarSyncedItem.trickPlayModel!))
|
||||
: null,
|
||||
fImages: isarSyncedItem.images != null ? ImagesData.fromJson(jsonDecode(isarSyncedItem.images!)) : null,
|
||||
fChapters: isarSyncedItem.chapters
|
||||
?.map(
|
||||
(e) => Chapter.fromJson(jsonDecode(e)),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
subtitles: isarSyncedItem.subtitles
|
||||
?.map(
|
||||
(e) => SubStreamModel.fromJson(jsonDecode(e)),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
userData: isarSyncedItem.userData != null ? UserData.fromJson(jsonDecode(isarSyncedItem.userData!)) : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum SyncStatus {
|
||||
complete(
|
||||
"Synced",
|
||||
Color.fromARGB(255, 141, 214, 58),
|
||||
IconsaxOutline.tick_circle,
|
||||
),
|
||||
partially(
|
||||
"Partially",
|
||||
Color.fromARGB(255, 221, 135, 23),
|
||||
IconsaxOutline.more_circle,
|
||||
),
|
||||
;
|
||||
|
||||
const SyncStatus(this.label, this.color, this.icon);
|
||||
|
||||
final Color color;
|
||||
final String label;
|
||||
final IconData icon;
|
||||
}
|
||||
|
||||
extension StatusExtension on TaskStatus {
|
||||
Color color(BuildContext context) => switch (this) {
|
||||
TaskStatus.enqueued => Colors.blueAccent,
|
||||
TaskStatus.running => Colors.limeAccent,
|
||||
TaskStatus.complete => Colors.limeAccent,
|
||||
TaskStatus.canceled || TaskStatus.notFound || TaskStatus.failed => Theme.of(context).colorScheme.error,
|
||||
TaskStatus.waitingToRetry => Colors.yellowAccent,
|
||||
TaskStatus.paused => Colors.orangeAccent,
|
||||
};
|
||||
|
||||
String get name => switch (this) {
|
||||
TaskStatus.enqueued => 'Enqueued',
|
||||
TaskStatus.running => 'Running',
|
||||
TaskStatus.complete => 'Complete',
|
||||
TaskStatus.notFound => 'Not Found',
|
||||
TaskStatus.failed => 'Failed',
|
||||
TaskStatus.canceled => 'Canceled',
|
||||
TaskStatus.waitingToRetry => 'Waiting To Retry',
|
||||
TaskStatus.paused => 'Paused',
|
||||
};
|
||||
}
|
||||
494
lib/models/syncing/sync_item.freezed.dart
Normal file
494
lib/models/syncing/sync_item.freezed.dart
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'sync_item.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
SyncedItem _$SyncedItemFromJson(Map<String, dynamic> json) {
|
||||
return _SyncItem.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SyncedItem {
|
||||
String get id => throw _privateConstructorUsedError;
|
||||
String? get parentId => throw _privateConstructorUsedError;
|
||||
String get userId => throw _privateConstructorUsedError;
|
||||
String? get path => throw _privateConstructorUsedError;
|
||||
bool get markedForDelete => throw _privateConstructorUsedError;
|
||||
int? get sortKey => throw _privateConstructorUsedError;
|
||||
int? get fileSize => throw _privateConstructorUsedError;
|
||||
String? get videoFileName => throw _privateConstructorUsedError;
|
||||
IntroOutSkipModel? get introOutSkipModel =>
|
||||
throw _privateConstructorUsedError;
|
||||
TrickPlayModel? get fTrickPlayModel => throw _privateConstructorUsedError;
|
||||
ImagesData? get fImages => throw _privateConstructorUsedError;
|
||||
List<Chapter> get fChapters => throw _privateConstructorUsedError;
|
||||
List<SubStreamModel> get subtitles => throw _privateConstructorUsedError;
|
||||
@UserDataJsonSerializer()
|
||||
UserData? get userData => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$SyncedItemCopyWith<SyncedItem> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SyncedItemCopyWith<$Res> {
|
||||
factory $SyncedItemCopyWith(
|
||||
SyncedItem value, $Res Function(SyncedItem) then) =
|
||||
_$SyncedItemCopyWithImpl<$Res, SyncedItem>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String id,
|
||||
String? parentId,
|
||||
String userId,
|
||||
String? path,
|
||||
bool markedForDelete,
|
||||
int? sortKey,
|
||||
int? fileSize,
|
||||
String? videoFileName,
|
||||
IntroOutSkipModel? introOutSkipModel,
|
||||
TrickPlayModel? fTrickPlayModel,
|
||||
ImagesData? fImages,
|
||||
List<Chapter> fChapters,
|
||||
List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData});
|
||||
|
||||
$IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel;
|
||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
|
||||
implements $SyncedItemCopyWith<$Res> {
|
||||
_$SyncedItemCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? parentId = freezed,
|
||||
Object? userId = null,
|
||||
Object? path = freezed,
|
||||
Object? markedForDelete = null,
|
||||
Object? sortKey = freezed,
|
||||
Object? fileSize = freezed,
|
||||
Object? videoFileName = freezed,
|
||||
Object? introOutSkipModel = freezed,
|
||||
Object? fTrickPlayModel = freezed,
|
||||
Object? fImages = freezed,
|
||||
Object? fChapters = null,
|
||||
Object? subtitles = null,
|
||||
Object? userData = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
parentId: freezed == parentId
|
||||
? _value.parentId
|
||||
: parentId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
path: freezed == path
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
markedForDelete: null == markedForDelete
|
||||
? _value.markedForDelete
|
||||
: markedForDelete // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
sortKey: freezed == sortKey
|
||||
? _value.sortKey
|
||||
: sortKey // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
fileSize: freezed == fileSize
|
||||
? _value.fileSize
|
||||
: fileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
videoFileName: freezed == videoFileName
|
||||
? _value.videoFileName
|
||||
: videoFileName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
introOutSkipModel: freezed == introOutSkipModel
|
||||
? _value.introOutSkipModel
|
||||
: introOutSkipModel // ignore: cast_nullable_to_non_nullable
|
||||
as IntroOutSkipModel?,
|
||||
fTrickPlayModel: freezed == fTrickPlayModel
|
||||
? _value.fTrickPlayModel
|
||||
: fTrickPlayModel // ignore: cast_nullable_to_non_nullable
|
||||
as TrickPlayModel?,
|
||||
fImages: freezed == fImages
|
||||
? _value.fImages
|
||||
: fImages // ignore: cast_nullable_to_non_nullable
|
||||
as ImagesData?,
|
||||
fChapters: null == fChapters
|
||||
? _value.fChapters
|
||||
: fChapters // ignore: cast_nullable_to_non_nullable
|
||||
as List<Chapter>,
|
||||
subtitles: null == subtitles
|
||||
? _value.subtitles
|
||||
: subtitles // ignore: cast_nullable_to_non_nullable
|
||||
as List<SubStreamModel>,
|
||||
userData: freezed == userData
|
||||
? _value.userData
|
||||
: userData // ignore: cast_nullable_to_non_nullable
|
||||
as UserData?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel {
|
||||
if (_value.introOutSkipModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $IntroOutSkipModelCopyWith<$Res>(_value.introOutSkipModel!, (value) {
|
||||
return _then(_value.copyWith(introOutSkipModel: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel {
|
||||
if (_value.fTrickPlayModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $TrickPlayModelCopyWith<$Res>(_value.fTrickPlayModel!, (value) {
|
||||
return _then(_value.copyWith(fTrickPlayModel: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SyncItemImplCopyWith<$Res>
|
||||
implements $SyncedItemCopyWith<$Res> {
|
||||
factory _$$SyncItemImplCopyWith(
|
||||
_$SyncItemImpl value, $Res Function(_$SyncItemImpl) then) =
|
||||
__$$SyncItemImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String id,
|
||||
String? parentId,
|
||||
String userId,
|
||||
String? path,
|
||||
bool markedForDelete,
|
||||
int? sortKey,
|
||||
int? fileSize,
|
||||
String? videoFileName,
|
||||
IntroOutSkipModel? introOutSkipModel,
|
||||
TrickPlayModel? fTrickPlayModel,
|
||||
ImagesData? fImages,
|
||||
List<Chapter> fChapters,
|
||||
List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData});
|
||||
|
||||
@override
|
||||
$IntroOutSkipModelCopyWith<$Res>? get introOutSkipModel;
|
||||
@override
|
||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SyncItemImplCopyWithImpl<$Res>
|
||||
extends _$SyncedItemCopyWithImpl<$Res, _$SyncItemImpl>
|
||||
implements _$$SyncItemImplCopyWith<$Res> {
|
||||
__$$SyncItemImplCopyWithImpl(
|
||||
_$SyncItemImpl _value, $Res Function(_$SyncItemImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? id = null,
|
||||
Object? parentId = freezed,
|
||||
Object? userId = null,
|
||||
Object? path = freezed,
|
||||
Object? markedForDelete = null,
|
||||
Object? sortKey = freezed,
|
||||
Object? fileSize = freezed,
|
||||
Object? videoFileName = freezed,
|
||||
Object? introOutSkipModel = freezed,
|
||||
Object? fTrickPlayModel = freezed,
|
||||
Object? fImages = freezed,
|
||||
Object? fChapters = null,
|
||||
Object? subtitles = null,
|
||||
Object? userData = freezed,
|
||||
}) {
|
||||
return _then(_$SyncItemImpl(
|
||||
id: null == id
|
||||
? _value.id
|
||||
: id // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
parentId: freezed == parentId
|
||||
? _value.parentId
|
||||
: parentId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
userId: null == userId
|
||||
? _value.userId
|
||||
: userId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
path: freezed == path
|
||||
? _value.path
|
||||
: path // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
markedForDelete: null == markedForDelete
|
||||
? _value.markedForDelete
|
||||
: markedForDelete // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
sortKey: freezed == sortKey
|
||||
? _value.sortKey
|
||||
: sortKey // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
fileSize: freezed == fileSize
|
||||
? _value.fileSize
|
||||
: fileSize // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
videoFileName: freezed == videoFileName
|
||||
? _value.videoFileName
|
||||
: videoFileName // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
introOutSkipModel: freezed == introOutSkipModel
|
||||
? _value.introOutSkipModel
|
||||
: introOutSkipModel // ignore: cast_nullable_to_non_nullable
|
||||
as IntroOutSkipModel?,
|
||||
fTrickPlayModel: freezed == fTrickPlayModel
|
||||
? _value.fTrickPlayModel
|
||||
: fTrickPlayModel // ignore: cast_nullable_to_non_nullable
|
||||
as TrickPlayModel?,
|
||||
fImages: freezed == fImages
|
||||
? _value.fImages
|
||||
: fImages // ignore: cast_nullable_to_non_nullable
|
||||
as ImagesData?,
|
||||
fChapters: null == fChapters
|
||||
? _value._fChapters
|
||||
: fChapters // ignore: cast_nullable_to_non_nullable
|
||||
as List<Chapter>,
|
||||
subtitles: null == subtitles
|
||||
? _value._subtitles
|
||||
: subtitles // ignore: cast_nullable_to_non_nullable
|
||||
as List<SubStreamModel>,
|
||||
userData: freezed == userData
|
||||
? _value.userData
|
||||
: userData // ignore: cast_nullable_to_non_nullable
|
||||
as UserData?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$SyncItemImpl extends _SyncItem {
|
||||
_$SyncItemImpl(
|
||||
{required this.id,
|
||||
this.parentId,
|
||||
required this.userId,
|
||||
this.path,
|
||||
this.markedForDelete = false,
|
||||
this.sortKey,
|
||||
this.fileSize,
|
||||
this.videoFileName,
|
||||
this.introOutSkipModel,
|
||||
this.fTrickPlayModel,
|
||||
this.fImages,
|
||||
final List<Chapter> fChapters = const [],
|
||||
final List<SubStreamModel> subtitles = const [],
|
||||
@UserDataJsonSerializer() this.userData})
|
||||
: _fChapters = fChapters,
|
||||
_subtitles = subtitles,
|
||||
super._();
|
||||
|
||||
factory _$SyncItemImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$SyncItemImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String? parentId;
|
||||
@override
|
||||
final String userId;
|
||||
@override
|
||||
final String? path;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool markedForDelete;
|
||||
@override
|
||||
final int? sortKey;
|
||||
@override
|
||||
final int? fileSize;
|
||||
@override
|
||||
final String? videoFileName;
|
||||
@override
|
||||
final IntroOutSkipModel? introOutSkipModel;
|
||||
@override
|
||||
final TrickPlayModel? fTrickPlayModel;
|
||||
@override
|
||||
final ImagesData? fImages;
|
||||
final List<Chapter> _fChapters;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Chapter> get fChapters {
|
||||
if (_fChapters is EqualUnmodifiableListView) return _fChapters;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_fChapters);
|
||||
}
|
||||
|
||||
final List<SubStreamModel> _subtitles;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<SubStreamModel> get subtitles {
|
||||
if (_subtitles is EqualUnmodifiableListView) return _subtitles;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_subtitles);
|
||||
}
|
||||
|
||||
@override
|
||||
@UserDataJsonSerializer()
|
||||
final UserData? userData;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SyncedItem(id: $id, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortKey: $sortKey, fileSize: $fileSize, videoFileName: $videoFileName, introOutSkipModel: $introOutSkipModel, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SyncItemImpl &&
|
||||
(identical(other.id, id) || other.id == id) &&
|
||||
(identical(other.parentId, parentId) ||
|
||||
other.parentId == parentId) &&
|
||||
(identical(other.userId, userId) || other.userId == userId) &&
|
||||
(identical(other.path, path) || other.path == path) &&
|
||||
(identical(other.markedForDelete, markedForDelete) ||
|
||||
other.markedForDelete == markedForDelete) &&
|
||||
(identical(other.sortKey, sortKey) || other.sortKey == sortKey) &&
|
||||
(identical(other.fileSize, fileSize) ||
|
||||
other.fileSize == fileSize) &&
|
||||
(identical(other.videoFileName, videoFileName) ||
|
||||
other.videoFileName == videoFileName) &&
|
||||
(identical(other.introOutSkipModel, introOutSkipModel) ||
|
||||
other.introOutSkipModel == introOutSkipModel) &&
|
||||
(identical(other.fTrickPlayModel, fTrickPlayModel) ||
|
||||
other.fTrickPlayModel == fTrickPlayModel) &&
|
||||
(identical(other.fImages, fImages) || other.fImages == fImages) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._fChapters, _fChapters) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._subtitles, _subtitles) &&
|
||||
(identical(other.userData, userData) ||
|
||||
other.userData == userData));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
id,
|
||||
parentId,
|
||||
userId,
|
||||
path,
|
||||
markedForDelete,
|
||||
sortKey,
|
||||
fileSize,
|
||||
videoFileName,
|
||||
introOutSkipModel,
|
||||
fTrickPlayModel,
|
||||
fImages,
|
||||
const DeepCollectionEquality().hash(_fChapters),
|
||||
const DeepCollectionEquality().hash(_subtitles),
|
||||
userData);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SyncItemImplCopyWith<_$SyncItemImpl> get copyWith =>
|
||||
__$$SyncItemImplCopyWithImpl<_$SyncItemImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$SyncItemImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SyncItem extends SyncedItem {
|
||||
factory _SyncItem(
|
||||
{required final String id,
|
||||
final String? parentId,
|
||||
required final String userId,
|
||||
final String? path,
|
||||
final bool markedForDelete,
|
||||
final int? sortKey,
|
||||
final int? fileSize,
|
||||
final String? videoFileName,
|
||||
final IntroOutSkipModel? introOutSkipModel,
|
||||
final TrickPlayModel? fTrickPlayModel,
|
||||
final ImagesData? fImages,
|
||||
final List<Chapter> fChapters,
|
||||
final List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() final UserData? userData}) = _$SyncItemImpl;
|
||||
_SyncItem._() : super._();
|
||||
|
||||
factory _SyncItem.fromJson(Map<String, dynamic> json) =
|
||||
_$SyncItemImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get id;
|
||||
@override
|
||||
String? get parentId;
|
||||
@override
|
||||
String get userId;
|
||||
@override
|
||||
String? get path;
|
||||
@override
|
||||
bool get markedForDelete;
|
||||
@override
|
||||
int? get sortKey;
|
||||
@override
|
||||
int? get fileSize;
|
||||
@override
|
||||
String? get videoFileName;
|
||||
@override
|
||||
IntroOutSkipModel? get introOutSkipModel;
|
||||
@override
|
||||
TrickPlayModel? get fTrickPlayModel;
|
||||
@override
|
||||
ImagesData? get fImages;
|
||||
@override
|
||||
List<Chapter> get fChapters;
|
||||
@override
|
||||
List<SubStreamModel> get subtitles;
|
||||
@override
|
||||
@UserDataJsonSerializer()
|
||||
UserData? get userData;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$SyncItemImplCopyWith<_$SyncItemImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
71
lib/models/syncing/sync_item.g.dart
Normal file
71
lib/models/syncing/sync_item.g.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sync_item.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$SyncItemImpl _$$SyncItemImplFromJson(Map<String, dynamic> json) =>
|
||||
_$SyncItemImpl(
|
||||
id: json['id'] as String,
|
||||
parentId: json['parentId'] as String?,
|
||||
userId: json['userId'] as String,
|
||||
path: json['path'] as String?,
|
||||
markedForDelete: json['markedForDelete'] as bool? ?? false,
|
||||
sortKey: (json['sortKey'] as num?)?.toInt(),
|
||||
fileSize: (json['fileSize'] as num?)?.toInt(),
|
||||
videoFileName: json['videoFileName'] as String?,
|
||||
introOutSkipModel: json['introOutSkipModel'] == null
|
||||
? null
|
||||
: IntroOutSkipModel.fromJson(
|
||||
json['introOutSkipModel'] as Map<String, dynamic>),
|
||||
fTrickPlayModel: json['fTrickPlayModel'] == null
|
||||
? null
|
||||
: TrickPlayModel.fromJson(
|
||||
json['fTrickPlayModel'] as Map<String, dynamic>),
|
||||
fImages: json['fImages'] == null
|
||||
? null
|
||||
: ImagesData.fromJson(json['fImages'] as String),
|
||||
fChapters: (json['fChapters'] as List<dynamic>?)
|
||||
?.map((e) => Chapter.fromJson(e as String))
|
||||
.toList() ??
|
||||
const [],
|
||||
subtitles: (json['subtitles'] as List<dynamic>?)
|
||||
?.map((e) => SubStreamModel.fromJson(e as String))
|
||||
.toList() ??
|
||||
const [],
|
||||
userData: _$JsonConverterFromJson<String, UserData>(
|
||||
json['userData'], const UserDataJsonSerializer().fromJson),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SyncItemImplToJson(_$SyncItemImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'parentId': instance.parentId,
|
||||
'userId': instance.userId,
|
||||
'path': instance.path,
|
||||
'markedForDelete': instance.markedForDelete,
|
||||
'sortKey': instance.sortKey,
|
||||
'fileSize': instance.fileSize,
|
||||
'videoFileName': instance.videoFileName,
|
||||
'introOutSkipModel': instance.introOutSkipModel,
|
||||
'fTrickPlayModel': instance.fTrickPlayModel,
|
||||
'fImages': instance.fImages,
|
||||
'fChapters': instance.fChapters,
|
||||
'subtitles': instance.subtitles,
|
||||
'userData': _$JsonConverterToJson<String, UserData>(
|
||||
instance.userData, const UserDataJsonSerializer().toJson),
|
||||
};
|
||||
|
||||
Value? _$JsonConverterFromJson<Json, Value>(
|
||||
Object? json,
|
||||
Value? Function(Json json) fromJson,
|
||||
) =>
|
||||
json == null ? null : fromJson(json as Json);
|
||||
|
||||
Json? _$JsonConverterToJson<Json, Value>(
|
||||
Value? value,
|
||||
Json? Function(Value value) toJson,
|
||||
) =>
|
||||
value == null ? null : toJson(value);
|
||||
16
lib/models/syncing/sync_settings_model.dart
Normal file
16
lib/models/syncing/sync_settings_model.dart
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// ignore_for_file: invalid_annotation_target
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
|
||||
part 'sync_settings_model.freezed.dart';
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
class SyncSettingsModel with _$SyncSettingsModel {
|
||||
const SyncSettingsModel._();
|
||||
|
||||
factory SyncSettingsModel({
|
||||
@Default([]) List<SyncedItem> items,
|
||||
}) = _SyncSettignsModel;
|
||||
}
|
||||
144
lib/models/syncing/sync_settings_model.freezed.dart
Normal file
144
lib/models/syncing/sync_settings_model.freezed.dart
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'sync_settings_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SyncSettingsModel {
|
||||
List<SyncedItem> get items => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$SyncSettingsModelCopyWith<SyncSettingsModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SyncSettingsModelCopyWith<$Res> {
|
||||
factory $SyncSettingsModelCopyWith(
|
||||
SyncSettingsModel value, $Res Function(SyncSettingsModel) then) =
|
||||
_$SyncSettingsModelCopyWithImpl<$Res, SyncSettingsModel>;
|
||||
@useResult
|
||||
$Res call({List<SyncedItem> items});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SyncSettingsModelCopyWithImpl<$Res, $Val extends SyncSettingsModel>
|
||||
implements $SyncSettingsModelCopyWith<$Res> {
|
||||
_$SyncSettingsModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? items = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
items: null == items
|
||||
? _value.items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<SyncedItem>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SyncSettignsModelImplCopyWith<$Res>
|
||||
implements $SyncSettingsModelCopyWith<$Res> {
|
||||
factory _$$SyncSettignsModelImplCopyWith(_$SyncSettignsModelImpl value,
|
||||
$Res Function(_$SyncSettignsModelImpl) then) =
|
||||
__$$SyncSettignsModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({List<SyncedItem> items});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SyncSettignsModelImplCopyWithImpl<$Res>
|
||||
extends _$SyncSettingsModelCopyWithImpl<$Res, _$SyncSettignsModelImpl>
|
||||
implements _$$SyncSettignsModelImplCopyWith<$Res> {
|
||||
__$$SyncSettignsModelImplCopyWithImpl(_$SyncSettignsModelImpl _value,
|
||||
$Res Function(_$SyncSettignsModelImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? items = null,
|
||||
}) {
|
||||
return _then(_$SyncSettignsModelImpl(
|
||||
items: null == items
|
||||
? _value._items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<SyncedItem>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$SyncSettignsModelImpl extends _SyncSettignsModel {
|
||||
_$SyncSettignsModelImpl({final List<SyncedItem> items = const []})
|
||||
: _items = items,
|
||||
super._();
|
||||
|
||||
final List<SyncedItem> _items;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<SyncedItem> get items {
|
||||
if (_items is EqualUnmodifiableListView) return _items;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_items);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SyncSettingsModel(items: $items)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SyncSettignsModelImpl &&
|
||||
const DeepCollectionEquality().equals(other._items, _items));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, const DeepCollectionEquality().hash(_items));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SyncSettignsModelImplCopyWith<_$SyncSettignsModelImpl> get copyWith =>
|
||||
__$$SyncSettignsModelImplCopyWithImpl<_$SyncSettignsModelImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _SyncSettignsModel extends SyncSettingsModel {
|
||||
factory _SyncSettignsModel({final List<SyncedItem> items}) =
|
||||
_$SyncSettignsModelImpl;
|
||||
_SyncSettignsModel._() : super._();
|
||||
|
||||
@override
|
||||
List<SyncedItem> get items;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$SyncSettignsModelImplCopyWith<_$SyncSettignsModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
177
lib/models/trickplayer_model.dart
Normal file
177
lib/models/trickplayer_model.dart
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
|
||||
class Bif {
|
||||
final String version;
|
||||
final List<int> widthResolutions;
|
||||
Bif({
|
||||
required this.version,
|
||||
required this.widthResolutions,
|
||||
});
|
||||
|
||||
Bif copyWith({
|
||||
String? version,
|
||||
List<int>? widthResolutions,
|
||||
}) {
|
||||
return Bif(
|
||||
version: version ?? this.version,
|
||||
widthResolutions: widthResolutions ?? this.widthResolutions,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'Version': version,
|
||||
'WidthResolutions': widthResolutions,
|
||||
};
|
||||
}
|
||||
|
||||
factory Bif.fromMap(Map<String, dynamic> map) {
|
||||
return Bif(
|
||||
version: (map['Version'] ?? '') as String,
|
||||
widthResolutions: List<int>.from((map['WidthResolutions'] ?? const <int>[]) as List<dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory Bif.fromJson(String source) => Bif.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||
|
||||
@override
|
||||
String toString() => 'Bif(version: $version, widthResolutions: $widthResolutions)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant Bif other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.version == version && listEquals(other.widthResolutions, widthResolutions);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => version.hashCode ^ widthResolutions.hashCode;
|
||||
}
|
||||
|
||||
class BifUtil {
|
||||
static List<int> bifMagicNumbers = [0x89, 0x42, 0x49, 0x46, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||
static const int supportedBifVersion = 0;
|
||||
|
||||
static Future<BifData?> trickPlayDecode(Uint8List array, int width) async {
|
||||
Indexed8Array data = Indexed8Array(array);
|
||||
|
||||
for (int b in bifMagicNumbers) {
|
||||
if (data.read() != b) {
|
||||
print('Attempted to read invalid bif file.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int bifVersion = data.readInt32();
|
||||
if (bifVersion != supportedBifVersion) {
|
||||
print('Client only supports BIF v$supportedBifVersion but file is v$bifVersion');
|
||||
return null;
|
||||
}
|
||||
|
||||
print('BIF version: $bifVersion');
|
||||
|
||||
int bifImgCount = data.readInt32();
|
||||
if (bifImgCount <= 0) {
|
||||
print('BIF file contains no images.');
|
||||
return null;
|
||||
}
|
||||
|
||||
print('BIF image count: $bifImgCount');
|
||||
|
||||
int timestampMultiplier = data.readInt32();
|
||||
if (timestampMultiplier == 0) timestampMultiplier = 1000;
|
||||
|
||||
data.addPosition(44); // Reserved
|
||||
|
||||
List<BifIndexEntry> bifIndex = [];
|
||||
for (int i = 0; i < bifImgCount; i++) {
|
||||
bifIndex.add(BifIndexEntry(data.readInt32(), data.readInt32()));
|
||||
}
|
||||
|
||||
List<Chapter> bifImages = [];
|
||||
for (int i = 0; i < bifIndex.length; i++) {
|
||||
BifIndexEntry indexEntry = bifIndex[i];
|
||||
int timestamp = indexEntry.timestamp;
|
||||
int offset = indexEntry.offset;
|
||||
int nextOffset = i + 1 < bifIndex.length ? bifIndex[i + 1].offset : data.limit();
|
||||
|
||||
Uint8List imageBytes = Uint8List.sublistView(Uint8List.view(data.array.buffer, offset, nextOffset - offset));
|
||||
|
||||
bifImages.add(
|
||||
Chapter(
|
||||
name: "",
|
||||
imageUrl: "",
|
||||
imageData: imageBytes,
|
||||
startPosition: Duration(milliseconds: timestamp * timestampMultiplier),
|
||||
),
|
||||
);
|
||||
}
|
||||
return BifData(bifVersion, bifImgCount, bifImages, width);
|
||||
}
|
||||
}
|
||||
|
||||
class Indexed8Array {
|
||||
Uint8List array;
|
||||
int readIndex;
|
||||
|
||||
Indexed8Array(Uint8List byteArray)
|
||||
: array = byteArray,
|
||||
readIndex = 0;
|
||||
|
||||
int read() {
|
||||
return array[readIndex++];
|
||||
}
|
||||
|
||||
void addPosition(int amount) {
|
||||
readIndex += amount;
|
||||
}
|
||||
|
||||
int readInt32() {
|
||||
int b1 = read() & 0xFF;
|
||||
int b2 = read() & 0xFF;
|
||||
int b3 = read() & 0xFF;
|
||||
int b4 = read() & 0xFF;
|
||||
|
||||
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
|
||||
}
|
||||
|
||||
Uint8List getByteArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
int limit() {
|
||||
return array.length;
|
||||
}
|
||||
|
||||
Endian order() {
|
||||
return Endian.big;
|
||||
}
|
||||
}
|
||||
|
||||
class BifIndexEntry {
|
||||
int timestamp;
|
||||
int offset;
|
||||
|
||||
BifIndexEntry(this.timestamp, this.offset);
|
||||
}
|
||||
|
||||
class BifData {
|
||||
int bifVersion;
|
||||
int bifImgCount;
|
||||
List<Chapter> chapters;
|
||||
int width;
|
||||
|
||||
BifData(
|
||||
this.bifVersion,
|
||||
this.bifImgCount,
|
||||
this.chapters,
|
||||
this.width,
|
||||
);
|
||||
}
|
||||
208
lib/models/video_stream_model.dart
Normal file
208
lib/models/video_stream_model.dart
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ficonsax/ficonsax.dart';
|
||||
import 'package:fladder/models/items/item_stream_model.dart';
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/intro_skip_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
|
||||
enum PlaybackType {
|
||||
directStream,
|
||||
offline,
|
||||
transcode;
|
||||
|
||||
IconData get icon => switch (this) {
|
||||
PlaybackType.offline => IconsaxOutline.cloud,
|
||||
PlaybackType.directStream => IconsaxOutline.arrow_right_1,
|
||||
PlaybackType.transcode => IconsaxOutline.convert,
|
||||
};
|
||||
|
||||
String get name {
|
||||
switch (this) {
|
||||
case PlaybackType.directStream:
|
||||
return "Direct";
|
||||
case PlaybackType.offline:
|
||||
return "Offline";
|
||||
case PlaybackType.transcode:
|
||||
return "Transcoding";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayback {
|
||||
final List<ItemStreamModel> queue;
|
||||
final ItemStreamModel? currentItem;
|
||||
final SyncedItem? currentSyncedItem;
|
||||
final VideoStream? currentStream;
|
||||
VideoPlayback({
|
||||
required this.queue,
|
||||
this.currentItem,
|
||||
this.currentSyncedItem,
|
||||
this.currentStream,
|
||||
});
|
||||
|
||||
List<SubStreamModel> get subStreams => currentStream?.mediaStreamsModel?.subStreams ?? [];
|
||||
List<AudioStreamModel> get audioStreams => currentStream?.mediaStreamsModel?.audioStreams ?? [];
|
||||
|
||||
ItemStreamModel? get previousVideo {
|
||||
final int currentIndex = queue.indexWhere((element) => element.id == currentItem?.id);
|
||||
if (currentIndex > 0) {
|
||||
return queue[currentIndex - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ItemStreamModel? get nextVideo {
|
||||
final int currentIndex = queue.indexWhere((element) => element.id == currentItem?.id);
|
||||
if ((currentIndex + 1) < queue.length) {
|
||||
return queue[currentIndex + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
VideoPlayback copyWith({
|
||||
List<ItemStreamModel>? queue,
|
||||
ItemStreamModel? currentItem,
|
||||
SyncedItem? currentSyncedItem,
|
||||
VideoStream? currentStream,
|
||||
Map<AudioStreamModel, AudioTrack>? audioMappings,
|
||||
Map<SubStreamModel, SubtitleTrack>? subMappings,
|
||||
}) {
|
||||
return VideoPlayback(
|
||||
queue: queue ?? this.queue,
|
||||
currentItem: currentItem ?? this.currentItem,
|
||||
currentSyncedItem: currentSyncedItem ?? this.currentSyncedItem,
|
||||
currentStream: currentStream ?? this.currentStream,
|
||||
);
|
||||
}
|
||||
|
||||
VideoPlayback clear() {
|
||||
return VideoPlayback(
|
||||
queue: queue,
|
||||
currentItem: null,
|
||||
currentStream: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoStream {
|
||||
final String id;
|
||||
final Duration? currentPosition;
|
||||
final PlaybackType playbackType;
|
||||
final String playbackUrl;
|
||||
final String playSessionId;
|
||||
final List<Chapter>? chapters;
|
||||
final List<Chapter>? trickPlay;
|
||||
final IntroSkipModel? introSkipModel;
|
||||
final int? audioStreamIndex;
|
||||
final int? subtitleStreamIndex;
|
||||
final MediaStreamsModel? mediaStreamsModel;
|
||||
|
||||
AudioStreamModel? get currentAudioStream {
|
||||
if (audioStreamIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
return mediaStreamsModel?.audioStreams.firstWhereOrNull(
|
||||
(element) => element.index == (audioStreamIndex ?? mediaStreamsModel?.currentAudioStream ?? 0));
|
||||
}
|
||||
|
||||
SubStreamModel? get currentSubStream {
|
||||
if (subtitleStreamIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
return mediaStreamsModel?.subStreams.firstWhereOrNull(
|
||||
(element) => element.index == (subtitleStreamIndex ?? mediaStreamsModel?.currentSubStream ?? 0));
|
||||
}
|
||||
|
||||
VideoStream({
|
||||
required this.id,
|
||||
this.currentPosition,
|
||||
required this.playbackType,
|
||||
required this.playbackUrl,
|
||||
required this.playSessionId,
|
||||
this.chapters,
|
||||
this.trickPlay,
|
||||
this.introSkipModel,
|
||||
this.audioStreamIndex,
|
||||
this.subtitleStreamIndex,
|
||||
this.mediaStreamsModel,
|
||||
});
|
||||
|
||||
VideoStream copyWith({
|
||||
String? id,
|
||||
Duration? currentPosition,
|
||||
PlaybackType? playbackType,
|
||||
String? playbackUrl,
|
||||
String? playSessionId,
|
||||
List<Chapter>? chapters,
|
||||
List<Chapter>? trickPlay,
|
||||
IntroSkipModel? introSkipModel,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex,
|
||||
MediaStreamsModel? mediaStreamsModel,
|
||||
}) {
|
||||
return VideoStream(
|
||||
id: id ?? this.id,
|
||||
currentPosition: currentPosition ?? this.currentPosition,
|
||||
playbackType: playbackType ?? this.playbackType,
|
||||
playbackUrl: playbackUrl ?? this.playbackUrl,
|
||||
playSessionId: playSessionId ?? this.playSessionId,
|
||||
chapters: chapters ?? this.chapters,
|
||||
trickPlay: trickPlay ?? this.trickPlay,
|
||||
introSkipModel: introSkipModel ?? this.introSkipModel,
|
||||
audioStreamIndex: audioStreamIndex ?? this.audioStreamIndex,
|
||||
subtitleStreamIndex: subtitleStreamIndex ?? this.subtitleStreamIndex,
|
||||
mediaStreamsModel: mediaStreamsModel ?? this.mediaStreamsModel,
|
||||
);
|
||||
}
|
||||
|
||||
static VideoStream? fromPlayBackInfo(PlaybackInfoResponse info, Ref ref) {
|
||||
final mediaSource = info.mediaSources?.first;
|
||||
var playType = PlaybackType.directStream;
|
||||
|
||||
String? playbackUrl;
|
||||
|
||||
if (mediaSource == null) return null;
|
||||
|
||||
if (mediaSource.supportsDirectStream ?? false) {
|
||||
final Map<String, String?> directOptions = {
|
||||
'Static': 'true',
|
||||
'mediaSourceId': mediaSource.id,
|
||||
'api_key': ref.read(userProvider)?.credentials.token,
|
||||
};
|
||||
|
||||
if (mediaSource.eTag != null) {
|
||||
directOptions['Tag'] = mediaSource.eTag;
|
||||
}
|
||||
|
||||
if (mediaSource.liveStreamId != null) {
|
||||
directOptions['LiveStreamId'] = mediaSource.liveStreamId;
|
||||
}
|
||||
|
||||
final params = Uri(queryParameters: directOptions).query;
|
||||
|
||||
playbackUrl = '${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params';
|
||||
} else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) {
|
||||
playbackUrl = "${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}";
|
||||
playType = PlaybackType.transcode;
|
||||
}
|
||||
|
||||
if (playbackUrl == null) return null;
|
||||
|
||||
return VideoStream(
|
||||
id: info.mediaSources?.first.id ?? "",
|
||||
playbackUrl: playbackUrl,
|
||||
playbackType: playType,
|
||||
playSessionId: info.playSessionId ?? "",
|
||||
mediaStreamsModel: MediaStreamsModel.fromMediaStreamsList(
|
||||
info.mediaSources?.firstOrNull, info.mediaSources?.firstOrNull?.mediaStreams ?? [], ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
97
lib/models/view_model.dart
Normal file
97
lib/models/view_model.dart
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto;
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
|
||||
class ViewModel {
|
||||
final String name;
|
||||
final String id;
|
||||
final String serverId;
|
||||
final DateTime dateCreated;
|
||||
final bool canDelete;
|
||||
final bool canDownload;
|
||||
final String parentId;
|
||||
final CollectionType collectionType;
|
||||
final dto.PlayAccess playAccess;
|
||||
final List<ItemBaseModel> recentlyAdded;
|
||||
final int childCount;
|
||||
ViewModel({
|
||||
required this.name,
|
||||
required this.id,
|
||||
required this.serverId,
|
||||
required this.dateCreated,
|
||||
required this.canDelete,
|
||||
required this.canDownload,
|
||||
required this.parentId,
|
||||
required this.collectionType,
|
||||
required this.playAccess,
|
||||
required this.recentlyAdded,
|
||||
required this.childCount,
|
||||
});
|
||||
|
||||
ViewModel copyWith({
|
||||
String? name,
|
||||
String? id,
|
||||
String? serverId,
|
||||
DateTime? dateCreated,
|
||||
bool? canDelete,
|
||||
bool? canDownload,
|
||||
String? parentId,
|
||||
CollectionType? collectionType,
|
||||
dto.PlayAccess? playAccess,
|
||||
List<ItemBaseModel>? recentlyAdded,
|
||||
int? childCount,
|
||||
}) {
|
||||
return ViewModel(
|
||||
name: name ?? this.name,
|
||||
id: id ?? this.id,
|
||||
serverId: serverId ?? this.serverId,
|
||||
dateCreated: dateCreated ?? this.dateCreated,
|
||||
canDelete: canDelete ?? this.canDelete,
|
||||
canDownload: canDownload ?? this.canDownload,
|
||||
parentId: parentId ?? this.parentId,
|
||||
collectionType: collectionType ?? this.collectionType,
|
||||
playAccess: playAccess ?? this.playAccess,
|
||||
recentlyAdded: recentlyAdded ?? this.recentlyAdded,
|
||||
childCount: childCount ?? this.childCount,
|
||||
);
|
||||
}
|
||||
|
||||
factory ViewModel.fromBodyDto(dto.BaseItemDto item, Ref ref) {
|
||||
return ViewModel(
|
||||
name: item.name ?? "",
|
||||
id: item.id ?? "",
|
||||
serverId: item.serverId ?? "",
|
||||
dateCreated: item.dateCreated ?? DateTime.now(),
|
||||
canDelete: item.canDelete ?? false,
|
||||
canDownload: item.canDownload ?? false,
|
||||
parentId: item.parentId ?? "",
|
||||
recentlyAdded: [],
|
||||
collectionType: CollectionType.values
|
||||
.firstWhereOrNull((element) => element.name.toLowerCase() == item.collectionType?.value?.toLowerCase()) ??
|
||||
CollectionType.movies,
|
||||
playAccess: item.playAccess ?? PlayAccess.none,
|
||||
childCount: item.childCount ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant ViewModel other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other.id == id && other.serverId == serverId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return id.hashCode ^ serverId.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ViewModel(name: $name, id: $id, serverId: $serverId, dateCreated: $dateCreated, canDelete: $canDelete, canDownload: $canDownload, parentId: $parentId, collectionType: $collectionType, playAccess: $playAccess, recentlyAdded: $recentlyAdded, childCount: $childCount)';
|
||||
}
|
||||
}
|
||||
24
lib/models/views_model.dart
Normal file
24
lib/models/views_model.dart
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:fladder/models/view_model.dart';
|
||||
|
||||
class ViewsModel {
|
||||
final bool loading;
|
||||
final List<ViewModel> views;
|
||||
final List<ViewModel> dashboardViews;
|
||||
ViewsModel({
|
||||
this.loading = false,
|
||||
this.views = const [],
|
||||
this.dashboardViews = const [],
|
||||
});
|
||||
|
||||
ViewsModel copyWith({
|
||||
bool? loading,
|
||||
List<ViewModel>? views,
|
||||
List<ViewModel>? dashboardViews,
|
||||
}) {
|
||||
return ViewsModel(
|
||||
loading: loading ?? this.loading,
|
||||
views: views ?? this.views,
|
||||
dashboardViews: dashboardViews ?? this.dashboardViews);
|
||||
}
|
||||
}
|
||||
35
lib/profiles/default_profile.dart
Normal file
35
lib/profiles/default_profile.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/profiles/web_profile.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
const DeviceProfile defaultProfile = kIsWeb
|
||||
? webProfile
|
||||
: DeviceProfile(
|
||||
maxStreamingBitrate: 120000000,
|
||||
maxStaticBitrate: 120000000,
|
||||
musicStreamingTranscodingBitrate: 384000,
|
||||
directPlayProfiles: [
|
||||
DirectPlayProfile(
|
||||
type: DlnaProfileType.video,
|
||||
),
|
||||
DirectPlayProfile(
|
||||
type: DlnaProfileType.audio,
|
||||
)
|
||||
],
|
||||
transcodingProfiles: [
|
||||
TranscodingProfile(
|
||||
audioCodec: 'aac,mp3,mp2',
|
||||
container: 'ts',
|
||||
maxAudioChannels: '2',
|
||||
protocol: MediaStreamProtocol.hls,
|
||||
type: DlnaProfileType.video,
|
||||
videoCodec: 'h264',
|
||||
),
|
||||
],
|
||||
containerProfiles: [],
|
||||
subtitleProfiles: [
|
||||
SubtitleProfile(format: 'vtt', method: SubtitleDeliveryMethod.$external),
|
||||
SubtitleProfile(format: 'ass', method: SubtitleDeliveryMethod.$external),
|
||||
SubtitleProfile(format: 'ssa', method: SubtitleDeliveryMethod.$external),
|
||||
],
|
||||
);
|
||||
269
lib/profiles/web_profile.dart
Normal file
269
lib/profiles/web_profile.dart
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
|
||||
const DeviceProfile webProfile = DeviceProfile(
|
||||
maxStreamingBitrate: 120000000,
|
||||
maxStaticBitrate: 100000000,
|
||||
musicStreamingTranscodingBitrate: 384000,
|
||||
directPlayProfiles: [
|
||||
DirectPlayProfile(
|
||||
container: 'mkv,webm',
|
||||
type: DlnaProfileType.video,
|
||||
videoCodec: 'h264,hevc,vp8,vp9,av1',
|
||||
audioCodec: 'vorbis,opus,aac,eac3',
|
||||
),
|
||||
DirectPlayProfile(
|
||||
container: 'mp4,m4v',
|
||||
type: DlnaProfileType.video,
|
||||
videoCodec: 'h264,hevc,vp8,vp9,av1',
|
||||
audioCodec: 'aac,mp3,mp2,opus,flac,vorbis',
|
||||
),
|
||||
DirectPlayProfile(
|
||||
container: 'mov',
|
||||
type: DlnaProfileType.video,
|
||||
videoCodec: 'h264',
|
||||
audioCodec: 'aac,mp3,mp2,opus,flac,vorbis',
|
||||
),
|
||||
DirectPlayProfile(container: 'opus', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'webm', audioCodec: 'opus', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'mp3', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'aac', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'm4a', audioCodec: 'aac', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'm4b', audioCodec: 'aac', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'flac', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'webma', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'webm', audioCodec: 'webma', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'wav', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(container: 'ogg', type: DlnaProfileType.audio),
|
||||
DirectPlayProfile(
|
||||
container: 'hls',
|
||||
type: DlnaProfileType.video,
|
||||
videoCodec: 'h264',
|
||||
audioCodec: 'aac,mp3,mp2',
|
||||
),
|
||||
],
|
||||
transcodingProfiles: [
|
||||
TranscodingProfile(
|
||||
container: 'ts',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'aac',
|
||||
context: EncodingContext.streaming,
|
||||
protocol: MediaStreamProtocol.hls,
|
||||
maxAudioChannels: '2',
|
||||
minSegments: 1,
|
||||
breakOnNonKeyFrames: true,
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'aac',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'aac',
|
||||
context: EncodingContext.streaming,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'mp3',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'mp3',
|
||||
context: EncodingContext.streaming,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'opus',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'opus',
|
||||
context: EncodingContext.streaming,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'wav',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'wav',
|
||||
context: EncodingContext.streaming,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'opus',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'opus',
|
||||
context: EncodingContext.$static,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'mp3',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'mp3',
|
||||
context: EncodingContext.$static,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'aac',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'aac',
|
||||
context: EncodingContext.$static,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'wav',
|
||||
type: DlnaProfileType.audio,
|
||||
audioCodec: 'wav',
|
||||
context: EncodingContext.$static,
|
||||
protocol: MediaStreamProtocol.http,
|
||||
maxAudioChannels: '2',
|
||||
),
|
||||
TranscodingProfile(
|
||||
container: 'ts',
|
||||
type: DlnaProfileType.video,
|
||||
audioCodec: 'aac,mp3,mp2',
|
||||
videoCodec: 'h264',
|
||||
context: EncodingContext.streaming,
|
||||
protocol: MediaStreamProtocol.hls,
|
||||
maxAudioChannels: '2',
|
||||
minSegments: 1,
|
||||
breakOnNonKeyFrames: true,
|
||||
),
|
||||
],
|
||||
containerProfiles: [],
|
||||
codecProfiles: [
|
||||
CodecProfile(
|
||||
type: CodecType.videoaudio,
|
||||
codec: 'aac',
|
||||
conditions: [
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equals,
|
||||
property: ProfileConditionValue.issecondaryaudio,
|
||||
$Value: 'false',
|
||||
isRequired: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
CodecProfile(
|
||||
type: CodecType.videoaudio,
|
||||
conditions: [
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equals,
|
||||
property: ProfileConditionValue.issecondaryaudio,
|
||||
$Value: 'false',
|
||||
isRequired: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
CodecProfile(
|
||||
type: CodecType.video,
|
||||
codec: 'h264',
|
||||
conditions: [
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.notequals,
|
||||
property: ProfileConditionValue.isanamorphic,
|
||||
$Value: 'true',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equalsany,
|
||||
property: ProfileConditionValue.videorangetype,
|
||||
$Value: 'SDR',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.lessthanequal,
|
||||
property: ProfileConditionValue.videolevel,
|
||||
$Value: '52',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.notequals,
|
||||
property: ProfileConditionValue.isinterlaced,
|
||||
$Value: 'true',
|
||||
isRequired: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
CodecProfile(
|
||||
type: CodecType.video,
|
||||
codec: 'hevc',
|
||||
conditions: [
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.notequals,
|
||||
property: ProfileConditionValue.isanamorphic,
|
||||
$Value: 'true',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equalsany,
|
||||
property: ProfileConditionValue.videoprofile,
|
||||
$Value: 'main',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equalsany,
|
||||
property: ProfileConditionValue.videorangetype,
|
||||
$Value: 'SDR|HDR10|HLG',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.lessthanequal,
|
||||
property: ProfileConditionValue.videolevel,
|
||||
$Value: '120',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.notequals,
|
||||
property: ProfileConditionValue.isinterlaced,
|
||||
$Value: 'true',
|
||||
isRequired: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
CodecProfile(
|
||||
type: CodecType.video,
|
||||
codec: 'vp9',
|
||||
conditions: [
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equalsany,
|
||||
property: ProfileConditionValue.videorangetype,
|
||||
$Value: 'SDR|HDR10|HLG',
|
||||
isRequired: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
CodecProfile(
|
||||
type: CodecType.video,
|
||||
codec: 'av1',
|
||||
conditions: [
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.notequals,
|
||||
property: ProfileConditionValue.isanamorphic,
|
||||
$Value: 'true',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equalsany,
|
||||
property: ProfileConditionValue.videoprofile,
|
||||
$Value: 'main',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.equalsany,
|
||||
property: ProfileConditionValue.videorangetype,
|
||||
$Value: 'SDR|HDR10|HLG',
|
||||
isRequired: false,
|
||||
),
|
||||
ProfileCondition(
|
||||
condition: ProfileConditionType.lessthanequal,
|
||||
property: ProfileConditionValue.videolevel,
|
||||
$Value: '19',
|
||||
isRequired: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
subtitleProfiles: [
|
||||
SubtitleProfile(format: 'vtt', method: SubtitleDeliveryMethod.embed),
|
||||
SubtitleProfile(format: 'srt', method: SubtitleDeliveryMethod.embed),
|
||||
],
|
||||
);
|
||||
73
lib/providers/api_provider.dart
Normal file
73
lib/providers/api_provider.dart
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/providers/auth_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'api_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class JellyApi extends _$JellyApi {
|
||||
@override
|
||||
JellyService build() {
|
||||
return JellyService(
|
||||
ref,
|
||||
JellyfinOpenApi.create(
|
||||
interceptors: [
|
||||
JellyRequest(ref),
|
||||
JellyResponse(ref),
|
||||
HttpLoggingInterceptor(level: Level.basic),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class JellyRequest implements RequestInterceptor {
|
||||
JellyRequest(this.ref);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
@override
|
||||
FutureOr<Request> onRequest(Request request) async {
|
||||
if (request.method == HttpMethod.Post) {
|
||||
chopperLogger.info('Performed a POST request');
|
||||
}
|
||||
|
||||
final serverUrl = Uri.parse(ref.read(userProvider)?.server ?? ref.read(authProvider).tempCredentials.server);
|
||||
|
||||
//Use current logged in user otherwise use the authprovider
|
||||
var loginModel = ref.read(userProvider)?.credentials ?? ref.read(authProvider).tempCredentials;
|
||||
var headers = loginModel.header(ref);
|
||||
|
||||
return request.copyWith(
|
||||
baseUri: serverUrl,
|
||||
headers: request.headers..addAll(headers),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class JellyResponse implements ResponseInterceptor {
|
||||
JellyResponse(this.ref);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
@override
|
||||
FutureOr<Response<dynamic>> onResponse(Response<dynamic> response) {
|
||||
if (!response.isSuccessful) {
|
||||
log('x- ${response.base.statusCode} - ${response.base.reasonPhrase} - ${response.error} - ${response.base.request?.method} ${response.base.request?.url.toString()}');
|
||||
}
|
||||
if (response.statusCode == 404) {
|
||||
chopperLogger.severe('404 NOT FOUND');
|
||||
}
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
// ref.read(sharedUtilityProvider).removeAccount(ref.read(userProvider));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
25
lib/providers/api_provider.g.dart
Normal file
25
lib/providers/api_provider.g.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'api_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$jellyApiHash() => r'c0cdc4127e7191523b1356e71c54c93f99020c1e';
|
||||
|
||||
/// See also [JellyApi].
|
||||
@ProviderFor(JellyApi)
|
||||
final jellyApiProvider =
|
||||
AutoDisposeNotifierProvider<JellyApi, JellyService>.internal(
|
||||
JellyApi.new,
|
||||
name: r'jellyApiProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$jellyApiHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$JellyApi = AutoDisposeNotifier<JellyService>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
123
lib/providers/auth_provider.dart
Normal file
123
lib/providers/auth_provider.dart
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/models/account_model.dart';
|
||||
import 'package:fladder/models/credentials_model.dart';
|
||||
import 'package:fladder/models/login_screen_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/dashboard_provider.dart';
|
||||
import 'package:fladder/providers/favourites_provider.dart';
|
||||
import 'package:fladder/providers/image_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final authProvider = StateNotifierProvider<AuthNotifier, LoginScreenModel>((ref) {
|
||||
return AuthNotifier(ref);
|
||||
});
|
||||
|
||||
class AuthNotifier extends StateNotifier<LoginScreenModel> {
|
||||
AuthNotifier(this.ref)
|
||||
: super(
|
||||
LoginScreenModel(
|
||||
accounts: [],
|
||||
tempCredentials: CredentialsModel.createNewCredentials(),
|
||||
loading: false,
|
||||
),
|
||||
);
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<Response<List<AccountModel>>?> getPublicUsers() async {
|
||||
try {
|
||||
var response = await api.usersPublicGet(state.tempCredentials);
|
||||
if (response.isSuccessful && response.body != null) {
|
||||
var models = response.body ?? [];
|
||||
|
||||
return response.copyWith(body: models.toList());
|
||||
}
|
||||
return response.copyWith(body: []);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<AccountModel>?> authenticateByName(String userName, String password) async {
|
||||
state = state.copyWith(loading: true);
|
||||
clearAllProviders();
|
||||
var response = await api.usersAuthenticateByNamePost(userName: userName, password: password);
|
||||
var serverResponse = await api.systemInfoPublicGet();
|
||||
CredentialsModel credentials = state.tempCredentials;
|
||||
if (response.isSuccessful && (response.body?.accessToken?.isNotEmpty ?? false)) {
|
||||
credentials = credentials.copyWith(
|
||||
token: response.body?.accessToken ?? "",
|
||||
serverId: response.body?.serverId,
|
||||
serverName: serverResponse.body?.serverName ?? "",
|
||||
);
|
||||
var imageUrl = ref.read(imageUtilityProvider).getUserImageUrl(response.body?.user?.id ?? "");
|
||||
AccountModel newUser = AccountModel(
|
||||
name: response.body?.user?.name ?? "",
|
||||
id: response.body?.user?.id ?? "",
|
||||
avatar: imageUrl,
|
||||
credentials: credentials,
|
||||
lastUsed: DateTime.now(),
|
||||
);
|
||||
ref.read(sharedUtilityProvider).addAccount(newUser);
|
||||
ref.read(userProvider.notifier).userState = newUser;
|
||||
state = state.copyWith(loading: false);
|
||||
return Response(response.base, newUser);
|
||||
}
|
||||
state = state.copyWith(loading: false);
|
||||
return Response(response.base, null);
|
||||
}
|
||||
|
||||
Future<Response?> logOutUser() async {
|
||||
if (ref.read(userProvider) != null) {
|
||||
final response = await api.sessionsLogoutPost();
|
||||
if (response.isSuccessful) {
|
||||
log('Logged out');
|
||||
}
|
||||
state = state.copyWith(tempCredentials: CredentialsModel.createNewCredentials());
|
||||
await ref.read(sharedUtilityProvider).removeAccount(ref.read(userProvider));
|
||||
return response;
|
||||
}
|
||||
clearAllProviders();
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> switchUser() async {
|
||||
clearAllProviders();
|
||||
}
|
||||
|
||||
void clearAllProviders() {
|
||||
ref.read(dashboardProvider.notifier).clear();
|
||||
ref.read(viewsProvider.notifier).clear();
|
||||
ref.read(favouritesProvider.notifier).clear();
|
||||
ref.read(userProvider.notifier).clear();
|
||||
ref.read(syncProvider.notifier).setup();
|
||||
}
|
||||
|
||||
void setServer(String server) {
|
||||
state = state.copyWith(
|
||||
tempCredentials: state.tempCredentials.copyWith(server: server),
|
||||
);
|
||||
}
|
||||
|
||||
List<AccountModel> getSavedAccounts() {
|
||||
state = state.copyWith(accounts: ref.read(sharedUtilityProvider).getAccounts());
|
||||
return state.accounts;
|
||||
}
|
||||
|
||||
void reOrderUsers(int oldIndex, int newIndex) {
|
||||
final accounts = state.accounts;
|
||||
final original = accounts.elementAt(oldIndex);
|
||||
accounts.removeAt(oldIndex);
|
||||
accounts.insert(newIndex, original);
|
||||
ref.read(sharedUtilityProvider).saveAccounts(accounts);
|
||||
}
|
||||
}
|
||||
188
lib/providers/book_viewer_provider.dart
Normal file
188
lib/providers/book_viewer_provider.dart
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/user_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/providers/api_provider.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class BookViewerModel {
|
||||
final BookModel? book;
|
||||
final bool loading;
|
||||
final List<String> pages;
|
||||
final int currentPage;
|
||||
BookViewerModel({
|
||||
this.book,
|
||||
this.loading = false,
|
||||
this.pages = const [],
|
||||
this.currentPage = 0,
|
||||
});
|
||||
|
||||
int get clampedCurrentPage => currentPage.clamp(0, pages.length);
|
||||
|
||||
BookViewerModel copyWith({
|
||||
ValueGetter<BookModel?>? book,
|
||||
bool? loading,
|
||||
List<String>? pages,
|
||||
int? currentPage,
|
||||
}) {
|
||||
return BookViewerModel(
|
||||
book: book != null ? book.call() : this.book,
|
||||
loading: loading ?? this.loading,
|
||||
pages: pages ?? this.pages,
|
||||
currentPage: currentPage ?? this.currentPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final bookViewerProvider = StateNotifierProvider<BookViewerNotifier, BookViewerModel>((ref) {
|
||||
return BookViewerNotifier(ref);
|
||||
});
|
||||
|
||||
class BookViewerNotifier extends StateNotifier<BookViewerModel> {
|
||||
BookViewerNotifier(this.ref) : super(BookViewerModel());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late Directory savedDirectory;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<List<String>?> fetchBook(BookModel? book) async {
|
||||
final oldState = state.copyWith();
|
||||
state = state.copyWith(loading: true, book: () => book, currentPage: 0);
|
||||
|
||||
//Stop and cleanup old state
|
||||
await _stopPlaybackOldState(oldState);
|
||||
|
||||
if (state.book == null) return null;
|
||||
try {
|
||||
final response = await api.itemsItemIdDownloadGet(itemId: state.book?.id);
|
||||
|
||||
final bookDirectory = state.book?.id;
|
||||
|
||||
String tempDir = (await getTemporaryDirectory()).path;
|
||||
savedDirectory = Directory('$tempDir/$bookDirectory');
|
||||
await savedDirectory.create();
|
||||
File bookFile = File('${savedDirectory.path}/archive.book');
|
||||
await bookFile.writeAsBytes(response.bodyBytes);
|
||||
|
||||
final inputStream = InputFileStream(bookFile.path);
|
||||
final archive = ZipDecoder().decodeBuffer(inputStream);
|
||||
|
||||
final List<String> imagesPath = [];
|
||||
for (var file in archive.files) {
|
||||
//filter out files with image extension
|
||||
if (file.isFile && _isImageFile(file.name)) {
|
||||
final path = '${savedDirectory.path}/Pages/${file.name}';
|
||||
final outputStream = OutputFileStream('${savedDirectory.path}/Pages/${file.name}');
|
||||
file.writeContent(outputStream);
|
||||
imagesPath.add(path);
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
state = state.copyWith(pages: imagesPath, loading: false);
|
||||
await inputStream.close();
|
||||
await bookFile.delete();
|
||||
return imagesPath;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
state = state.copyWith(loading: false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Simple file checker
|
||||
bool _isImageFile(String filePath) {
|
||||
final imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'tif', 'webp'];
|
||||
final fileExtension = filePath.toLowerCase().split('.').last;
|
||||
return imageExtensions.contains(fileExtension);
|
||||
}
|
||||
|
||||
Future<Response?> updatePlayback(int page) async {
|
||||
if (state.book == null) return null;
|
||||
if (page == state.currentPage) return null;
|
||||
state = state.copyWith(currentPage: page);
|
||||
return await api.sessionsPlayingStoppedPost(
|
||||
body: PlaybackStopInfo(
|
||||
itemId: state.book?.id,
|
||||
mediaSourceId: state.book?.id,
|
||||
positionTicks: state.clampedCurrentPage * 10000,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response?> _stopPlaybackOldState(BookViewerModel oldState) async {
|
||||
if (oldState.book == null) return null;
|
||||
|
||||
if (oldState.clampedCurrentPage < oldState.pages.length && oldState.pages.isNotEmpty) {
|
||||
await ref.read(userProvider.notifier).markAsPlayed(false, oldState.book?.id ?? "");
|
||||
}
|
||||
|
||||
final response = await api.sessionsPlayingStoppedPost(
|
||||
body: PlaybackStopInfo(
|
||||
itemId: oldState.book?.id,
|
||||
mediaSourceId: oldState.book?.id,
|
||||
positionTicks: oldState.clampedCurrentPage * 10000,
|
||||
));
|
||||
|
||||
if (oldState.clampedCurrentPage >= oldState.pages.length && oldState.pages.isNotEmpty) {
|
||||
await ref.read(userProvider.notifier).markAsPlayed(true, oldState.book?.id ?? "");
|
||||
}
|
||||
|
||||
await _cleanUp();
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response?> stopPlayback() async {
|
||||
if (state.book == null) return null;
|
||||
|
||||
if (state.clampedCurrentPage < state.pages.length && state.pages.isNotEmpty) {
|
||||
await ref.read(userProvider.notifier).markAsPlayed(false, state.book?.id ?? "");
|
||||
}
|
||||
|
||||
final response = await api.sessionsPlayingStoppedPost(
|
||||
body: PlaybackStopInfo(
|
||||
itemId: state.book?.id,
|
||||
mediaSourceId: state.book?.id,
|
||||
positionTicks: state.clampedCurrentPage * 10000,
|
||||
));
|
||||
|
||||
if (state.clampedCurrentPage >= state.pages.length && state.pages.isNotEmpty) {
|
||||
await ref.read(userProvider.notifier).markAsPlayed(true, state.book?.id ?? "");
|
||||
}
|
||||
|
||||
await _cleanUp();
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<void> _cleanUp() async {
|
||||
try {
|
||||
for (var i = 0; i < state.pages.length; i++) {
|
||||
final file = File(state.pages[i]);
|
||||
if (file.existsSync()) {
|
||||
await file.delete();
|
||||
}
|
||||
}
|
||||
final directoryExists = await savedDirectory.exists();
|
||||
if (directoryExists) {
|
||||
await savedDirectory.delete(recursive: true);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void setPage(double value) => state = state.copyWith(currentPage: value.toInt());
|
||||
|
||||
void setBook(BookModel book) => state = state.copyWith(
|
||||
book: () => book,
|
||||
);
|
||||
}
|
||||
100
lib/providers/collections_provider.dart
Normal file
100
lib/providers/collections_provider.dart
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import 'package:chopper/chopper.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/boxset_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/util/map_bool_helper.dart';
|
||||
|
||||
class _CollectionSetModel {
|
||||
final List<ItemBaseModel> items;
|
||||
final Map<BoxSetModel, bool?> collections;
|
||||
_CollectionSetModel({
|
||||
required this.items,
|
||||
required this.collections,
|
||||
});
|
||||
|
||||
_CollectionSetModel copyWith({
|
||||
List<ItemBaseModel>? items,
|
||||
Map<BoxSetModel, bool?>? collections,
|
||||
}) {
|
||||
return _CollectionSetModel(
|
||||
items: items ?? this.items,
|
||||
collections: collections ?? this.collections,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final collectionsProvider = StateNotifierProvider.autoDispose<BoxSetNotifier, _CollectionSetModel>((ref) {
|
||||
return BoxSetNotifier(ref);
|
||||
});
|
||||
|
||||
class BoxSetNotifier extends StateNotifier<_CollectionSetModel> {
|
||||
BoxSetNotifier(this.ref) : super(_CollectionSetModel(items: [], collections: {}));
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<void> setItems(List<ItemBaseModel> items) async {
|
||||
state = state.copyWith(items: items);
|
||||
return _init();
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
final collections = await api.usersUserIdItemsGet(
|
||||
recursive: true,
|
||||
includeItemTypes: [
|
||||
BaseItemKind.boxset,
|
||||
],
|
||||
);
|
||||
|
||||
final boxsets = collections.body?.items?.map((e) => BoxSetModel.fromBaseDto(e, ref)).toList();
|
||||
|
||||
if (state.items.length == 1 && (boxsets?.length ?? 0) < 25) {
|
||||
final List<Future<bool>> itemChecks = boxsets?.map((element) async {
|
||||
final itemList = await api.usersUserIdItemsGet(
|
||||
parentId: element.id,
|
||||
);
|
||||
final List<String?> items = (itemList.body?.items ?? []).map((e) => e.id).toList();
|
||||
return items.contains(state.items.firstOrNull?.id);
|
||||
}).toList() ??
|
||||
[];
|
||||
|
||||
final List<bool> results = await Future.wait(itemChecks);
|
||||
|
||||
final Map<BoxSetModel, bool?> boxSetContainsItemMap = Map.fromIterables(boxsets ?? [], results);
|
||||
|
||||
state = state.copyWith(collections: boxSetContainsItemMap);
|
||||
} else {
|
||||
final Map<BoxSetModel, bool?> boxSetContainsItemMap =
|
||||
Map.fromIterables(boxsets ?? [], List.generate(boxsets?.length ?? 0, (index) => null));
|
||||
state = state.copyWith(collections: boxSetContainsItemMap);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response> toggleCollection(
|
||||
{required BoxSetModel boxSet, required bool value, required ItemBaseModel item}) async {
|
||||
final Response response = value
|
||||
? await api.collectionsCollectionIdItemsPost(collectionId: boxSet.id, ids: [item.id])
|
||||
: await api.collectionsCollectionIdItemsDelete(collectionId: boxSet.id, ids: [item.id]);
|
||||
|
||||
if (response.isSuccessful) {
|
||||
state = state.copyWith(collections: state.collections.setKey(boxSet, response.isSuccessful ? value : !value));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Response> addToCollection({required BoxSetModel boxSet, required bool add}) async => add
|
||||
? await api.collectionsCollectionIdItemsPost(collectionId: boxSet.id, ids: state.items.map((e) => e.id).toList())
|
||||
: await api.collectionsCollectionIdItemsDelete(
|
||||
collectionId: boxSet.id, ids: state.items.map((e) => e.id).toList());
|
||||
|
||||
Future<void> addToNewCollection({required String name}) async {
|
||||
final result = await api.collectionsPost(name: name, ids: state.items.map((e) => e.id).toList());
|
||||
if (result.isSuccessful) {
|
||||
await _init();
|
||||
}
|
||||
}
|
||||
}
|
||||
111
lib/providers/dashboard_provider.dart
Normal file
111
lib/providers/dashboard_provider.dart
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/models/home_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:fladder/util/list_extensions.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final dashboardProvider = StateNotifierProvider<DashboardNotifier, HomeModel>((ref) {
|
||||
return DashboardNotifier(ref);
|
||||
});
|
||||
|
||||
class DashboardNotifier extends StateNotifier<HomeModel> {
|
||||
DashboardNotifier(this.ref) : super(HomeModel());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
late final JellyService api = ref.read(jellyApiProvider);
|
||||
|
||||
Future<void> fetchNextUpAndResume() async {
|
||||
if (state.loading) return;
|
||||
state = state.copyWith(loading: true);
|
||||
final viewTypes =
|
||||
ref.read(viewsProvider.select((value) => value.dashboardViews)).map((e) => e.collectionType).toSet().toList();
|
||||
|
||||
if (viewTypes.containsAny([CollectionType.movies, CollectionType.tvshows])) {
|
||||
final resumeVideoResponse = await api.usersUserIdItemsResumeGet(
|
||||
limit: 16,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
ItemFields.mediasources,
|
||||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
],
|
||||
mediaTypes: [MediaType.video],
|
||||
enableTotalRecordCount: false,
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
resumeVideo: resumeVideoResponse.body?.items?.map((e) => ItemBaseModel.fromBaseDto(e, ref)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewTypes.contains(CollectionType.music)) {
|
||||
final resumeAudioResponse = await api.usersUserIdItemsResumeGet(
|
||||
limit: 16,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
ItemFields.mediasources,
|
||||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
],
|
||||
mediaTypes: [MediaType.audio],
|
||||
enableTotalRecordCount: false,
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
resumeAudio: resumeAudioResponse.body?.items?.map((e) => ItemBaseModel.fromBaseDto(e, ref)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewTypes.contains(CollectionType.books)) {
|
||||
final resumeBookResponse = await api.usersUserIdItemsResumeGet(
|
||||
limit: 16,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
ItemFields.mediasources,
|
||||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
],
|
||||
mediaTypes: [MediaType.book],
|
||||
enableTotalRecordCount: false,
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
resumeBooks: resumeBookResponse.body?.items?.map((e) => ItemBaseModel.fromBaseDto(e, ref)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
final nextResponse = await api.showsNextUpGet(
|
||||
limit: 16,
|
||||
nextUpDateCutoff: DateTime.now()
|
||||
.subtract(ref.read(clientSettingsProvider.select((value) => value.nextUpDateCutoff ?? Duration(days: 28)))),
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.mediastreams,
|
||||
ItemFields.mediasources,
|
||||
ItemFields.candelete,
|
||||
ItemFields.candownload,
|
||||
],
|
||||
);
|
||||
|
||||
final next = nextResponse.body?.items
|
||||
?.map(
|
||||
(e) => ItemBaseModel.fromBaseDto(e, ref),
|
||||
)
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
state = state.copyWith(nextUp: next, loading: false);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
state = HomeModel();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue