Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Redesign] Add more BaseItemDtoTypes #749

Merged
merged 3 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions lib/models/finamp_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ class FinampSettings {
this.showArtistChipImage = _showArtistChipImage,
this.trackOfflineFavorites = _trackOfflineFavoritesDefault,
this.showProgressOnNowPlayingBar = _showProgressOnNowPlayingBarDefault,
this.startInstantMixForIndividualTracks = _startInstantMixForIndividualTracksDefault,
this.startInstantMixForIndividualTracks =
_startInstantMixForIndividualTracksDefault,
});

@HiveField(0, defaultValue: _isOfflineDefault)
Expand Down Expand Up @@ -821,22 +822,21 @@ class DownloadStub {
case DownloadItemType.collection:
return baseItem != null &&
BaseItemDtoType.fromItem(baseItem!) == baseItemType &&
baseItemType != BaseItemDtoType.song &&
baseItemType != BaseItemDtoType.unknown;
baseItemType.downloadType == DownloadItemType.collection &&
baseItemType != BaseItemDtoType.noItem;
case DownloadItemType.song:
return baseItemType == BaseItemDtoType.song &&
return baseItemType.downloadType == DownloadItemType.song &&
baseItem != null &&
BaseItemDtoType.fromItem(baseItem!) == baseItemType;
case DownloadItemType.image:
return baseItem != null;
case DownloadItemType.finampCollection:
// TODO create an enum or somthing for this if more custom collections happen
return baseItem == null &&
baseItemType == BaseItemDtoType.unknown &&
baseItemType == BaseItemDtoType.noItem &&
finampCollection != null;
case DownloadItemType.anchor:
return baseItem == null &&
baseItemType == BaseItemDtoType.unknown &&
baseItemType == BaseItemDtoType.noItem &&
id == "Anchor";
}
}
Expand Down Expand Up @@ -873,7 +873,7 @@ class DownloadStub {
jsonItem: null,
type: type,
name: name ?? "Unlocalized $id",
baseItemType: BaseItemDtoType.unknown);
baseItemType: BaseItemDtoType.noItem);
}

factory DownloadStub.fromFinampCollection(FinampCollection collection) {
Expand All @@ -891,7 +891,7 @@ class DownloadStub {
jsonItem: jsonEncode(collection.toJson()),
type: DownloadItemType.finampCollection,
name: name ?? "Unlocalized Finamp Collection $id",
baseItemType: BaseItemDtoType.unknown);
baseItemType: BaseItemDtoType.noItem);
}

/// The integer iD used as a database key by Isar
Expand Down Expand Up @@ -1231,28 +1231,52 @@ enum DownloadItemStatus {
/// The type of a BaseItemDto as determined from its type field.
/// Enumerated by Isar, do not modify order or delete existing entries
enum BaseItemDtoType {
unknown(null, true, null),
album("MusicAlbum", false, [song]),
artist("MusicArtist", true, [album, song]),
playlist("Playlist", true, [song]),
genre("MusicGenre", true, [album, song]),
song("Audio", false, []),
library("CollectionFolder", true, [album, song]),
folder("Folder", true, null),
musicVideo("MusicVideo", false, []);

const BaseItemDtoType(this.idString, this.expectChanges, this.childTypes);
noItem(null, true, null, null),
album("MusicAlbum", false, [song], DownloadItemType.collection),
artist("MusicArtist", true, [album, song], DownloadItemType.collection),
playlist("Playlist", true, [song], DownloadItemType.collection),
genre("MusicGenre", true, [album, song], DownloadItemType.collection),
song("Audio", false, [], DownloadItemType.song),
library("CollectionFolder", true, [album, song], DownloadItemType.collection),
folder("Folder", true, null, DownloadItemType.collection),
musicVideo("MusicVideo", false, [], DownloadItemType.song),
audioBook("AudioBook", false, [], DownloadItemType.song),
tvEpisode("Episode", false, [], DownloadItemType.song),
video("Video", false, [], DownloadItemType.song),
movie("Movie", false, [], DownloadItemType.song),
trailer("Trailer", false, [], DownloadItemType.song),
unknown(null, true, null, DownloadItemType.collection);

// All possible types in Jellyfin as of 10.9:
//"AggregateFolder" "Audio" "AudioBook" "BasePluginFolder" "Book" "BoxSet"
// "Channel" "ChannelFolderItem" "CollectionFolder" "Episode" "Folder" "Genre"
// "ManualPlaylistsFolder" "Movie" "LiveTvChannel" "LiveTvProgram" "MusicAlbum"
// "MusicArtist" "MusicGenre" "MusicVideo" "Person" "Photo" "PhotoAlbum" "Playlist"
// "PlaylistsFolder" "Program" "Recording" "Season" "Series" "Studio" "Trailer" "TvChannel"
// "TvProgram" "UserRootFolder" "UserView" "Video" "Year"

const BaseItemDtoType(
this.idString, this.expectChanges, this.childTypes, this.downloadType);

final String? idString;
final bool expectChanges;
final List<BaseItemDtoType>? childTypes;
final DownloadItemType? downloadType;

bool get expectChangesInChildren =>
childTypes?.any((x) => x.expectChanges) ?? true;

// BaseItemDto types that we handle like songs have been handled by returning
// the actual song type. This may be a bad ides?
static BaseItemDtoType fromItem(BaseItemDto item) {
switch (item.type) {
case "Audio":
case "AudioBook":
case "MusicVideo":
case "Episode":
case "Video":
case "Movie":
case "Trailer":
return song;
case "MusicAlbum":
return album;
Expand All @@ -1265,11 +1289,9 @@ enum BaseItemDtoType {
case "CollectionFolder":
return library;
case "Folder":
return song;
case "MusicVideo":
return song;
return folder;
default:
throw "Unknown baseItemDto type ${item.type}";
return unknown;
}
}
}
Expand Down
28 changes: 23 additions & 5 deletions lib/models/finamp_models.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions lib/models/jellyfin_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2206,7 +2206,7 @@ class BaseItemDto with RunTimeTickDuration {

/// Custom helper field to determine if the BaseItemDto was created in offline mode
bool? finampOffline;

/// Checks if the item has its own image (not inherited from a parent)
bool get hasOwnImage => imageTags?.containsKey("Primary") ?? false;

Expand Down Expand Up @@ -2302,7 +2302,7 @@ class BaseItemDto with RunTimeTickDuration {
}

DownloadItemType get downloadType =>
type! == "Audio" ? DownloadItemType.song : DownloadItemType.collection;
BaseItemDtoType.fromItem(this).downloadType!;
}

@JsonSerializable(
Expand Down
64 changes: 33 additions & 31 deletions lib/screens/blurred_player_screen_background.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class BlurredPlayerScreenBackground extends ConsumerWidget {
// Don't transition between images with identical files/urls unless
// system theme has changed
key: ValueKey(imageProvider.hashCode +
Theme.of(context).brightness.index),
Theme.of(context).brightness.hashCode),
image: imageProvider,
fit: BoxFit.cover,
fadeInDuration: const Duration(seconds: 0),
Expand All @@ -76,33 +76,47 @@ class BlurredPlayerScreenBackground extends ConsumerWidget {
if (Platform.isLinux) {
return image;
}
return CachePaint(
imageKey: imageProvider.toString(), child: image);
return CachePaint(imageKey: imageProvider, child: image);
})));
}
}

