Files
plezy/lib/utils/content_utils.dart
T
edde746 4f736c555b fix: make PlexMetadata type/key/title nullable for folder items
Plex API returns Directory entries without type field, causing
TypeError crash in fromJson. Made fields nullable and migrated
callers to use mediaType enum and displayTitle getter instead
of raw string access.
2026-03-14 05:40:14 +01:00

115 lines
4.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../models/plex_metadata.dart';
/// Content type constants used throughout the app
class ContentTypes {
ContentTypes._();
static const String movie = 'movie';
static const String show = 'show';
static const String season = 'season';
static const String episode = 'episode';
static const String artist = 'artist';
static const String album = 'album';
static const String track = 'track';
static const String collection = 'collection';
static const String playlist = 'playlist';
static const String clip = 'clip';
static const Set<String> musicTypes = {artist, album, track};
static const Set<String> videoTypes = {movie, show, season, episode};
static const Set<String> playableTypes = {movie, episode, clip, track};
}
/// Utility class for content type checking and filtering
class ContentTypeHelper {
ContentTypeHelper._();
/// Checks if the given type is music content (artist, album, or track)
static bool isMusicContent(String type) => ContentTypes.musicTypes.contains(type.toLowerCase());
/// Checks if the given type is video content (movie, show, episode, or season)
static bool isVideoContent(String type) => ContentTypes.videoTypes.contains(type.toLowerCase());
/// Checks if the given library is a music library
static bool isMusicLibrary(dynamic lib) {
if (lib == null) return false;
try {
final type = (lib as dynamic).type as String?;
return type?.toLowerCase() == ContentTypes.artist;
} catch (e) {
return false;
}
}
/// Filters out music content from a list of items
static List<T> filterOutMusic<T>(List<T> items, String Function(T) getType) {
return items.where((item) => !isMusicContent(getType(item))).toList();
}
/// Returns the appropriate icon for a given library type
static IconData getLibraryIcon(String type) {
switch (type.toLowerCase()) {
case ContentTypes.movie:
return Symbols.movie_rounded;
case ContentTypes.show:
return Symbols.tv_rounded;
case ContentTypes.artist:
return Symbols.music_note_rounded;
case 'photo':
return Symbols.photo_rounded;
default:
return Symbols.folder_rounded;
}
}
}
/// Utility function to format content ratings by removing country prefixes
String formatContentRating(String? contentRating) {
if (contentRating == null || contentRating.isEmpty) {
return '';
}
// Remove common country prefixes like "gb/", "us/", "de/", etc.
// The pattern matches: lowercase letters followed by a forward slash
final regex = RegExp(r'^[a-z]{2,3}/(.+)$', caseSensitive: false);
final match = regex.firstMatch(contentRating);
if (match != null && match.groupCount >= 1) {
return match.group(1) ?? contentRating;
}
return contentRating;
}
/// Extension on PlexMetadata for type checking convenience methods
extension PlexMetadataType on PlexMetadata {
String get _lowerType => type?.toLowerCase() ?? '';
bool get isShow => _lowerType == ContentTypes.show;
bool get isMovie => _lowerType == ContentTypes.movie;
bool get isSeason => _lowerType == ContentTypes.season;
bool get isEpisode => _lowerType == ContentTypes.episode;
bool get isArtist => _lowerType == ContentTypes.artist;
bool get isAlbum => _lowerType == ContentTypes.album;
bool get isTrack => _lowerType == ContentTypes.track;
bool get isCollection => _lowerType == ContentTypes.collection;
bool get isPlaylist => _lowerType == ContentTypes.playlist;
bool get isClip => _lowerType == ContentTypes.clip;
bool get isMusicContent => ContentTypes.musicTypes.contains(_lowerType);
bool get isVideoContent => ContentTypes.videoTypes.contains(_lowerType);
/// Whether this episode should have spoiler protection applied.
/// True when the item is an unwatched episode watched less than 50%.
bool get shouldHideSpoiler {
if (!isEpisode) return false;
if (isWatched) return false;
if (viewOffset != null && viewOffset! > 0 && duration != null && duration! > 0) {
return viewOffset! / duration! < 0.5;
}
return true;
}
}