From a34f8fe2f4faeea3d29b0d1e33073c2b9e128303 Mon Sep 17 00:00:00 2001 From: PartyDonut <42371342+PartyDonut@users.noreply.github.com> Date: Sun, 16 Feb 2025 11:58:53 +0100 Subject: [PATCH] fix: Improve null handling when parsing image urls (#228) Co-authored-by: PartyDonut --- lib/models/items/images_models.dart | 171 ++++++++++++----------- lib/models/items/item_shared_models.dart | 106 +------------- lib/providers/image_provider.dart | 11 +- 3 files changed, 100 insertions(+), 188 deletions(-) diff --git a/lib/models/items/images_models.dart b/lib/models/items/images_models.dart index 6290c71..4ad9a24 100644 --- a/lib/models/items/images_models.dart +++ b/lib/models/items/images_models.dart @@ -12,7 +12,6 @@ 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/custom_cache_manager.dart'; -import 'package:fladder/util/jelly_id.dart'; class ImagesData { final ImageData? primary; @@ -35,74 +34,80 @@ class ImagesData { ImageData? get randomBackDrop => (backDrop?..shuffle())?.firstOrNull ?? primary; - factory ImagesData.fromBaseItem( + static 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, + int quality = 95, }) { + final itemid = item.id; + if (itemid == null) return null; + final imageProvider = ref.read(imageUtilityProvider); 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, - ), + ? imageProvider.getItemsOrigImageUrl( + itemid, + type: enums.ImageType.primary, + ) + : imageProvider.getItemsImageUrl( + itemid, + 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? ?? "", + hash: item.imageBlurHashes?.primary?[item.imageTags?['Primary']] ?? "", ) : 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, + ? imageProvider.getItemsOrigImageUrl( + itemid, + type: enums.ImageType.logo, ) - : ref.read(imageUtilityProvider).getBackdropImage( - (item.id!), - index, - backdrop, - maxHeight: backDrop.height.toInt(), - maxWidth: backDrop.width.toInt(), + : imageProvider.getItemsImageUrl( + itemid, + type: enums.ImageType.logo, + maxHeight: logo.height.toInt(), + maxWidth: logo.width.toInt(), quality: quality, ), - key: backdrop, - hash: item.imageBlurHashes?.backdrop?[backdrop] ?? jellyId, - ); - return image; - }, - ).toList(), + key: item.imageTags?['Logo'], + hash: item.imageBlurHashes?.logo?[item.imageTags?['Logo']] ?? "") + : null, + backDrop: (item.backdropImageTags ?? []) + .mapIndexed( + (index, backdrop) { + final image = ImageData( + path: getOriginalSize + ? imageProvider.getBackdropOrigImage( + itemid, + index, + backdrop, + ) + : imageProvider.getBackdropImage( + itemid, + index, + backdrop, + maxHeight: backDrop.height.toInt(), + maxWidth: backDrop.width.toInt(), + quality: quality, + ), + key: backdrop, + hash: item.imageBlurHashes?.backdrop?[backdrop] ?? "", + ); + return image; + }, + ) + .nonNulls + .toList(), ); return newImgesData; } @@ -113,51 +118,59 @@ class ImagesData { Size backDrop = const Size(2000, 2000), Size logo = const Size(1000, 1000), Size primary = const Size(600, 600), - int quality = 90, + int quality = 95, }) { if (item.seriesId == null && item.parentId == null) return null; + + final imageProvider = ref.read(imageUtilityProvider); + 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, - ), + path: imageProvider.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? ?? "") + hash: item.imageBlurHashes?.primary?[item.seriesPrimaryImageTag] ?? "") : 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, - ), + path: imageProvider.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? ?? "") + hash: item.imageBlurHashes?.logo?[item.parentLogoImageTag] ?? "") : null, - backDrop: (item.backdropImageTags ?? []).mapIndexed( - (index, backdrop) { - final image = ImageData( - path: ref.read(imageUtilityProvider).getBackdropImage( - ((item.seriesId ?? item.parentId)!), + backDrop: (item.backdropImageTags ?? []) + .mapIndexed( + (index, backdrop) { + final itemId = item.seriesId ?? item.parentId; + if (itemId == null) return null; + final image = ImageData( + path: imageProvider.getBackdropImage( + itemId, index, backdrop, maxHeight: backDrop.height.toInt(), maxWidth: backDrop.width.toInt(), quality: quality, ), - key: backdrop, - hash: item.imageBlurHashes?.backdrop?[backdrop], - ); - return image; - }, - ).toList(), + key: backdrop, + hash: item.imageBlurHashes?.backdrop?[backdrop] ?? "", + ); + return image; + }, + ) + .nonNulls + .toList(), ); return newImgesData; } @@ -168,7 +181,7 @@ class ImagesData { Size backDrop = const Size(2000, 2000), Size logo = const Size(1000, 1000), Size primary = const Size(2000, 2000), - int quality = 90, + int quality = 95, }) { return ImagesData( primary: (item.primaryImageTag != null && item.imageBlurHashes != null) @@ -181,7 +194,7 @@ class ImagesData { quality: quality, ), key: item.primaryImageTag ?? "", - hash: item.imageBlurHashes?.primary?[item.primaryImageTag] as String? ?? jellyId) + hash: item.imageBlurHashes?.primary?[item.primaryImageTag] ?? '') : null, logo: null, backDrop: null, diff --git a/lib/models/items/item_shared_models.dart b/lib/models/items/item_shared_models.dart index a304821..45cc175 100644 --- a/lib/models/items/item_shared_models.dart +++ b/lib/models/items/item_shared_models.dart @@ -187,7 +187,7 @@ class Person { return Person( id: item.id ?? "", name: item.name ?? "", - image: ImagesData.fromBaseItem(item, ref).primary, + image: ImagesData.fromBaseItem(item, ref)?.primary, ); } @@ -274,107 +274,3 @@ class Studio { @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 toMap() { -// return { -// 'isFavourite': isFavourite, -// 'playCount': playCount, -// 'unPlayedItemCount': unPlayedItemCount, -// 'playbackPositionTicks': playbackPositionTicks, -// 'progress': progress, -// 'played': played, -// }; -// } - -// factory UserData.fromMap(Map 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); - -// @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; -// } -// } diff --git a/lib/providers/image_provider.dart b/lib/providers/image_provider.dart index 492d469..8b22e7f 100644 --- a/lib/providers/image_provider.dart +++ b/lib/providers/image_provider.dart @@ -1,8 +1,8 @@ -import 'package:fladder/providers/auth_provider.dart'; -import 'package:fladder/providers/user_provider.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; +import 'package:fladder/providers/auth_provider.dart'; +import 'package:fladder/providers/user_provider.dart'; const _defaultHeight = 576; const _defaultWidth = 384; @@ -26,12 +26,14 @@ class ImageNotifier { return Uri.decodeFull("$currentServerUrl/Users/$id/Images/${ImageType.primary.value}"); } - String getItemsImageUrl(String itemId, + String getItemsImageUrl(String? itemId, {ImageType type = ImageType.primary, int maxHeight = _defaultHeight, int maxWidth = _defaultWidth, int quality = _defaultQuality}) { try { + if (itemId == null) return ""; + return Uri.decodeFull( "$currentServerUrl/Items/$itemId/Images/${type.value}?fillHeight=$maxHeight&fillWidth=$maxWidth&quality=$quality"); } catch (e) { @@ -39,8 +41,9 @@ class ImageNotifier { } } - String getItemsOrigImageUrl(String itemId, {ImageType type = ImageType.primary}) { + String getItemsOrigImageUrl(String? itemId, {ImageType type = ImageType.primary}) { try { + if (itemId == null) return ""; return Uri.decodeFull("$currentServerUrl/Items/$itemId/Images/${type.value}"); } catch (e) { return "";