Merge the deduplication and dead-code removal pass
Consolidates duplicated logic behind shared implementations — paginated grid tabs, focus chrome, cached remote stores, sheet selection columns, the server artifact store and a test fixture layer — and removes code that had become unreachable. Net reduction of about 5,500 lines with no behaviour change. Where a fix had landed separately in code that moved into a shared helper, the fix was re-applied inside the helper rather than left behind in the copy that went away.
This commit is contained in:
@@ -268,7 +268,13 @@ PageRoute<bool> mediaDetailRoute({
|
||||
}
|
||||
|
||||
class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
with WatchStateAware, DeletionAware, MountedSetStateMixin, ServerBoundMediaMixin, RouteAware {
|
||||
with
|
||||
WatchStateAware,
|
||||
DeletionAware,
|
||||
DeletionMirrorsWatchState,
|
||||
MountedSetStateMixin,
|
||||
ServerBoundMediaMixin,
|
||||
RouteAware {
|
||||
/// Public input alias — used as the live source of truth until the detail
|
||||
/// fetch returns. Holds backend-neutral [MediaItem] data.
|
||||
MediaItem get _metadata => _fullMetadata ?? widget.metadata;
|
||||
@@ -394,7 +400,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
@override
|
||||
bool get isServerBoundOffline => widget.isOffline;
|
||||
|
||||
// WatchStateAware: watch the show/movie and all season/episode ratingKeys
|
||||
// WatchStateAware: watch the show/movie and all season/episode ratingKeys.
|
||||
// DeletionMirrorsWatchState reuses these three getters for deletion events —
|
||||
// the same items are on screen either way.
|
||||
@override
|
||||
Set<String>? get watchedIds {
|
||||
final keys = <String>{_metadata.id};
|
||||
@@ -534,36 +542,6 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Set<String>? get deletionIds {
|
||||
final keys = <String>{_metadata.id};
|
||||
for (final season in _seasons) {
|
||||
keys.add(season.id);
|
||||
}
|
||||
for (final ep in _episodes) {
|
||||
keys.add(ep.id);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
String? get deletionServerId => serverBoundServerId;
|
||||
|
||||
@override
|
||||
Set<String>? get deletionGlobalKeys {
|
||||
final serverId = serverBoundServerId;
|
||||
if (serverId == null) return null;
|
||||
|
||||
final keys = <String>{toServerBoundGlobalKey(_metadata.id, serverId: ServerId(serverId))};
|
||||
for (final season in _seasons) {
|
||||
keys.add(toServerBoundGlobalKey(season.id, serverId: ServerId(season.serverId ?? serverId)));
|
||||
}
|
||||
for (final ep in _episodes) {
|
||||
keys.add(toServerBoundGlobalKey(ep.id, serverId: ServerId(ep.serverId ?? serverId)));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDeletionEvent(DeletionEvent event) {
|
||||
// Download-only deletions should only remove items when viewing offline content
|
||||
@@ -3592,6 +3570,42 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/// The ordered metadata fields the TV detail line renders and its announcement reads,
|
||||
/// built through [text] for plain fields and [rating] for the rating slot.
|
||||
List<T> _tvDetailMetadataParts<T extends Object>(
|
||||
MediaItem metadata, {
|
||||
required T Function(String value) text,
|
||||
required T? Function(MediaItem item) rating,
|
||||
}) {
|
||||
final lineMetadata = _tvDetailFocusedEpisode.value ?? metadata;
|
||||
final parts = <T>[];
|
||||
|
||||
void add(T? part) {
|
||||
if (part != null) parts.add(part);
|
||||
}
|
||||
|
||||
final episodeLabel = formatSeasonEpisodeLabel(lineMetadata.parentIndex, lineMetadata.index);
|
||||
if (lineMetadata.isEpisode && episodeLabel != null) add(text(episodeLabel));
|
||||
if (lineMetadata.isMovie) {
|
||||
add(text(t.discover.movie));
|
||||
} else if (lineMetadata.isShow) {
|
||||
add(text(t.discover.tvShow));
|
||||
}
|
||||
add(rating(lineMetadata));
|
||||
if (lineMetadata.contentRating != null) add(text(formatContentRating(lineMetadata.contentRating!)));
|
||||
if (lineMetadata.durationMs != null) add(text(formatDurationTextual(lineMetadata.durationMs!)));
|
||||
if (lineMetadata.isEpisode && lineMetadata.originallyAvailableAt != null) {
|
||||
add(text(formatAbbreviatedDate(lineMetadata.originallyAvailableAt!)));
|
||||
} else if (lineMetadata.year != null) {
|
||||
add(text(lineMetadata.year.toString()));
|
||||
}
|
||||
for (final label in buildMediaQualityLabels(lineMetadata)) {
|
||||
add(text(label));
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
String _tvDetailInformationSemanticLabel(
|
||||
MediaItem metadata, {
|
||||
required String? description,
|
||||
@@ -3608,23 +3622,13 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
add(metadata.displayTitle);
|
||||
if (!identical(lineMetadata, metadata)) add(lineMetadata.displayTitle);
|
||||
|
||||
final episodeLabel = formatSeasonEpisodeLabel(lineMetadata.parentIndex, lineMetadata.index);
|
||||
if (lineMetadata.isEpisode) add(episodeLabel);
|
||||
if (lineMetadata.isMovie) {
|
||||
add(t.discover.movie);
|
||||
} else if (lineMetadata.isShow) {
|
||||
add(t.discover.tvShow);
|
||||
}
|
||||
add(MediaRatingBadge.semanticLabelForMedia(lineMetadata, fallbackItem: metadata));
|
||||
if (lineMetadata.contentRating != null) add(formatContentRating(lineMetadata.contentRating!));
|
||||
if (lineMetadata.durationMs != null) add(formatDurationTextual(lineMetadata.durationMs!));
|
||||
if (lineMetadata.isEpisode && lineMetadata.originallyAvailableAt != null) {
|
||||
add(formatAbbreviatedDate(lineMetadata.originallyAvailableAt!));
|
||||
} else if (lineMetadata.year != null) {
|
||||
add(lineMetadata.year.toString());
|
||||
}
|
||||
for (final label in buildMediaQualityLabels(lineMetadata)) {
|
||||
add(label);
|
||||
final fields = _tvDetailMetadataParts<String>(
|
||||
metadata,
|
||||
text: (value) => value,
|
||||
rating: (item) => MediaRatingBadge.semanticLabelForMedia(item, fallbackItem: metadata),
|
||||
);
|
||||
for (final field in fields) {
|
||||
add(field);
|
||||
}
|
||||
if (genres.isNotEmpty) add(genres.join(', '));
|
||||
add(description);
|
||||
@@ -3681,60 +3685,32 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
|
||||
}
|
||||
|
||||
Widget _buildTvDetailMetadataLine(BuildContext context, MediaItem metadata, double scale) {
|
||||
final lineMetadata = _tvDetailFocusedEpisode.value ?? metadata;
|
||||
final episodeLabel = formatSeasonEpisodeLabel(lineMetadata.parentIndex, lineMetadata.index);
|
||||
final qualityLabels = buildMediaQualityLabels(lineMetadata);
|
||||
final textStyle = TextStyle(
|
||||
color: _tvDetailForegroundColor(context),
|
||||
fontSize: 18 * scale,
|
||||
fontWeight: .w700,
|
||||
letterSpacing: 0.1,
|
||||
);
|
||||
final children = <Widget>[];
|
||||
|
||||
void addSeparator() {
|
||||
if (children.isNotEmpty) children.add(Text(' • ', maxLines: 1, style: textStyle));
|
||||
}
|
||||
|
||||
void addTextPart(String text) {
|
||||
addSeparator();
|
||||
children.add(Text(text, maxLines: 1, style: textStyle));
|
||||
}
|
||||
|
||||
void addWidgetPart(Widget widget) {
|
||||
addSeparator();
|
||||
children.add(widget);
|
||||
}
|
||||
|
||||
if (lineMetadata.isEpisode && episodeLabel != null) addTextPart(episodeLabel);
|
||||
if (lineMetadata.isMovie) {
|
||||
addTextPart(t.discover.movie);
|
||||
} else if (lineMetadata.isShow) {
|
||||
addTextPart(t.discover.tvShow);
|
||||
}
|
||||
final ratingBadge = MediaRatingBadge.inlineForMedia(
|
||||
item: lineMetadata,
|
||||
fallbackItem: metadata,
|
||||
foregroundColor: textStyle.color,
|
||||
iconSize: textStyle.fontSize,
|
||||
spacing: 4 * scale,
|
||||
textStyle: textStyle,
|
||||
final fields = _tvDetailMetadataParts<Widget>(
|
||||
metadata,
|
||||
text: (value) => Text(value, maxLines: 1, style: textStyle),
|
||||
rating: (item) => MediaRatingBadge.inlineForMedia(
|
||||
item: item,
|
||||
fallbackItem: metadata,
|
||||
foregroundColor: textStyle.color,
|
||||
iconSize: textStyle.fontSize,
|
||||
spacing: 4 * scale,
|
||||
textStyle: textStyle,
|
||||
),
|
||||
);
|
||||
if (ratingBadge != null) {
|
||||
addWidgetPart(ratingBadge);
|
||||
}
|
||||
if (lineMetadata.contentRating != null) addTextPart(formatContentRating(lineMetadata.contentRating!));
|
||||
if (lineMetadata.durationMs != null) addTextPart(formatDurationTextual(lineMetadata.durationMs!));
|
||||
if (lineMetadata.isEpisode && lineMetadata.originallyAvailableAt != null) {
|
||||
addTextPart(formatAbbreviatedDate(lineMetadata.originallyAvailableAt!));
|
||||
} else if (lineMetadata.year != null) {
|
||||
addTextPart(lineMetadata.year.toString());
|
||||
}
|
||||
for (final label in qualityLabels) {
|
||||
addTextPart(label);
|
||||
}
|
||||
|
||||
if (children.isEmpty) return const SizedBox.shrink();
|
||||
if (fields.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
final children = <Widget>[];
|
||||
for (final field in fields) {
|
||||
if (children.isNotEmpty) children.add(Text(' • ', maxLines: 1, style: textStyle));
|
||||
children.add(field);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
|
||||
Reference in New Issue
Block a user