diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c280e9ad7..feb550072 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2570,5 +2570,17 @@ "@groupedFoldersDescription": {}, "downloadTranscoded": "Download transcoded", "downloadOriginal": "Download original", - "today": "Today" + "today": "Today", + "dvPlayerSelectionTitle": "Dolby Vision Player", + "dvPlayerSelectionDesc": "Choose how to play Dolby Vision on Windows. Energy Player is required for native vision and high-quality audio passthrough (Atmos, 5.1).", + "dvPlayerAsk": "Always ask", + "dvPlayerDisabled": "Disabled (Internal player)", + "dvPlayerEnergyPlayer": "Energy Player (External)", + "dvPlayerDialogTitle": "Play with...", + "dvPlayerDialogDesc": "This content is in native Dolby Vision. Use Energy Player for accurate HDR colors and advanced audio support (Atmos/5.1 passthrough). Internal playback may have dull colors or limited audio codec support.", + "dvRememberChoice": "Remember my choice", + "dvPlayerNotInstalled": "Energy Player is not installed. Would you like to download it from the Microsoft Store?", + "dvPlayerGetFromStore": "Get from Store", + "dvEnableInstruction": "Configuration for native Dolby Vision and Audio:\n• Enable 'Allow Dolby Vision decoding' in Energy Player 'Video/Audio settings'.\n• Enable relevant 'Passthrough' options for Atmos/5.1 support.\n• Ensure HDR is enabled in Windows Display settings.", + "dvSyncWarning": "Note: Playback state is saved within Energy Player only and will not sync back to Fladder." } \ No newline at end of file diff --git a/lib/models/items/media_streams_model.dart b/lib/models/items/media_streams_model.dart index 63478e1ad..c1f7cc1db 100644 --- a/lib/models/items/media_streams_model.dart +++ b/lib/models/items/media_streams_model.dart @@ -71,6 +71,8 @@ class MediaStreamsModel { String? get mediaInfoTag => '${displayProfile?.value} ${resolution?.value}'; + bool get hasDolbyVision => videoStreams.any((element) => element.isDolbyVision); + Widget? audioIcon( BuildContext context, Function()? onTap, @@ -257,6 +259,19 @@ class VideoStreamModel extends StreamModel { index: stream.index ?? -1, ); } + + bool get isDolbyVision { + final range = videoRangeType; + if (range == null) return false; + return range == VideoRangeType.dovi || + range == VideoRangeType.doviwithhdr10 || + range == VideoRangeType.doviwithhlg || + range == VideoRangeType.doviwithsdr || + range == VideoRangeType.doviwithel || + range == VideoRangeType.doviwithhdr10plus || + range == VideoRangeType.doviwithelhdr10plus; + } + String get prettyName { return "${Resolution.fromVideoStream(this)?.value} - ${DisplayProfile.fromVideoStream(this).value} - (${codec.toUpperCase()})"; } diff --git a/lib/models/playback/direct_playback_model.dart b/lib/models/playback/direct_playback_model.dart index 19edfbfea..15f121ec4 100644 --- a/lib/models/playback/direct_playback_model.dart +++ b/lib/models/playback/direct_playback_model.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/widgets.dart' hide RepeatMode; import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/models/playback/transcode_playback_model.dart b/lib/models/playback/transcode_playback_model.dart index 4b0110542..5a20b949f 100644 --- a/lib/models/playback/transcode_playback_model.dart +++ b/lib/models/playback/transcode_playback_model.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/widgets.dart' hide RepeatMode; import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/models/settings/video_player_settings.dart b/lib/models/settings/video_player_settings.dart index 427f8bf32..cb8c213b8 100644 --- a/lib/models/settings/video_player_settings.dart +++ b/lib/models/settings/video_player_settings.dart @@ -84,6 +84,7 @@ abstract class VideoPlayerSettingsModel with _$VideoPlayerSettingsModel { @Default(2.0) double speedBoostRate, @Default(true) bool enableDoubleTapSeek, @Default(false) bool enableAdvancedVideoOptions, + @Default(DVPlayerChoice.ask) DVPlayerChoice dvPlayerChoice, }) = _VideoPlayerSettingsModel; double get volume => switch (defaultTargetPlatform) { @@ -245,3 +246,19 @@ Map get _defaultVideoHotKeys => { VideoHotKeys.exit => KeyCombination(key: LogicalKeyboardKey.escape), }, }; + +enum DVPlayerChoice { + internalPlayer, + ask, + energyPlayer; + + const DVPlayerChoice(); + + String label(BuildContext context) { + return switch (this) { + DVPlayerChoice.internalPlayer => context.localized.dvPlayerDisabled, + DVPlayerChoice.ask => context.localized.dvPlayerAsk, + DVPlayerChoice.energyPlayer => context.localized.dvPlayerEnergyPlayer, + }; + } +} diff --git a/lib/models/settings/video_player_settings.freezed.dart b/lib/models/settings/video_player_settings.freezed.dart index ef65e7386..692e44f07 100644 --- a/lib/models/settings/video_player_settings.freezed.dart +++ b/lib/models/settings/video_player_settings.freezed.dart @@ -35,6 +35,7 @@ mixin _$VideoPlayerSettingsModel implements DiagnosticableTreeMixin { double get speedBoostRate; bool get enableDoubleTapSeek; bool get enableAdvancedVideoOptions; + DVPlayerChoice get dvPlayerChoice; /// Create a copy of VideoPlayerSettingsModel /// with the given fields replaced by the non-null parameter values. @@ -72,12 +73,13 @@ mixin _$VideoPlayerSettingsModel implements DiagnosticableTreeMixin { ..add(DiagnosticsProperty('speedBoostRate', speedBoostRate)) ..add(DiagnosticsProperty('enableDoubleTapSeek', enableDoubleTapSeek)) ..add(DiagnosticsProperty( - 'enableAdvancedVideoOptions', enableAdvancedVideoOptions)); + 'enableAdvancedVideoOptions', enableAdvancedVideoOptions)) + ..add(DiagnosticsProperty('dvPlayerChoice', dvPlayerChoice)); } @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, enableTunneling: $enableTunneling, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys, screensaver: $screensaver, enableSpeedBoost: $enableSpeedBoost, speedBoostRate: $speedBoostRate, enableDoubleTapSeek: $enableDoubleTapSeek, enableAdvancedVideoOptions: $enableAdvancedVideoOptions)'; + return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, enableTunneling: $enableTunneling, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys, screensaver: $screensaver, enableSpeedBoost: $enableSpeedBoost, speedBoostRate: $speedBoostRate, enableDoubleTapSeek: $enableDoubleTapSeek, enableAdvancedVideoOptions: $enableAdvancedVideoOptions, dvPlayerChoice: $dvPlayerChoice)'; } } @@ -108,7 +110,8 @@ abstract mixin class $VideoPlayerSettingsModelCopyWith<$Res> { bool enableSpeedBoost, double speedBoostRate, bool enableDoubleTapSeek, - bool enableAdvancedVideoOptions}); + bool enableAdvancedVideoOptions, + DVPlayerChoice dvPlayerChoice}); } /// @nodoc @@ -145,6 +148,7 @@ class _$VideoPlayerSettingsModelCopyWithImpl<$Res> Object? speedBoostRate = null, Object? enableDoubleTapSeek = null, Object? enableAdvancedVideoOptions = null, + Object? dvPlayerChoice = null, }) { return _then(_self.copyWith( screenBrightness: freezed == screenBrightness @@ -231,6 +235,10 @@ class _$VideoPlayerSettingsModelCopyWithImpl<$Res> ? _self.enableAdvancedVideoOptions : enableAdvancedVideoOptions // ignore: cast_nullable_to_non_nullable as bool, + dvPlayerChoice: null == dvPlayerChoice + ? _self.dvPlayerChoice + : dvPlayerChoice // ignore: cast_nullable_to_non_nullable + as DVPlayerChoice, )); } } @@ -349,7 +357,8 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { bool enableSpeedBoost, double speedBoostRate, bool enableDoubleTapSeek, - bool enableAdvancedVideoOptions)? + bool enableAdvancedVideoOptions, + DVPlayerChoice dvPlayerChoice)? $default, { required TResult orElse(), }) { @@ -377,7 +386,8 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { _that.enableSpeedBoost, _that.speedBoostRate, _that.enableDoubleTapSeek, - _that.enableAdvancedVideoOptions); + _that.enableAdvancedVideoOptions, + _that.dvPlayerChoice); case _: return orElse(); } @@ -419,7 +429,8 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { bool enableSpeedBoost, double speedBoostRate, bool enableDoubleTapSeek, - bool enableAdvancedVideoOptions) + bool enableAdvancedVideoOptions, + DVPlayerChoice dvPlayerChoice) $default, ) { final _that = this; @@ -446,7 +457,8 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { _that.enableSpeedBoost, _that.speedBoostRate, _that.enableDoubleTapSeek, - _that.enableAdvancedVideoOptions); + _that.enableAdvancedVideoOptions, + _that.dvPlayerChoice); case _: throw StateError('Unexpected subclass'); } @@ -487,7 +499,8 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { bool enableSpeedBoost, double speedBoostRate, bool enableDoubleTapSeek, - bool enableAdvancedVideoOptions)? + bool enableAdvancedVideoOptions, + DVPlayerChoice dvPlayerChoice)? $default, ) { final _that = this; @@ -514,7 +527,8 @@ extension VideoPlayerSettingsModelPatterns on VideoPlayerSettingsModel { _that.enableSpeedBoost, _that.speedBoostRate, _that.enableDoubleTapSeek, - _that.enableAdvancedVideoOptions); + _that.enableAdvancedVideoOptions, + _that.dvPlayerChoice); case _: return null; } @@ -547,7 +561,8 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel this.enableSpeedBoost = false, this.speedBoostRate = 2.0, this.enableDoubleTapSeek = true, - this.enableAdvancedVideoOptions = false}) + this.enableAdvancedVideoOptions = false, + this.dvPlayerChoice = DVPlayerChoice.ask}) : _allowedOrientations = allowedOrientations, _segmentSkipSettings = segmentSkipSettings, _hotKeys = hotKeys, @@ -636,6 +651,9 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel @override @JsonKey() final bool enableAdvancedVideoOptions; + @override + @JsonKey() + final DVPlayerChoice dvPlayerChoice; /// Create a copy of VideoPlayerSettingsModel /// with the given fields replaced by the non-null parameter values. @@ -678,12 +696,13 @@ class _VideoPlayerSettingsModel extends VideoPlayerSettingsModel ..add(DiagnosticsProperty('speedBoostRate', speedBoostRate)) ..add(DiagnosticsProperty('enableDoubleTapSeek', enableDoubleTapSeek)) ..add(DiagnosticsProperty( - 'enableAdvancedVideoOptions', enableAdvancedVideoOptions)); + 'enableAdvancedVideoOptions', enableAdvancedVideoOptions)) + ..add(DiagnosticsProperty('dvPlayerChoice', dvPlayerChoice)); } @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, enableTunneling: $enableTunneling, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys, screensaver: $screensaver, enableSpeedBoost: $enableSpeedBoost, speedBoostRate: $speedBoostRate, enableDoubleTapSeek: $enableDoubleTapSeek, enableAdvancedVideoOptions: $enableAdvancedVideoOptions)'; + return 'VideoPlayerSettingsModel(screenBrightness: $screenBrightness, videoFit: $videoFit, fillScreen: $fillScreen, hardwareAccel: $hardwareAccel, useLibass: $useLibass, enableTunneling: $enableTunneling, bufferSize: $bufferSize, playerOptions: $playerOptions, internalVolume: $internalVolume, allowedOrientations: $allowedOrientations, nextVideoType: $nextVideoType, maxHomeBitrate: $maxHomeBitrate, maxInternetBitrate: $maxInternetBitrate, audioDevice: $audioDevice, segmentSkipSettings: $segmentSkipSettings, hotKeys: $hotKeys, screensaver: $screensaver, enableSpeedBoost: $enableSpeedBoost, speedBoostRate: $speedBoostRate, enableDoubleTapSeek: $enableDoubleTapSeek, enableAdvancedVideoOptions: $enableAdvancedVideoOptions, dvPlayerChoice: $dvPlayerChoice)'; } } @@ -716,7 +735,8 @@ abstract mixin class _$VideoPlayerSettingsModelCopyWith<$Res> bool enableSpeedBoost, double speedBoostRate, bool enableDoubleTapSeek, - bool enableAdvancedVideoOptions}); + bool enableAdvancedVideoOptions, + DVPlayerChoice dvPlayerChoice}); } /// @nodoc @@ -753,6 +773,7 @@ class __$VideoPlayerSettingsModelCopyWithImpl<$Res> Object? speedBoostRate = null, Object? enableDoubleTapSeek = null, Object? enableAdvancedVideoOptions = null, + Object? dvPlayerChoice = null, }) { return _then(_VideoPlayerSettingsModel( screenBrightness: freezed == screenBrightness @@ -839,6 +860,10 @@ class __$VideoPlayerSettingsModelCopyWithImpl<$Res> ? _self.enableAdvancedVideoOptions : enableAdvancedVideoOptions // ignore: cast_nullable_to_non_nullable as bool, + dvPlayerChoice: null == dvPlayerChoice + ? _self.dvPlayerChoice + : dvPlayerChoice // ignore: cast_nullable_to_non_nullable + as DVPlayerChoice, )); } } diff --git a/lib/models/settings/video_player_settings.g.dart b/lib/models/settings/video_player_settings.g.dart index 0b4547e8d..c61de0ab6 100644 --- a/lib/models/settings/video_player_settings.g.dart +++ b/lib/models/settings/video_player_settings.g.dart @@ -52,6 +52,9 @@ _VideoPlayerSettingsModel _$VideoPlayerSettingsModelFromJson( enableDoubleTapSeek: json['enableDoubleTapSeek'] as bool? ?? true, enableAdvancedVideoOptions: json['enableAdvancedVideoOptions'] as bool? ?? false, + dvPlayerChoice: $enumDecodeNullable( + _$DVPlayerChoiceEnumMap, json['dvPlayerChoice']) ?? + DVPlayerChoice.ask, ); Map _$VideoPlayerSettingsModelToJson( @@ -82,6 +85,7 @@ Map _$VideoPlayerSettingsModelToJson( 'speedBoostRate': instance.speedBoostRate, 'enableDoubleTapSeek': instance.enableDoubleTapSeek, 'enableAdvancedVideoOptions': instance.enableAdvancedVideoOptions, + 'dvPlayerChoice': _$DVPlayerChoiceEnumMap[instance.dvPlayerChoice]!, }; const _$BoxFitEnumMap = { @@ -175,3 +179,9 @@ const _$ScreensaverEnumMap = { Screensaver.time: 'time', Screensaver.black: 'black', }; + +const _$DVPlayerChoiceEnumMap = { + DVPlayerChoice.internalPlayer: 'internalPlayer', + DVPlayerChoice.ask: 'ask', + DVPlayerChoice.energyPlayer: 'energyPlayer', +}; diff --git a/lib/providers/settings/video_player_settings_provider.dart b/lib/providers/settings/video_player_settings_provider.dart index da71228cb..e8813b428 100644 --- a/lib/providers/settings/video_player_settings_provider.dart +++ b/lib/providers/settings/video_player_settings_provider.dart @@ -137,4 +137,6 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier state = state.copyWith(enableDoubleTapSeek: value); void setEnableAdvancedVideoOptions(bool value) => state = state.copyWith(enableAdvancedVideoOptions: value); + + void setDVPlayerChoice(DVPlayerChoice value) => state = state.copyWith(dvPlayerChoice: value); } diff --git a/lib/screens/settings/player_settings_page.dart b/lib/screens/settings/player_settings_page.dart index ff41cc885..8db3f2af3 100644 --- a/lib/screens/settings/player_settings_page.dart +++ b/lib/screens/settings/player_settings_page.dart @@ -138,6 +138,44 @@ class _PlayerSettingsPageState extends ConsumerState { ], ), const SizedBox(height: 12), + if (Platform.isWindows) + ...settingsListGroup( + context, + SettingsLabelDivider(label: context.localized.dvPlayerSelectionTitle), + [ + SettingsListTile( + label: Text(context.localized.dvPlayerSelectionTitle), + subLabel: Text(context.localized.dvPlayerSelectionDesc), + trailing: EnumBox( + current: videoSettings.dvPlayerChoice.label(context), + itemBuilder: (context) => DVPlayerChoice.values + .map( + (entry) => ItemActionButton( + label: Text(entry.label(context)), + action: () => provider.setDVPlayerChoice(entry), + ), + ) + .toList(), + ), + ), + AnimatedFadeSize( + child: Column( + children: [ + SettingsMessageBox( + context.localized.dvEnableInstruction, + messageType: MessageType.info, + ), + const SizedBox(height: 8), + SettingsMessageBox( + context.localized.dvSyncWarning, + messageType: MessageType.warning, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), ...settingsListGroup(context, SettingsLabelDivider(label: context.localized.mediaSegmentActions), [ ...videoSettings.segmentSkipSettings.entries.sorted((a, b) => b.key.index.compareTo(a.key.index)).map( (entry) => Padding( diff --git a/lib/seerr/seerr_models.g.dart b/lib/seerr/seerr_models.g.dart index c563bf8b7..23bd00c06 100644 --- a/lib/seerr/seerr_models.g.dart +++ b/lib/seerr/seerr_models.g.dart @@ -13,24 +13,32 @@ SeerrStatus _$SeerrStatusFromJson(Map json) => SeerrStatus( commitsBehind: (json['commitsBehind'] as num?)?.toInt(), ); -Map _$SeerrStatusToJson(SeerrStatus instance) => { +Map _$SeerrStatusToJson(SeerrStatus instance) => + { 'version': instance.version, 'commitTag': instance.commitTag, 'updateAvailable': instance.updateAvailable, 'commitsBehind': instance.commitsBehind, }; -SeerrUserQuota _$SeerrUserQuotaFromJson(Map json) => SeerrUserQuota( - movie: json['movie'] == null ? null : SeerrQuotaEntry.fromJson(json['movie'] as Map), - tv: json['tv'] == null ? null : SeerrQuotaEntry.fromJson(json['tv'] as Map), +SeerrUserQuota _$SeerrUserQuotaFromJson(Map json) => + SeerrUserQuota( + movie: json['movie'] == null + ? null + : SeerrQuotaEntry.fromJson(json['movie'] as Map), + tv: json['tv'] == null + ? null + : SeerrQuotaEntry.fromJson(json['tv'] as Map), ); -Map _$SeerrUserQuotaToJson(SeerrUserQuota instance) => { +Map _$SeerrUserQuotaToJson(SeerrUserQuota instance) => + { 'movie': instance.movie, 'tv': instance.tv, }; -SeerrQuotaEntry _$SeerrQuotaEntryFromJson(Map json) => SeerrQuotaEntry( +SeerrQuotaEntry _$SeerrQuotaEntryFromJson(Map json) => + SeerrQuotaEntry( days: (json['days'] as num?)?.toInt(), limit: (json['limit'] as num?)?.toInt(), used: (json['used'] as num?)?.toInt(), @@ -38,7 +46,8 @@ SeerrQuotaEntry _$SeerrQuotaEntryFromJson(Map json) => SeerrQuo restricted: json['restricted'] as bool?, ); -Map _$SeerrQuotaEntryToJson(SeerrQuotaEntry instance) => { +Map _$SeerrQuotaEntryToJson(SeerrQuotaEntry instance) => + { 'days': instance.days, 'limit': instance.limit, 'used': instance.used, @@ -46,47 +55,61 @@ Map _$SeerrQuotaEntryToJson(SeerrQuotaEntry instance) => json) => SeerrUserSettings( +SeerrUserSettings _$SeerrUserSettingsFromJson(Map json) => + SeerrUserSettings( locale: json['locale'] as String?, discoverRegion: json['discoverRegion'] as String?, originalLanguage: json['originalLanguage'] as String?, ); -Map _$SeerrUserSettingsToJson(SeerrUserSettings instance) => { +Map _$SeerrUserSettingsToJson(SeerrUserSettings instance) => + { 'locale': instance.locale, 'discoverRegion': instance.discoverRegion, 'originalLanguage': instance.originalLanguage, }; -SeerrUsersResponse _$SeerrUsersResponseFromJson(Map json) => SeerrUsersResponse( - results: - (json['results'] as List?)?.map((e) => SeerrUserModel.fromJson(e as Map)).toList(), - pageInfo: json['pageInfo'] == null ? null : SeerrPageInfo.fromJson(json['pageInfo'] as Map), +SeerrUsersResponse _$SeerrUsersResponseFromJson(Map json) => + SeerrUsersResponse( + results: (json['results'] as List?) + ?.map((e) => SeerrUserModel.fromJson(e as Map)) + .toList(), + pageInfo: json['pageInfo'] == null + ? null + : SeerrPageInfo.fromJson(json['pageInfo'] as Map), ); -Map _$SeerrUsersResponseToJson(SeerrUsersResponse instance) => { +Map _$SeerrUsersResponseToJson(SeerrUsersResponse instance) => + { 'results': instance.results, 'pageInfo': instance.pageInfo, }; -SeerrContentRating _$SeerrContentRatingFromJson(Map json) => SeerrContentRating( +SeerrContentRating _$SeerrContentRatingFromJson(Map json) => + SeerrContentRating( countryCode: json['iso_3166_1'] as String?, rating: json['rating'] as String?, descriptors: json['descriptors'] as List?, ); -Map _$SeerrContentRatingToJson(SeerrContentRating instance) => { +Map _$SeerrContentRatingToJson(SeerrContentRating instance) => + { 'iso_3166_1': instance.countryCode, 'rating': instance.rating, 'descriptors': instance.descriptors, }; SeerrCredits _$SeerrCreditsFromJson(Map json) => SeerrCredits( - cast: (json['cast'] as List?)?.map((e) => SeerrCast.fromJson(e as Map)).toList(), - crew: (json['crew'] as List?)?.map((e) => SeerrCrew.fromJson(e as Map)).toList(), + cast: (json['cast'] as List?) + ?.map((e) => SeerrCast.fromJson(e as Map)) + .toList(), + crew: (json['crew'] as List?) + ?.map((e) => SeerrCrew.fromJson(e as Map)) + .toList(), ); -Map _$SeerrCreditsToJson(SeerrCredits instance) => { +Map _$SeerrCreditsToJson(SeerrCredits instance) => + { 'cast': instance.cast, 'crew': instance.crew, }; @@ -133,7 +156,8 @@ Map _$SeerrCrewToJson(SeerrCrew instance) => { 'profilePath': instance.internalProfilePath, }; -SeerrMovieDetails _$SeerrMovieDetailsFromJson(Map json) => SeerrMovieDetails( +SeerrMovieDetails _$SeerrMovieDetailsFromJson(Map json) => + SeerrMovieDetails( id: (json['id'] as num?)?.toInt(), title: json['title'] as String?, originalTitle: json['originalTitle'] as String?, @@ -144,18 +168,28 @@ SeerrMovieDetails _$SeerrMovieDetailsFromJson(Map json) => Seer voteAverage: (json['voteAverage'] as num?)?.toDouble(), voteCount: (json['voteCount'] as num?)?.toInt(), runtime: (json['runtime'] as num?)?.toInt(), - genres: (json['genres'] as List?)?.map((e) => SeerrGenre.fromJson(e as Map)).toList(), - mediaInfo: json['mediaInfo'] == null ? null : SeerrMediaInfo.fromJson(json['mediaInfo'] as Map), - externalIds: - json['externalIds'] == null ? null : SeerrExternalIds.fromJson(json['externalIds'] as Map), - credits: json['credits'] == null ? null : SeerrCredits.fromJson(json['credits'] as Map), + genres: (json['genres'] as List?) + ?.map((e) => SeerrGenre.fromJson(e as Map)) + .toList(), + mediaInfo: json['mediaInfo'] == null + ? null + : SeerrMediaInfo.fromJson(json['mediaInfo'] as Map), + externalIds: json['externalIds'] == null + ? null + : SeerrExternalIds.fromJson( + json['externalIds'] as Map), + credits: json['credits'] == null + ? null + : SeerrCredits.fromJson(json['credits'] as Map), mediaId: _readJellyfinMediaId(json, 'mediaId') as String?, - contentRatings: (_readContentRatings(json, 'contentRatings') as List?) + contentRatings: (_readContentRatings(json, 'contentRatings') + as List?) ?.map((e) => SeerrContentRating.fromJson(e as Map)) .toList(), ); -Map _$SeerrMovieDetailsToJson(SeerrMovieDetails instance) => { +Map _$SeerrMovieDetailsToJson(SeerrMovieDetails instance) => + { 'id': instance.id, 'title': instance.title, 'originalTitle': instance.originalTitle, @@ -174,7 +208,8 @@ Map _$SeerrMovieDetailsToJson(SeerrMovieDetails instance) => json) => SeerrTvDetails( +SeerrTvDetails _$SeerrTvDetailsFromJson(Map json) => + SeerrTvDetails( id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, originalName: json['originalName'] as String?, @@ -187,22 +222,34 @@ SeerrTvDetails _$SeerrTvDetailsFromJson(Map json) => SeerrTvDet voteCount: (json['voteCount'] as num?)?.toInt(), numberOfSeasons: (json['numberOfSeasons'] as num?)?.toInt(), numberOfEpisodes: (json['numberOfEpisodes'] as num?)?.toInt(), - genres: (json['genres'] as List?)?.map((e) => SeerrGenre.fromJson(e as Map)).toList(), - seasons: - (json['seasons'] as List?)?.map((e) => SeerrSeason.fromJson(e as Map)).toList(), - mediaInfo: json['mediaInfo'] == null ? null : SeerrMediaInfo.fromJson(json['mediaInfo'] as Map), - externalIds: - json['externalIds'] == null ? null : SeerrExternalIds.fromJson(json['externalIds'] as Map), - keywords: - (json['keywords'] as List?)?.map((e) => SeerrKeyword.fromJson(e as Map)).toList(), - credits: json['credits'] == null ? null : SeerrCredits.fromJson(json['credits'] as Map), + genres: (json['genres'] as List?) + ?.map((e) => SeerrGenre.fromJson(e as Map)) + .toList(), + seasons: (json['seasons'] as List?) + ?.map((e) => SeerrSeason.fromJson(e as Map)) + .toList(), + mediaInfo: json['mediaInfo'] == null + ? null + : SeerrMediaInfo.fromJson(json['mediaInfo'] as Map), + externalIds: json['externalIds'] == null + ? null + : SeerrExternalIds.fromJson( + json['externalIds'] as Map), + keywords: (json['keywords'] as List?) + ?.map((e) => SeerrKeyword.fromJson(e as Map)) + .toList(), + credits: json['credits'] == null + ? null + : SeerrCredits.fromJson(json['credits'] as Map), mediaId: _readJellyfinMediaId(json, 'mediaId') as String?, - contentRatings: (_readContentRatings(json, 'contentRatings') as List?) + contentRatings: (_readContentRatings(json, 'contentRatings') + as List?) ?.map((e) => SeerrContentRating.fromJson(e as Map)) .toList(), ); -Map _$SeerrTvDetailsToJson(SeerrTvDetails instance) => { +Map _$SeerrTvDetailsToJson(SeerrTvDetails instance) => + { 'id': instance.id, 'name': instance.name, 'originalName': instance.originalName, @@ -230,7 +277,8 @@ SeerrGenre _$SeerrGenreFromJson(Map json) => SeerrGenre( name: json['name'] as String?, ); -Map _$SeerrGenreToJson(SeerrGenre instance) => { +Map _$SeerrGenreToJson(SeerrGenre instance) => + { 'id': instance.id, 'name': instance.name, }; @@ -240,7 +288,8 @@ SeerrKeyword _$SeerrKeywordFromJson(Map json) => SeerrKeyword( name: json['name'] as String?, ); -Map _$SeerrKeywordToJson(SeerrKeyword instance) => { +Map _$SeerrKeywordToJson(SeerrKeyword instance) => + { 'id': instance.id, 'name': instance.name, }; @@ -255,7 +304,8 @@ SeerrSeason _$SeerrSeasonFromJson(Map json) => SeerrSeason( mediaId: _readJellyfinMediaId(json, 'mediaId') as String?, ); -Map _$SeerrSeasonToJson(SeerrSeason instance) => { +Map _$SeerrSeasonToJson(SeerrSeason instance) => + { 'id': instance.id, 'name': instance.name, 'overview': instance.overview, @@ -265,17 +315,20 @@ Map _$SeerrSeasonToJson(SeerrSeason instance) => json) => SeerrSeasonDetails( +SeerrSeasonDetails _$SeerrSeasonDetailsFromJson(Map json) => + SeerrSeasonDetails( id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, overview: json['overview'] as String?, seasonNumber: (json['seasonNumber'] as num?)?.toInt(), internalPosterPath: json['posterPath'] as String?, - episodes: - (json['episodes'] as List?)?.map((e) => SeerrEpisode.fromJson(e as Map)).toList(), + episodes: (json['episodes'] as List?) + ?.map((e) => SeerrEpisode.fromJson(e as Map)) + .toList(), ); -Map _$SeerrSeasonDetailsToJson(SeerrSeasonDetails instance) => { +Map _$SeerrSeasonDetailsToJson(SeerrSeasonDetails instance) => + { 'id': instance.id, 'name': instance.name, 'overview': instance.overview, @@ -296,7 +349,8 @@ SeerrEpisode _$SeerrEpisodeFromJson(Map json) => SeerrEpisode( voteCount: (json['voteCount'] as num?)?.toInt(), ); -Map _$SeerrEpisodeToJson(SeerrEpisode instance) => { +Map _$SeerrEpisodeToJson(SeerrEpisode instance) => + { 'id': instance.id, 'name': instance.name, 'overview': instance.overview, @@ -308,7 +362,8 @@ Map _$SeerrEpisodeToJson(SeerrEpisode instance) => json) => +SeerrDownloadStatusEpisode _$SeerrDownloadStatusEpisodeFromJson( + Map json) => SeerrDownloadStatusEpisode( seriesId: (json['seriesId'] as num?)?.toInt(), tvdbId: (json['tvdbId'] as num?)?.toInt(), @@ -328,7 +383,9 @@ SeerrDownloadStatusEpisode _$SeerrDownloadStatusEpisodeFromJson(Map _$SeerrDownloadStatusEpisodeToJson(SeerrDownloadStatusEpisode instance) => { +Map _$SeerrDownloadStatusEpisodeToJson( + SeerrDownloadStatusEpisode instance) => + { 'seriesId': instance.seriesId, 'tvdbId': instance.tvdbId, 'episodeFileId': instance.episodeFileId, @@ -347,7 +404,8 @@ Map _$SeerrDownloadStatusEpisodeToJson(SeerrDownloadStatusEpiso 'id': instance.id, }; -SeerrDownloadStatus _$SeerrDownloadStatusFromJson(Map json) => SeerrDownloadStatus( +SeerrDownloadStatus _$SeerrDownloadStatusFromJson(Map json) => + SeerrDownloadStatus( externalId: (json['externalId'] as num?)?.toInt(), estimatedCompletionTime: json['estimatedCompletionTime'] as String?, mediaType: json['mediaType'] as String?, @@ -356,12 +414,16 @@ SeerrDownloadStatus _$SeerrDownloadStatusFromJson(Map json) => status: json['status'] as String?, timeLeft: json['timeLeft'] as String?, title: json['title'] as String?, - episode: - json['episode'] == null ? null : SeerrDownloadStatusEpisode.fromJson(json['episode'] as Map), + episode: json['episode'] == null + ? null + : SeerrDownloadStatusEpisode.fromJson( + json['episode'] as Map), downloadId: json['downloadId'] as String?, ); -Map _$SeerrDownloadStatusToJson(SeerrDownloadStatus instance) => { +Map _$SeerrDownloadStatusToJson( + SeerrDownloadStatus instance) => + { 'externalId': instance.externalId, 'estimatedCompletionTime': instance.estimatedCompletionTime, 'mediaType': instance.mediaType, @@ -374,7 +436,9 @@ Map _$SeerrDownloadStatusToJson(SeerrDownloadStatus instance) = 'downloadId': instance.downloadId, }; -SeerrMediaInfoSeason _$SeerrMediaInfoSeasonFromJson(Map json) => SeerrMediaInfoSeason( +SeerrMediaInfoSeason _$SeerrMediaInfoSeasonFromJson( + Map json) => + SeerrMediaInfoSeason( id: (json['id'] as num?)?.toInt(), seasonNumber: (json['seasonNumber'] as num?)?.toInt(), status: (json['status'] as num?)?.toInt(), @@ -382,7 +446,9 @@ SeerrMediaInfoSeason _$SeerrMediaInfoSeasonFromJson(Map json) = updatedAt: json['updatedAt'] as String?, ); -Map _$SeerrMediaInfoSeasonToJson(SeerrMediaInfoSeason instance) => { +Map _$SeerrMediaInfoSeasonToJson( + SeerrMediaInfoSeason instance) => + { 'id': instance.id, 'seasonNumber': instance.seasonNumber, 'status': instance.status, @@ -390,31 +456,42 @@ Map _$SeerrMediaInfoSeasonToJson(SeerrMediaInfoSeason instance) 'updatedAt': instance.updatedAt, }; -SeerrExternalIds _$SeerrExternalIdsFromJson(Map json) => SeerrExternalIds( +SeerrExternalIds _$SeerrExternalIdsFromJson(Map json) => + SeerrExternalIds( imdbId: json['imdbId'] as String?, facebookId: json['facebookId'] as String?, instagramId: json['instagramId'] as String?, twitterId: json['twitterId'] as String?, ); -Map _$SeerrExternalIdsToJson(SeerrExternalIds instance) => { +Map _$SeerrExternalIdsToJson(SeerrExternalIds instance) => + { 'imdbId': instance.imdbId, 'facebookId': instance.facebookId, 'instagramId': instance.instagramId, 'twitterId': instance.twitterId, }; -SeerrRatingsResponse _$SeerrRatingsResponseFromJson(Map json) => SeerrRatingsResponse( - rt: json['rt'] == null ? null : SeerrRtRating.fromJson(json['rt'] as Map), - imdb: json['imdb'] == null ? null : SeerrImdbRating.fromJson(json['imdb'] as Map), +SeerrRatingsResponse _$SeerrRatingsResponseFromJson( + Map json) => + SeerrRatingsResponse( + rt: json['rt'] == null + ? null + : SeerrRtRating.fromJson(json['rt'] as Map), + imdb: json['imdb'] == null + ? null + : SeerrImdbRating.fromJson(json['imdb'] as Map), ); -Map _$SeerrRatingsResponseToJson(SeerrRatingsResponse instance) => { +Map _$SeerrRatingsResponseToJson( + SeerrRatingsResponse instance) => + { 'rt': instance.rt, 'imdb': instance.imdb, }; -SeerrRtRating _$SeerrRtRatingFromJson(Map json) => SeerrRtRating( +SeerrRtRating _$SeerrRtRatingFromJson(Map json) => + SeerrRtRating( title: json['title'] as String?, year: (json['year'] as num?)?.toInt(), criticsScore: (json['criticsScore'] as num?)?.toInt(), @@ -424,7 +501,8 @@ SeerrRtRating _$SeerrRtRatingFromJson(Map json) => SeerrRtRatin url: json['url'] as String?, ); -Map _$SeerrRtRatingToJson(SeerrRtRating instance) => { +Map _$SeerrRtRatingToJson(SeerrRtRating instance) => + { 'title': instance.title, 'year': instance.year, 'criticsScore': instance.criticsScore, @@ -434,40 +512,58 @@ Map _$SeerrRtRatingToJson(SeerrRtRating instance) => json) => SeerrImdbRating( +SeerrImdbRating _$SeerrImdbRatingFromJson(Map json) => + SeerrImdbRating( title: json['title'] as String?, url: json['url'] as String?, criticsScore: (json['criticsScore'] as num?)?.toDouble(), ); -Map _$SeerrImdbRatingToJson(SeerrImdbRating instance) => { +Map _$SeerrImdbRatingToJson(SeerrImdbRating instance) => + { 'title': instance.title, 'url': instance.url, 'criticsScore': instance.criticsScore, }; -SeerrRequestsResponse _$SeerrRequestsResponseFromJson(Map json) => SeerrRequestsResponse( +SeerrRequestsResponse _$SeerrRequestsResponseFromJson( + Map json) => + SeerrRequestsResponse( results: (json['results'] as List?) ?.map((e) => SeerrMediaRequest.fromJson(e as Map)) .toList(), - pageInfo: json['pageInfo'] == null ? null : SeerrPageInfo.fromJson(json['pageInfo'] as Map), + pageInfo: json['pageInfo'] == null + ? null + : SeerrPageInfo.fromJson(json['pageInfo'] as Map), ); -Map _$SeerrRequestsResponseToJson(SeerrRequestsResponse instance) => { +Map _$SeerrRequestsResponseToJson( + SeerrRequestsResponse instance) => + { 'results': instance.results, 'pageInfo': instance.pageInfo, }; -SeerrMediaRequest _$SeerrMediaRequestFromJson(Map json) => SeerrMediaRequest( +SeerrMediaRequest _$SeerrMediaRequestFromJson(Map json) => + SeerrMediaRequest( id: (json['id'] as num?)?.toInt(), status: (json['status'] as num?)?.toInt(), - media: json['media'] == null ? null : SeerrMedia.fromJson(json['media'] as Map), - createdAt: json['createdAt'] == null ? null : DateTime.parse(json['createdAt'] as String), - updatedAt: json['updatedAt'] == null ? null : DateTime.parse(json['updatedAt'] as String), - requestedBy: - json['requestedBy'] == null ? null : SeerrUserModel.fromJson(json['requestedBy'] as Map), - modifiedBy: - json['modifiedBy'] == null ? null : SeerrUserModel.fromJson(json['modifiedBy'] as Map), + media: json['media'] == null + ? null + : SeerrMedia.fromJson(json['media'] as Map), + createdAt: json['createdAt'] == null + ? null + : DateTime.parse(json['createdAt'] as String), + updatedAt: json['updatedAt'] == null + ? null + : DateTime.parse(json['updatedAt'] as String), + requestedBy: json['requestedBy'] == null + ? null + : SeerrUserModel.fromJson( + json['requestedBy'] as Map), + modifiedBy: json['modifiedBy'] == null + ? null + : SeerrUserModel.fromJson(json['modifiedBy'] as Map), is4k: json['is4k'] as bool?, seasons: _parseRequestSeasons(json['seasons'] as List?), serverId: (json['serverId'] as num?)?.toInt(), @@ -475,7 +571,8 @@ SeerrMediaRequest _$SeerrMediaRequestFromJson(Map json) => Seer rootFolder: json['rootFolder'] as String?, ); -Map _$SeerrMediaRequestToJson(SeerrMediaRequest instance) => { +Map _$SeerrMediaRequestToJson(SeerrMediaRequest instance) => + { 'id': instance.id, 'status': instance.status, 'media': instance.media, @@ -490,33 +587,43 @@ Map _$SeerrMediaRequestToJson(SeerrMediaRequest instance) => json) => SeerrPageInfo( +SeerrPageInfo _$SeerrPageInfoFromJson(Map json) => + SeerrPageInfo( pages: (json['pages'] as num?)?.toInt(), pageSize: (json['pageSize'] as num?)?.toInt(), results: (json['results'] as num?)?.toInt(), page: (json['page'] as num?)?.toInt(), ); -Map _$SeerrPageInfoToJson(SeerrPageInfo instance) => { +Map _$SeerrPageInfoToJson(SeerrPageInfo instance) => + { 'pages': instance.pages, 'pageSize': instance.pageSize, 'results': instance.results, 'page': instance.page, }; -SeerrCreateRequestBody _$SeerrCreateRequestBodyFromJson(Map json) => SeerrCreateRequestBody( +SeerrCreateRequestBody _$SeerrCreateRequestBodyFromJson( + Map json) => + SeerrCreateRequestBody( mediaType: json['mediaType'] as String?, mediaId: (json['mediaId'] as num?)?.toInt(), is4k: json['is4k'] as bool?, - seasons: (json['seasons'] as List?)?.map((e) => (e as num).toInt()).toList(), + seasons: (json['seasons'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), serverId: (json['serverId'] as num?)?.toInt(), profileId: (json['profileId'] as num?)?.toInt(), rootFolder: json['rootFolder'] as String?, - tags: (json['tags'] as List?)?.map((e) => (e as num).toInt()).toList(), + tags: (json['tags'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), userId: (json['userId'] as num?)?.toInt(), ); -Map _$SeerrCreateRequestBodyToJson(SeerrCreateRequestBody instance) => { +Map _$SeerrCreateRequestBodyToJson( + SeerrCreateRequestBody instance) => + { 'mediaType': instance.mediaType, 'mediaId': instance.mediaId, if (instance.is4k case final value?) 'is4k': value, @@ -539,7 +646,8 @@ SeerrMedia _$SeerrMediaFromJson(Map json) => SeerrMedia( .toList(), ); -Map _$SeerrMediaToJson(SeerrMedia instance) => { +Map _$SeerrMediaToJson(SeerrMedia instance) => + { 'id': instance.id, 'tmdbId': instance.tmdbId, 'tvdbId': instance.tvdbId, @@ -548,19 +656,27 @@ Map _$SeerrMediaToJson(SeerrMedia instance) => json) => SeerrMediaResponse( - results: (json['results'] as List?)?.map((e) => SeerrMedia.fromJson(e as Map)).toList(), - pageInfo: json['pageInfo'] == null ? null : SeerrPageInfo.fromJson(json['pageInfo'] as Map), +SeerrMediaResponse _$SeerrMediaResponseFromJson(Map json) => + SeerrMediaResponse( + results: (json['results'] as List?) + ?.map((e) => SeerrMedia.fromJson(e as Map)) + .toList(), + pageInfo: json['pageInfo'] == null + ? null + : SeerrPageInfo.fromJson(json['pageInfo'] as Map), ); -Map _$SeerrMediaResponseToJson(SeerrMediaResponse instance) => { +Map _$SeerrMediaResponseToJson(SeerrMediaResponse instance) => + { 'results': instance.results, 'pageInfo': instance.pageInfo, }; -SeerrDiscoverItem _$SeerrDiscoverItemFromJson(Map json) => SeerrDiscoverItem( +SeerrDiscoverItem _$SeerrDiscoverItemFromJson(Map json) => + SeerrDiscoverItem( id: (json['id'] as num?)?.toInt(), - mediaType: $enumDecodeNullable(_$SeerrMediaTypeEnumMap, json['mediaType']), + mediaType: + $enumDecodeNullable(_$SeerrMediaTypeEnumMap, json['mediaType']), title: json['title'] as String?, name: json['name'] as String?, originalTitle: json['originalTitle'] as String?, @@ -570,11 +686,14 @@ SeerrDiscoverItem _$SeerrDiscoverItemFromJson(Map json) => Seer internalBackdropPath: json['backdropPath'] as String?, releaseDate: json['releaseDate'] as String?, firstAirDate: json['firstAirDate'] as String?, - mediaInfo: json['mediaInfo'] == null ? null : SeerrMediaInfo.fromJson(json['mediaInfo'] as Map), + mediaInfo: json['mediaInfo'] == null + ? null + : SeerrMediaInfo.fromJson(json['mediaInfo'] as Map), mediaId: _readJellyfinMediaId(json, 'mediaId') as String?, ); -Map _$SeerrDiscoverItemToJson(SeerrDiscoverItem instance) => { +Map _$SeerrDiscoverItemToJson(SeerrDiscoverItem instance) => + { 'id': instance.id, 'mediaType': _$SeerrMediaTypeEnumMap[instance.mediaType], 'title': instance.title, @@ -596,7 +715,9 @@ const _$SeerrMediaTypeEnumMap = { SeerrMediaType.person: 'person', }; -SeerrDiscoverResponse _$SeerrDiscoverResponseFromJson(Map json) => SeerrDiscoverResponse( +SeerrDiscoverResponse _$SeerrDiscoverResponseFromJson( + Map json) => + SeerrDiscoverResponse( results: (json['results'] as List?) ?.map((e) => SeerrDiscoverItem.fromJson(e as Map)) .toList(), @@ -605,82 +726,107 @@ SeerrDiscoverResponse _$SeerrDiscoverResponseFromJson(Map json) totalResults: (json['totalResults'] as num?)?.toInt(), ); -Map _$SeerrDiscoverResponseToJson(SeerrDiscoverResponse instance) => { +Map _$SeerrDiscoverResponseToJson( + SeerrDiscoverResponse instance) => + { 'results': instance.results, 'page': instance.page, 'totalPages': instance.totalPages, 'totalResults': instance.totalResults, }; -SeerrGenreResponse _$SeerrGenreResponseFromJson(Map json) => SeerrGenreResponse( - genres: (json['genres'] as List?)?.map((e) => SeerrGenre.fromJson(e as Map)).toList(), +SeerrGenreResponse _$SeerrGenreResponseFromJson(Map json) => + SeerrGenreResponse( + genres: (json['genres'] as List?) + ?.map((e) => SeerrGenre.fromJson(e as Map)) + .toList(), ); -Map _$SeerrGenreResponseToJson(SeerrGenreResponse instance) => { +Map _$SeerrGenreResponseToJson(SeerrGenreResponse instance) => + { 'genres': instance.genres, }; -SeerrWatchProvider _$SeerrWatchProviderFromJson(Map json) => SeerrWatchProvider( +SeerrWatchProvider _$SeerrWatchProviderFromJson(Map json) => + SeerrWatchProvider( providerId: (json['id'] as num?)?.toInt(), providerName: json['name'] as String?, internalLogoPath: json['logoPath'] as String?, displayPriority: (json['displayPriority'] as num?)?.toInt(), ); -Map _$SeerrWatchProviderToJson(SeerrWatchProvider instance) => { +Map _$SeerrWatchProviderToJson(SeerrWatchProvider instance) => + { 'id': instance.providerId, 'name': instance.providerName, 'logoPath': instance.internalLogoPath, 'displayPriority': instance.displayPriority, }; -SeerrWatchProviderRegion _$SeerrWatchProviderRegionFromJson(Map json) => SeerrWatchProviderRegion( +SeerrWatchProviderRegion _$SeerrWatchProviderRegionFromJson( + Map json) => + SeerrWatchProviderRegion( iso31661: json['iso_3166_1'] as String?, englishName: json['english_name'] as String?, nativeName: json['native_name'] as String?, ); -Map _$SeerrWatchProviderRegionToJson(SeerrWatchProviderRegion instance) => { +Map _$SeerrWatchProviderRegionToJson( + SeerrWatchProviderRegion instance) => + { 'iso_3166_1': instance.iso31661, 'english_name': instance.englishName, 'native_name': instance.nativeName, }; -SeerrCertification _$SeerrCertificationFromJson(Map json) => SeerrCertification( +SeerrCertification _$SeerrCertificationFromJson(Map json) => + SeerrCertification( certification: json['certification'] as String?, meaning: json['meaning'] as String?, order: (json['order'] as num?)?.toInt(), ); -Map _$SeerrCertificationToJson(SeerrCertification instance) => { +Map _$SeerrCertificationToJson(SeerrCertification instance) => + { 'certification': instance.certification, 'meaning': instance.meaning, 'order': instance.order, }; -SeerrCertificationsResponse _$SeerrCertificationsResponseFromJson(Map json) => +SeerrCertificationsResponse _$SeerrCertificationsResponseFromJson( + Map json) => SeerrCertificationsResponse( certifications: (json['certifications'] as Map?)?.map( (k, e) => MapEntry( - k, (e as List).map((e) => SeerrCertification.fromJson(e as Map)).toList()), + k, + (e as List) + .map((e) => + SeerrCertification.fromJson(e as Map)) + .toList()), ), ); -Map _$SeerrCertificationsResponseToJson(SeerrCertificationsResponse instance) => { +Map _$SeerrCertificationsResponseToJson( + SeerrCertificationsResponse instance) => + { 'certifications': instance.certifications, }; -SeerrAuthLocalBody _$SeerrAuthLocalBodyFromJson(Map json) => SeerrAuthLocalBody( +SeerrAuthLocalBody _$SeerrAuthLocalBodyFromJson(Map json) => + SeerrAuthLocalBody( email: json['email'] as String, password: json['password'] as String, ); -Map _$SeerrAuthLocalBodyToJson(SeerrAuthLocalBody instance) => { +Map _$SeerrAuthLocalBodyToJson(SeerrAuthLocalBody instance) => + { 'email': instance.email, 'password': instance.password, }; -SeerrAuthJellyfinBody _$SeerrAuthJellyfinBodyFromJson(Map json) => SeerrAuthJellyfinBody( +SeerrAuthJellyfinBody _$SeerrAuthJellyfinBodyFromJson( + Map json) => + SeerrAuthJellyfinBody( username: json['username'] as String, password: json['password'] as String, customHeaders: (json['customHeaders'] as Map?)?.map( @@ -689,14 +835,17 @@ SeerrAuthJellyfinBody _$SeerrAuthJellyfinBodyFromJson(Map json) hostname: json['hostname'] as String?, ); -Map _$SeerrAuthJellyfinBodyToJson(SeerrAuthJellyfinBody instance) => { +Map _$SeerrAuthJellyfinBodyToJson( + SeerrAuthJellyfinBody instance) => + { 'username': instance.username, 'password': instance.password, if (instance.customHeaders case final value?) 'customHeaders': value, if (instance.hostname case final value?) 'hostname': value, }; -_SeerrUserModel _$SeerrUserModelFromJson(Map json) => _SeerrUserModel( +_SeerrUserModel _$SeerrUserModelFromJson(Map json) => + _SeerrUserModel( id: (json['id'] as num?)?.toInt(), email: json['email'] as String?, username: json['username'] as String?, @@ -705,14 +854,18 @@ _SeerrUserModel _$SeerrUserModelFromJson(Map json) => _SeerrUse plexUsername: json['plexUsername'] as String?, permissions: (json['permissions'] as num?)?.toInt(), avatar: json['avatar'] as String?, - settings: json['settings'] == null ? null : SeerrUserSettings.fromJson(json['settings'] as Map), + settings: json['settings'] == null + ? null + : SeerrUserSettings.fromJson( + json['settings'] as Map), movieQuotaLimit: (json['movieQuotaLimit'] as num?)?.toInt(), movieQuotaDays: (json['movieQuotaDays'] as num?)?.toInt(), tvQuotaLimit: (json['tvQuotaLimit'] as num?)?.toInt(), tvQuotaDays: (json['tvQuotaDays'] as num?)?.toInt(), ); -Map _$SeerrUserModelToJson(_SeerrUserModel instance) => { +Map _$SeerrUserModelToJson(_SeerrUserModel instance) => + { 'id': instance.id, 'email': instance.email, 'username': instance.username, @@ -728,7 +881,8 @@ Map _$SeerrUserModelToJson(_SeerrUserModel instance) => json) => _SeerrSonarrServer( +_SeerrSonarrServer _$SeerrSonarrServerFromJson(Map json) => + _SeerrSonarrServer( id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, hostname: json['hostname'] as String?, @@ -738,10 +892,12 @@ _SeerrSonarrServer _$SeerrSonarrServerFromJson(Map json) => _Se baseUrl: json['baseUrl'] as String?, activeProfileId: (json['activeProfileId'] as num?)?.toInt(), activeProfileName: json['activeProfileName'] as String?, - activeLanguageProfileId: (json['activeLanguageProfileId'] as num?)?.toInt(), + activeLanguageProfileId: + (json['activeLanguageProfileId'] as num?)?.toInt(), activeDirectory: json['activeDirectory'] as String?, activeAnimeProfileId: (json['activeAnimeProfileId'] as num?)?.toInt(), - activeAnimeLanguageProfileId: (json['activeAnimeLanguageProfileId'] as num?)?.toInt(), + activeAnimeLanguageProfileId: + (json['activeAnimeLanguageProfileId'] as num?)?.toInt(), activeAnimeProfileName: json['activeAnimeProfileName'] as String?, activeAnimeDirectory: json['activeAnimeDirectory'] as String?, is4k: json['is4k'] as bool?, @@ -752,14 +908,19 @@ _SeerrSonarrServer _$SeerrSonarrServerFromJson(Map json) => _Se profiles: (json['profiles'] as List?) ?.map((e) => SeerrServiceProfile.fromJson(e as Map)) .toList(), - tags: (json['tags'] as List?)?.map((e) => SeerrServiceTag.fromJson(e as Map)).toList(), + tags: (json['tags'] as List?) + ?.map((e) => SeerrServiceTag.fromJson(e as Map)) + .toList(), rootFolders: (json['rootFolders'] as List?) ?.map((e) => SeerrRootFolder.fromJson(e as Map)) .toList(), - activeTags: (json['activeTags'] as List?)?.map((e) => (e as num).toInt()).toList(), + activeTags: (json['activeTags'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), ); -Map _$SeerrSonarrServerToJson(_SeerrSonarrServer instance) => { +Map _$SeerrSonarrServerToJson(_SeerrSonarrServer instance) => + { 'id': instance.id, 'name': instance.name, 'hostname': instance.hostname, @@ -786,25 +947,34 @@ Map _$SeerrSonarrServerToJson(_SeerrSonarrServer instance) => < 'activeTags': instance.activeTags, }; -_SeerrSonarrServerResponse _$SeerrSonarrServerResponseFromJson(Map json) => _SeerrSonarrServerResponse( - server: json['server'] == null ? null : SeerrSonarrServer.fromJson(json['server'] as Map), +_SeerrSonarrServerResponse _$SeerrSonarrServerResponseFromJson( + Map json) => + _SeerrSonarrServerResponse( + server: json['server'] == null + ? null + : SeerrSonarrServer.fromJson(json['server'] as Map), profiles: (json['profiles'] as List?) ?.map((e) => SeerrServiceProfile.fromJson(e as Map)) .toList(), rootFolders: (json['rootFolders'] as List?) ?.map((e) => SeerrRootFolder.fromJson(e as Map)) .toList(), - tags: (json['tags'] as List?)?.map((e) => SeerrServiceTag.fromJson(e as Map)).toList(), + tags: (json['tags'] as List?) + ?.map((e) => SeerrServiceTag.fromJson(e as Map)) + .toList(), ); -Map _$SeerrSonarrServerResponseToJson(_SeerrSonarrServerResponse instance) => { +Map _$SeerrSonarrServerResponseToJson( + _SeerrSonarrServerResponse instance) => + { 'server': instance.server, 'profiles': instance.profiles, 'rootFolders': instance.rootFolders, 'tags': instance.tags, }; -_SeerrRadarrServer _$SeerrRadarrServerFromJson(Map json) => _SeerrRadarrServer( +_SeerrRadarrServer _$SeerrRadarrServerFromJson(Map json) => + _SeerrRadarrServer( id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, hostname: json['hostname'] as String?, @@ -814,10 +984,12 @@ _SeerrRadarrServer _$SeerrRadarrServerFromJson(Map json) => _Se baseUrl: json['baseUrl'] as String?, activeProfileId: (json['activeProfileId'] as num?)?.toInt(), activeProfileName: json['activeProfileName'] as String?, - activeLanguageProfileId: (json['activeLanguageProfileId'] as num?)?.toInt(), + activeLanguageProfileId: + (json['activeLanguageProfileId'] as num?)?.toInt(), activeDirectory: json['activeDirectory'] as String?, activeAnimeProfileId: (json['activeAnimeProfileId'] as num?)?.toInt(), - activeAnimeLanguageProfileId: (json['activeAnimeLanguageProfileId'] as num?)?.toInt(), + activeAnimeLanguageProfileId: + (json['activeAnimeLanguageProfileId'] as num?)?.toInt(), activeAnimeProfileName: json['activeAnimeProfileName'] as String?, activeAnimeDirectory: json['activeAnimeDirectory'] as String?, is4k: json['is4k'] as bool?, @@ -828,14 +1000,19 @@ _SeerrRadarrServer _$SeerrRadarrServerFromJson(Map json) => _Se profiles: (json['profiles'] as List?) ?.map((e) => SeerrServiceProfile.fromJson(e as Map)) .toList(), - tags: (json['tags'] as List?)?.map((e) => SeerrServiceTag.fromJson(e as Map)).toList(), + tags: (json['tags'] as List?) + ?.map((e) => SeerrServiceTag.fromJson(e as Map)) + .toList(), rootFolders: (json['rootFolders'] as List?) ?.map((e) => SeerrRootFolder.fromJson(e as Map)) .toList(), - activeTags: (json['activeTags'] as List?)?.map((e) => (e as num).toInt()).toList(), + activeTags: (json['activeTags'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), ); -Map _$SeerrRadarrServerToJson(_SeerrRadarrServer instance) => { +Map _$SeerrRadarrServerToJson(_SeerrRadarrServer instance) => + { 'id': instance.id, 'name': instance.name, 'hostname': instance.hostname, @@ -862,57 +1039,73 @@ Map _$SeerrRadarrServerToJson(_SeerrRadarrServer instance) => < 'activeTags': instance.activeTags, }; -_SeerrRadarrServerResponse _$SeerrRadarrServerResponseFromJson(Map json) => _SeerrRadarrServerResponse( - server: json['server'] == null ? null : SeerrRadarrServer.fromJson(json['server'] as Map), +_SeerrRadarrServerResponse _$SeerrRadarrServerResponseFromJson( + Map json) => + _SeerrRadarrServerResponse( + server: json['server'] == null + ? null + : SeerrRadarrServer.fromJson(json['server'] as Map), profiles: (json['profiles'] as List?) ?.map((e) => SeerrServiceProfile.fromJson(e as Map)) .toList(), rootFolders: (json['rootFolders'] as List?) ?.map((e) => SeerrRootFolder.fromJson(e as Map)) .toList(), - tags: (json['tags'] as List?)?.map((e) => SeerrServiceTag.fromJson(e as Map)).toList(), + tags: (json['tags'] as List?) + ?.map((e) => SeerrServiceTag.fromJson(e as Map)) + .toList(), ); -Map _$SeerrRadarrServerResponseToJson(_SeerrRadarrServerResponse instance) => { +Map _$SeerrRadarrServerResponseToJson( + _SeerrRadarrServerResponse instance) => + { 'server': instance.server, 'profiles': instance.profiles, 'rootFolders': instance.rootFolders, 'tags': instance.tags, }; -_SeerrServiceProfile _$SeerrServiceProfileFromJson(Map json) => _SeerrServiceProfile( +_SeerrServiceProfile _$SeerrServiceProfileFromJson(Map json) => + _SeerrServiceProfile( id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, ); -Map _$SeerrServiceProfileToJson(_SeerrServiceProfile instance) => { +Map _$SeerrServiceProfileToJson( + _SeerrServiceProfile instance) => + { 'id': instance.id, 'name': instance.name, }; -_SeerrServiceTag _$SeerrServiceTagFromJson(Map json) => _SeerrServiceTag( +_SeerrServiceTag _$SeerrServiceTagFromJson(Map json) => + _SeerrServiceTag( id: (json['id'] as num?)?.toInt(), label: json['label'] as String?, ); -Map _$SeerrServiceTagToJson(_SeerrServiceTag instance) => { +Map _$SeerrServiceTagToJson(_SeerrServiceTag instance) => + { 'id': instance.id, 'label': instance.label, }; -_SeerrRootFolder _$SeerrRootFolderFromJson(Map json) => _SeerrRootFolder( +_SeerrRootFolder _$SeerrRootFolderFromJson(Map json) => + _SeerrRootFolder( id: (json['id'] as num?)?.toInt(), freeSpace: (json['freeSpace'] as num?)?.toInt(), path: json['path'] as String?, ); -Map _$SeerrRootFolderToJson(_SeerrRootFolder instance) => { +Map _$SeerrRootFolderToJson(_SeerrRootFolder instance) => + { 'id': instance.id, 'freeSpace': instance.freeSpace, 'path': instance.path, }; -_SeerrMediaInfo _$SeerrMediaInfoFromJson(Map json) => _SeerrMediaInfo( +_SeerrMediaInfo _$SeerrMediaInfoFromJson(Map json) => + _SeerrMediaInfo( id: (json['id'] as num?)?.toInt(), tmdbId: (json['tmdbId'] as num?)?.toInt(), tvdbId: (json['tvdbId'] as num?)?.toInt(), @@ -934,7 +1127,8 @@ _SeerrMediaInfo _$SeerrMediaInfoFromJson(Map json) => _SeerrMed .toList(), ); -Map _$SeerrMediaInfoToJson(_SeerrMediaInfo instance) => { +Map _$SeerrMediaInfoToJson(_SeerrMediaInfo instance) => + { 'id': instance.id, 'tmdbId': instance.tmdbId, 'tvdbId': instance.tvdbId, diff --git a/lib/util/external_player_helper.dart b/lib/util/external_player_helper.dart new file mode 100644 index 000000000..3904808fc --- /dev/null +++ b/lib/util/external_player_helper.dart @@ -0,0 +1,76 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'dart:io'; + +import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/providers/user_provider.dart'; + +class ExternalPlayerHelper { + static const String energyProtocol = 'energyplayer:launch=media'; + static const String energyStoreUrl = + 'ms-windows-store://pdp/?productid=9P9ZH5FL1BFK'; + + static bool canShowEnergyPlayer(ItemBaseModel? item) { + if (!Platform.isWindows) return false; + if (item == null) return false; + return item.streamModel?.hasDolbyVision ?? false; + } + + static Future isEnergyPlayerInstalled() async { + if (!Platform.isWindows) return false; + final uri = Uri.parse('energyplayer:'); + return await canLaunchUrl(uri); + } + + static Future openStore() async { + final uri = Uri.parse(energyStoreUrl); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + // Fallback to web link if store protocol fails + await launchUrl( + Uri.parse('https://apps.microsoft.com/detail/9P9ZH5FL1BFK')); + } + } + + static Future launchEnergyPlayer( + WidgetRef ref, ItemBaseModel item) async { + final user = ref.read(userProvider); + if (user == null) return; + + var serverUrl = user.credentials.url; + final apiKey = user.credentials.token; + + if (serverUrl.isEmpty || apiKey.isEmpty) return; + + // Normalize serverUrl to avoid double slashes + if (serverUrl.endsWith('/')) { + serverUrl = serverUrl.substring(0, serverUrl.length - 1); + } + + final videoId = item.id; + final streamUrl = + "$serverUrl/Videos/$videoId/stream?static=true&api_key=$apiKey"; + + // Subtitles + final currentSub = item.streamModel?.currentSubStream; + final subsPath = + (currentSub != null && currentSub.url != null) ? currentSub.url! : ""; + + // Start position (Energy Player uses seconds) + final startTime = (item.userData.playbackPositionTicks) ~/ 10000000; + + // Use proper encoding for individual parameters (no manual quotes needed) + final energyUrl = 'energyplayer:launch=media' + '&path=${Uri.encodeComponent(streamUrl)}' + '&subsPath=${Uri.encodeComponent(subsPath)}' + '&startTime=$startTime'; + + final uri = Uri.parse(energyUrl); + if (await canLaunchUrl(uri)) { + await launchUrl(uri); + } else { + await openStore(); + } + } +} diff --git a/lib/util/item_base_model/play_item_helpers.dart b/lib/util/item_base_model/play_item_helpers.dart index 63989110e..2b325f2f4 100644 --- a/lib/util/item_base_model/play_item_helpers.dart +++ b/lib/util/item_base_model/play_item_helpers.dart @@ -31,7 +31,11 @@ import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/list_extensions.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/refresh_state.dart'; +import 'package:fladder/util/external_player_helper.dart'; import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart'; +import 'package:fladder/widgets/shared/dv_player_selection_dialog.dart'; +import 'package:fladder/providers/settings/video_player_settings_provider.dart'; +import 'package:fladder/models/settings/video_player_settings.dart'; extension BookBaseModelExtension on BookModel? { Future play( @@ -198,6 +202,27 @@ extension ItemBaseModelExtensions on ItemBaseModel? { }) async { if (itemModel == null) return; + // DV selection on Windows logic + if (ExternalPlayerHelper.canShowEnergyPlayer(itemModel)) { + final settings = ref.read(videoPlayerSettingsProvider); + DVPlayerChoice choice = settings.dvPlayerChoice; + + if (choice == DVPlayerChoice.ask) { + final result = await showDialog( + context: context, + builder: (context) => const DVPlayerSelectionDialog(), + ); + + if (result == null) return; + choice = result; + } + + if (choice == DVPlayerChoice.energyPlayer) { + await ExternalPlayerHelper.launchEnergyPlayer(ref, itemModel); + return; + } + } + await ref.read(videoPlayerProvider.notifier).init(); final op = CancelableOperation.fromFuture(ref.read(playbackModelHelper).createPlaybackModel( diff --git a/lib/widgets/shared/dv_player_selection_dialog.dart b/lib/widgets/shared/dv_player_selection_dialog.dart new file mode 100644 index 000000000..0ccad5d33 --- /dev/null +++ b/lib/widgets/shared/dv_player_selection_dialog.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:fladder/models/settings/video_player_settings.dart'; +import 'package:fladder/providers/settings/video_player_settings_provider.dart'; +import 'package:fladder/util/external_player_helper.dart'; +import 'package:fladder/util/localization_helper.dart'; +import 'package:fladder/screens/settings/widgets/settings_message_box.dart'; + +class DVPlayerSelectionDialog extends ConsumerStatefulWidget { + const DVPlayerSelectionDialog({super.key}); + + @override + ConsumerState createState() => _DVPlayerSelectionDialogState(); +} + +class _DVPlayerSelectionDialogState extends ConsumerState { + bool _remember = false; + bool? _isInstalled; + + @override + void initState() { + super.initState(); + _checkInstallation(); + } + + Future _checkInstallation() async { + final installed = await ExternalPlayerHelper.isEnergyPlayerInstalled(); + if (mounted) { + setState(() => _isInstalled = installed); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(context.localized.dvPlayerDialogTitle), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(context.localized.dvPlayerDialogDesc), + const SizedBox(height: 12), + Text( + context.localized.dvEnableInstruction, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + const SizedBox(height: 12), + SettingsMessageBox( + context.localized.dvSyncWarning, + messageType: MessageType.warning, + ), + if (_isInstalled == false) ...[ + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.errorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Text( + context.localized.dvPlayerNotInstalled, + textAlign: TextAlign.center, + style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer), + ), + const SizedBox(height: 8), + TextButton.icon( + onPressed: () => ExternalPlayerHelper.openStore(), + icon: const Icon(Icons.download), + label: Text(context.localized.dvPlayerGetFromStore), + style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error), + ), + ], + ), + ), + ], + const SizedBox(height: 16), + CheckboxListTile( + value: _remember, + onChanged: (val) => setState(() => _remember = val ?? false), + title: Text(context.localized.dvRememberChoice), + controlAffinity: ListTileControlAffinity.leading, + contentPadding: EdgeInsets.zero, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.localized.cancel), + ), + TextButton( + onPressed: () { + if (_remember) { + ref.read(videoPlayerSettingsProvider.notifier).setDVPlayerChoice(DVPlayerChoice.internalPlayer); + } + Navigator.of(context).pop(DVPlayerChoice.internalPlayer); + }, + child: Text(context.localized.dvPlayerDisabled), + ), + FilledButton( + onPressed: () { + if (_remember) { + ref.read(videoPlayerSettingsProvider.notifier).setDVPlayerChoice(DVPlayerChoice.energyPlayer); + } + Navigator.of(context).pop(DVPlayerChoice.energyPlayer); + }, + child: Text(context.localized.dvPlayerEnergyPlayer), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 7385c79a7..0c95ae8d6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -245,10 +245,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -1257,18 +1257,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" media_kit: dependency: "direct main" description: @@ -1345,10 +1345,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -2142,10 +2142,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.10" timezone: dependency: transitive description: