@@ -23,6 +23,7 @@ import '../exceptions/media_server_exceptions.dart';
|
||||
import '../media/media_backend.dart';
|
||||
import '../media/media_hub.dart';
|
||||
import '../utils/provider_extensions.dart';
|
||||
import '../utils/plex_season_display.dart';
|
||||
import '../media/media_item.dart';
|
||||
import '../media/media_item_types.dart';
|
||||
import '../media/media_kind.dart';
|
||||
@@ -1083,17 +1084,15 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
.map((season) => season.copyWith(serverId: serverId, serverName: _metadata.serverName ?? season.serverName))
|
||||
.toList();
|
||||
|
||||
// Plex's flattenSeasons modes: 1 = always, 2 = single-season only.
|
||||
// Jellyfin falls through to "flatten when there's a single season".
|
||||
// Plex can override the library season mode per show; Jellyfin falls
|
||||
// through to "flatten when there's a single season".
|
||||
bool shouldShowEpisodesDirectly;
|
||||
if (client is PlexClient) {
|
||||
const flattenSeasonsAlways = 1;
|
||||
const flattenSeasonsSingleSeason = 2;
|
||||
final flattenSeasons = int.tryParse(prefs['flattenSeasons']?.toString() ?? '');
|
||||
final isAlways = flattenSeasons == flattenSeasonsAlways;
|
||||
final isSingleSeason = flattenSeasons == flattenSeasonsSingleSeason;
|
||||
shouldShowEpisodesDirectly =
|
||||
isAlways || seasonsWithServerId.isEmpty || (isSingleSeason && seasonsWithServerId.length == 1);
|
||||
shouldShowEpisodesDirectly = shouldShowPlexEpisodesDirectly(
|
||||
show: _metadata,
|
||||
seasons: seasonsWithServerId,
|
||||
libraryPrefs: prefs,
|
||||
);
|
||||
} else {
|
||||
shouldShowEpisodesDirectly = seasonsWithServerId.length <= 1;
|
||||
}
|
||||
|
||||
@@ -465,6 +465,10 @@ class PlexMetadataDto {
|
||||
final String? serverName;
|
||||
final String? clearLogo;
|
||||
final String? backgroundSquare;
|
||||
@JsonKey(fromJson: flexibleBoolNullable)
|
||||
final bool? skipChildren;
|
||||
@JsonKey(fromJson: flexibleInt)
|
||||
final int? flattenSeasons;
|
||||
|
||||
const PlexMetadataDto({
|
||||
required this.ratingKey,
|
||||
@@ -532,6 +536,8 @@ class PlexMetadataDto {
|
||||
this.serverName,
|
||||
this.clearLogo,
|
||||
this.backgroundSquare,
|
||||
this.skipChildren,
|
||||
this.flattenSeasons,
|
||||
});
|
||||
|
||||
factory PlexMetadataDto.fromJson(Map<String, dynamic> rawJson) {
|
||||
@@ -659,6 +665,8 @@ class PlexMetadataDto {
|
||||
String? serverName,
|
||||
String? clearLogo,
|
||||
String? backgroundSquare,
|
||||
bool? skipChildren,
|
||||
int? flattenSeasons,
|
||||
}) {
|
||||
return PlexMetadataDto(
|
||||
ratingKey: ratingKey ?? this.ratingKey,
|
||||
@@ -726,10 +734,20 @@ class PlexMetadataDto {
|
||||
serverName: serverName ?? this.serverName,
|
||||
clearLogo: clearLogo ?? this.clearLogo,
|
||||
backgroundSquare: backgroundSquare ?? this.backgroundSquare,
|
||||
skipChildren: skipChildren ?? this.skipChildren,
|
||||
flattenSeasons: flattenSeasons ?? this.flattenSeasons,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object?>? _rawMetadata(PlexMetadataDto dto) {
|
||||
final raw = <String, Object?>{};
|
||||
if (dto.key != null) raw['key'] = dto.key;
|
||||
if (dto.skipChildren != null) raw['skipChildren'] = dto.skipChildren;
|
||||
if (dto.flattenSeasons != null) raw['flattenSeasons'] = dto.flattenSeasons;
|
||||
return raw.isEmpty ? null : raw;
|
||||
}
|
||||
|
||||
/// Pure JSON/DTO→neutral-type mappers for Plex. Mirrors [JellyfinMappers].
|
||||
///
|
||||
/// Methods come in two flavours:
|
||||
@@ -827,7 +845,7 @@ class PlexMappers {
|
||||
extraType: dto.extraType,
|
||||
serverId: dto.serverId,
|
||||
serverName: dto.serverName,
|
||||
raw: dto.key != null ? {'key': dto.key} : null,
|
||||
raw: _rawMetadata(dto),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,8 @@ PlexMetadataDto _$PlexMetadataDtoFromJson(Map<String, dynamic> json) =>
|
||||
primaryExtraKey: json['primaryExtraKey'] as String?,
|
||||
clearLogo: json['clearLogo'] as String?,
|
||||
backgroundSquare: json['backgroundSquare'] as String?,
|
||||
skipChildren: flexibleBoolNullable(json['skipChildren']),
|
||||
flattenSeasons: flexibleInt(json['flattenSeasons']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PlexMetadataDtoToJson(PlexMetadataDto instance) =>
|
||||
@@ -201,4 +203,6 @@ Map<String, dynamic> _$PlexMetadataDtoToJson(PlexMetadataDto instance) =>
|
||||
'primaryExtraKey': ?instance.primaryExtraKey,
|
||||
'clearLogo': ?instance.clearLogo,
|
||||
'backgroundSquare': ?instance.backgroundSquare,
|
||||
'skipChildren': ?instance.skipChildren,
|
||||
'flattenSeasons': ?instance.flattenSeasons,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import '../media/media_item.dart';
|
||||
import 'json_utils.dart';
|
||||
|
||||
const _plexFlattenSeasonsShow = 0;
|
||||
const _plexFlattenSeasonsHide = 1;
|
||||
const _plexFlattenSeasonsSingleSeason = 2;
|
||||
|
||||
bool shouldShowPlexEpisodesDirectly({
|
||||
required MediaItem show,
|
||||
required List<MediaItem> seasons,
|
||||
required Map<String, dynamic> libraryPrefs,
|
||||
}) {
|
||||
final showOverride = flexibleInt(show.raw?['flattenSeasons']);
|
||||
if (showOverride == _plexFlattenSeasonsHide) return true;
|
||||
if (showOverride == _plexFlattenSeasonsShow) return false;
|
||||
|
||||
if (flexibleBool(show.raw?['skipChildren'])) return true;
|
||||
|
||||
final libraryFlattenSeasons = flexibleInt(libraryPrefs['flattenSeasons']);
|
||||
return libraryFlattenSeasons == _plexFlattenSeasonsHide ||
|
||||
seasons.isEmpty ||
|
||||
(libraryFlattenSeasons == _plexFlattenSeasonsSingleSeason && seasons.length == 1);
|
||||
}
|
||||
@@ -140,6 +140,23 @@ void main() {
|
||||
expect(item.isWatched, isTrue);
|
||||
});
|
||||
|
||||
test('show preserves Plex season display flags in raw metadata', () {
|
||||
final json = {
|
||||
'ratingKey': '500',
|
||||
'key': '/library/metadata/500',
|
||||
'type': 'show',
|
||||
'title': 'Breaking Bad',
|
||||
'skipChildren': '1',
|
||||
'flattenSeasons': '1',
|
||||
};
|
||||
|
||||
final item = PlexMappers.mediaItemFromJson(json, serverId: _serverId);
|
||||
|
||||
expect(item.raw, containsPair('key', '/library/metadata/500'));
|
||||
expect(item.raw, containsPair('skipChildren', true));
|
||||
expect(item.raw, containsPair('flattenSeasons', 1));
|
||||
});
|
||||
|
||||
test('season carries parent (show) reference', () {
|
||||
final json = {
|
||||
'ratingKey': '510',
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/media/media_backend.dart';
|
||||
import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/utils/plex_season_display.dart';
|
||||
|
||||
void main() {
|
||||
group('shouldShowPlexEpisodesDirectly', () {
|
||||
test('per-show hide override shows episodes directly', () {
|
||||
final show = _show(raw: {'flattenSeasons': 1});
|
||||
|
||||
expect(
|
||||
shouldShowPlexEpisodesDirectly(show: show, seasons: [_season('1'), _season('2')], libraryPrefs: {}),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('per-show show override keeps seasons despite hidden library default', () {
|
||||
final show = _show(raw: {'flattenSeasons': 0});
|
||||
|
||||
expect(
|
||||
shouldShowPlexEpisodesDirectly(
|
||||
show: show,
|
||||
seasons: [_season('1'), _season('2')],
|
||||
libraryPrefs: {'flattenSeasons': 1},
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('skipChildren hides seasons when no explicit show override exists', () {
|
||||
final show = _show(raw: {'skipChildren': true});
|
||||
|
||||
expect(
|
||||
shouldShowPlexEpisodesDirectly(show: show, seasons: [_season('1'), _season('2')], libraryPrefs: {}),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('falls back to library single-season mode', () {
|
||||
final show = _show();
|
||||
|
||||
expect(
|
||||
shouldShowPlexEpisodesDirectly(show: show, seasons: [_season('1')], libraryPrefs: {'flattenSeasons': '2'}),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
shouldShowPlexEpisodesDirectly(
|
||||
show: show,
|
||||
seasons: [_season('1'), _season('2')],
|
||||
libraryPrefs: {'flattenSeasons': '2'},
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
MediaItem _show({Map<String, Object?>? raw}) {
|
||||
return MediaItem(id: 'show', backend: MediaBackend.plex, kind: MediaKind.show, raw: raw);
|
||||
}
|
||||
|
||||
MediaItem _season(String id) {
|
||||
return MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.season);
|
||||
}
|
||||
Reference in New Issue
Block a user