class CachePaint extends SingleChildRenderObjectWidget {
const CachePaint({super.key, super.child, required this.imageKey});

final String imageKey;
final ImageProvider imageKey;

@override
void updateRenderObject(BuildContext context, RenderCachePaint renderObject) {
renderObject.screenSize = MediaQuery.sizeOf(context);
}

@override
RenderCachePaint createRenderObject(BuildContext context) {
return RenderCachePaint(imageKey, Theme.of(context).brightness);
return RenderCachePaint(
imageKey, MediaQuery.sizeOf(context), Theme.of(context).brightness);
}
}

class RenderCachePaint extends RenderProxyBox {
RenderCachePaint(this._imageKey, this._brightness);
RenderCachePaint(this._imageKey, this._screenSize, this._brightness);

final ImageProvider _imageKey;

final String _imageKey;
int get _cacheKey => Object.hash(_imageKey, _screenSize, _brightness);

String get _cacheKey => _imageKey + size.toString() + _brightness.toString();
Size _screenSize;

final Brightness _brightness;

static final Map<String, (List<RenderCachePaint>, ui.Image?)> _cache = {};
set screenSize(Size value) {
if (value != _screenSize) {
_disposeCache();
}
_screenSize = value;
}

static final Map<int, (List<RenderCachePaint>, ui.Image?)> _cache = {};

@override
bool get isRepaintBoundary => true;
Expand All @@ -129,8 +143,10 @@ class RenderCachePaint extends RenderProxyBox {
// Save image of child to cache
final OffsetLayer offsetLayer = layer! as OffsetLayer;
Future.sync(() async {
_cache[_cacheKey] =
(_cache[_cacheKey]!.$1, await offsetLayer.toImage(offset & size));
_cache[_cacheKey] = (
_cache[_cacheKey]!.$1,
await offsetLayer.toImage(offset & _screenSize)
);
// Schedule repaint next frame because the image is lighter than the full
// child during compositing, which is more frequent than paints.
for (var element in _cache[_cacheKey]!.$1) {
Expand All @@ -140,32 +156,18 @@ class RenderCachePaint extends RenderProxyBox {
}
}

@override

/// Dispose of outdated render cache whenever widget size changes
set size(Size newSize) {
String? oldKey;
if (hasSize) {
oldKey = _cacheKey;
}
super.size = newSize;
if (_cacheKey != oldKey && oldKey != null) {
_disposeCache(oldKey);
}
}

void _disposeCache(String key) {
_cache[key]?.$1.remove(this);
if (_cache[key]?.$1.isEmpty ?? false) {
void _disposeCache() {
_cache[_cacheKey]?.$1.remove(this);
if (_cache[_cacheKey]?.$1.isEmpty ?? false) {
// If we are last user of image, dispose
_cache[key]?.$2?.dispose();
_cache.remove(key);
_cache[_cacheKey]?.$2?.dispose();
_cache.remove(_cacheKey);
}
}

@override
void dispose() {
_disposeCache(_cacheKey);
_disposeCache();
super.dispose();
}
}
5 changes: 4 additions & 1 deletion lib/services/downloads_service_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@ class DownloadsSyncService {
String? fields;
String? sortOrder;
assert(parent.type == DownloadItemType.collection);
assert(parent.baseItemType.downloadType == DownloadItemType.collection);
switch (parent.baseItemType) {
case BaseItemDtoType.playlist || BaseItemDtoType.album:
childType = DownloadItemType.song;
Expand All @@ -1279,7 +1280,9 @@ class DownloadsSyncService {
childFilter = BaseItemDtoType.album;
fields = "${_jellyfinApiData.defaultFields},SortName";
case _:
throw StateError("Unknown collection type ${parent.baseItemType}");
_syncLogger.severe(
"Unknown collection type ${parent.baseItemType} for ${parent.name}");
return Future.value([]);
}
var item = parent.baseItem!;

Expand Down