fix(music): preserve Jellyfin collection posters
This commit is contained in:
@@ -137,7 +137,15 @@ class _LibraryCollectionsTabState extends BaseLibraryTabState<MediaItem, Library
|
||||
return base.copyWith(top: base.top + _focusDecorationPadding);
|
||||
}
|
||||
|
||||
bool get _usesSquareCards => widget.library.kind.isMusic;
|
||||
bool get _usesSquareCards {
|
||||
final loaded = loadedItems.values;
|
||||
return loaded.isNotEmpty && loaded.every(_isMusicCollection);
|
||||
}
|
||||
|
||||
// Plex collection rows are library-scoped, so their container kind is a
|
||||
// safe fallback. Jellyfin BoxSets are server-wide and must opt in per item.
|
||||
bool _isMusicCollection(MediaItem item) =>
|
||||
item.kind.isMusic || (item is PlexMediaItem && widget.library.kind.isMusic);
|
||||
|
||||
Widget _buildItemsSliver(ViewMode viewMode, int density, {required bool fullCardLayout}) {
|
||||
final shape = _usesSquareCards ? CardShape.square : null;
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/theme/mono_theme.dart';
|
||||
import 'package:plezy/utils/media_server_http_client.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/widgets/focusable_media_card.dart';
|
||||
import 'package:plezy/widgets/media_card_sliver_layout.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -37,8 +38,11 @@ void main() {
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
LocaleSettings.setLocaleSync(AppLocale.en);
|
||||
TvDetectionService.debugSetAppleTVOverride(false);
|
||||
});
|
||||
|
||||
tearDown(() => TvDetectionService.debugSetAppleTVOverride(null));
|
||||
|
||||
testWidgets('music collection contents use square grid geometry and cards', (tester) async {
|
||||
final album = testMediaItem(
|
||||
id: 'album_1',
|
||||
@@ -49,6 +53,8 @@ void main() {
|
||||
serverName: 'Server',
|
||||
);
|
||||
final harness = await _createHarness([album]);
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
|
||||
|
||||
await tester.pumpWidget(
|
||||
harness.wrap(SizedBox(width: 1280, height: 720, child: CollectionDetailScreen(collection: _collection))),
|
||||
|
||||
@@ -12,24 +12,30 @@ import 'package:plezy/media/media_backend.dart';
|
||||
import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/media/media_library.dart';
|
||||
import 'package:plezy/media/media_server_client.dart';
|
||||
import 'package:plezy/models/plex/plex_config.dart';
|
||||
import 'package:plezy/navigation/main_screen_scope.dart';
|
||||
import 'package:plezy/providers/multi_server_provider.dart';
|
||||
import 'package:plezy/screens/libraries/tabs/library_collections_tab.dart';
|
||||
import 'package:plezy/services/data_aggregation_service.dart';
|
||||
import 'package:plezy/services/jellyfin_api_cache.dart';
|
||||
import 'package:plezy/services/jellyfin_client.dart';
|
||||
import 'package:plezy/services/multi_server_manager.dart';
|
||||
import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/services/plex_client.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/theme/mono_theme.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/widgets/card_inflation_budget.dart';
|
||||
import 'package:plezy/widgets/focusable_media_card.dart';
|
||||
import 'package:plezy/widgets/media_card_sliver_layout.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../test_helpers/backend_client_fixtures.dart';
|
||||
import '../../test_helpers/prefs.dart';
|
||||
|
||||
final _serverId = ServerId('collection-server');
|
||||
final _jellyfinServerId = ServerId('jellyfin-collection-server');
|
||||
final _musicLibrary = MediaLibrary(
|
||||
id: 'music',
|
||||
backend: MediaBackend.plex,
|
||||
@@ -37,6 +43,13 @@ final _musicLibrary = MediaLibrary(
|
||||
kind: MediaKind.artist,
|
||||
serverId: _serverId,
|
||||
);
|
||||
final _jellyfinMusicLibrary = MediaLibrary(
|
||||
id: 'music-library',
|
||||
backend: MediaBackend.jellyfin,
|
||||
title: 'Music',
|
||||
kind: MediaKind.artist,
|
||||
serverId: _jellyfinServerId,
|
||||
);
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -45,63 +58,91 @@ void main() {
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
CardInflationBudget.reset();
|
||||
TvDetectionService.debugSetAppleTVOverride(false);
|
||||
await SettingsService.getInstance();
|
||||
});
|
||||
|
||||
tearDown(() => TvDetectionService.debugSetAppleTVOverride(null));
|
||||
|
||||
testWidgets('music library collections use square grid geometry and square cards', (tester) async {
|
||||
final harness = _CollectionHarness();
|
||||
final harness = _CollectionHarness.plex();
|
||||
addTearDown(harness.dispose);
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
|
||||
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(800, 600);
|
||||
addTearDown(() {
|
||||
tester.view.resetDevicePixelRatio();
|
||||
tester.view.resetPhysicalSize();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
ChangeNotifierProvider<MultiServerProvider>.value(
|
||||
value: harness.provider,
|
||||
child: InputModeTracker(
|
||||
child: MaterialApp(
|
||||
theme: monoTheme(dark: true),
|
||||
home: MainScreenFocusScope(
|
||||
focusSidebar: () {},
|
||||
focusContent: () {},
|
||||
isSidebarFocused: false,
|
||||
sideNavigationWidth: 0,
|
||||
child: Scaffold(
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: const SliverToBoxAdapter(child: SizedBox(height: 1)),
|
||||
),
|
||||
],
|
||||
body: LibraryCollectionsTab(library: _musicLibrary, suppressAutoFocus: true, onBack: () {}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await _pumpTab(tester, harness: harness, library: _musicLibrary);
|
||||
|
||||
final layout = tester.widget<MediaCardSliverLayout>(find.byType(MediaCardSliverLayout));
|
||||
expect(layout.shape, CardShape.square);
|
||||
expect(layout.fullBleedImage, isFalse);
|
||||
expect(tester.widget<FocusableMediaCard>(find.byType(FocusableMediaCard)).cardShapeOverride, CardShape.square);
|
||||
});
|
||||
|
||||
testWidgets('Jellyfin video collections keep poster geometry when opened from a music library', (tester) async {
|
||||
final harness = _CollectionHarness.jellyfin();
|
||||
addTearDown(harness.dispose);
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
|
||||
|
||||
await _pumpTab(tester, harness: harness, library: _jellyfinMusicLibrary);
|
||||
|
||||
final layout = tester.widget<MediaCardSliverLayout>(find.byType(MediaCardSliverLayout));
|
||||
expect(layout.shape, isNull);
|
||||
expect(layout.fullBleedImage, isTrue);
|
||||
expect(tester.widget<FocusableMediaCard>(find.byType(FocusableMediaCard)).cardShapeOverride, isNull);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pumpTab(WidgetTester tester, {required _CollectionHarness harness, required MediaLibrary library}) async {
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(800, 600);
|
||||
addTearDown(() {
|
||||
tester.view.resetDevicePixelRatio();
|
||||
tester.view.resetPhysicalSize();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
ChangeNotifierProvider<MultiServerProvider>.value(
|
||||
value: harness.provider,
|
||||
child: InputModeTracker(
|
||||
child: MaterialApp(
|
||||
theme: monoTheme(dark: true),
|
||||
home: MainScreenFocusScope(
|
||||
focusSidebar: () {},
|
||||
focusContent: () {},
|
||||
isSidebarFocused: false,
|
||||
sideNavigationWidth: 0,
|
||||
child: Scaffold(
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, _) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: const SliverToBoxAdapter(child: SizedBox(height: 1)),
|
||||
),
|
||||
],
|
||||
body: LibraryCollectionsTab(library: library, suppressAutoFocus: true, onBack: () {}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
class _CollectionHarness {
|
||||
late final AppDatabase database;
|
||||
final AppDatabase database;
|
||||
late final MultiServerManager manager;
|
||||
late final MultiServerProvider provider;
|
||||
|
||||
_CollectionHarness() {
|
||||
database = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
_CollectionHarness._({required this.database, required MediaServerClient client}) {
|
||||
manager = MultiServerManager()..debugRegisterClientForTesting(client);
|
||||
provider = MultiServerProvider(manager, DataAggregationService(manager));
|
||||
}
|
||||
|
||||
factory _CollectionHarness.plex() {
|
||||
final database = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
PlexApiCache.initialize(database);
|
||||
final client = PlexClient.forTesting(
|
||||
config: PlexConfig(
|
||||
@@ -131,8 +172,48 @@ class _CollectionHarness {
|
||||
);
|
||||
}),
|
||||
);
|
||||
manager = MultiServerManager()..debugRegisterClientForTesting(client);
|
||||
provider = MultiServerProvider(manager, DataAggregationService(manager));
|
||||
return _CollectionHarness._(database: database, client: client);
|
||||
}
|
||||
|
||||
factory _CollectionHarness.jellyfin() {
|
||||
final database = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
JellyfinApiCache.initialize(database);
|
||||
final client = JellyfinClient.forTesting(
|
||||
connection: testJellyfinConnection(machineId: _jellyfinServerId),
|
||||
httpClient: MockClient((request) async {
|
||||
if (request.url.path == '/Users/user-1/Views') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{'Id': 'boxsets-root', 'Name': 'Collections', 'CollectionType': 'boxsets'},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
if (request.url.path == '/Items') {
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'TotalRecordCount': 1,
|
||||
'Items': [
|
||||
{
|
||||
'Id': 'video-collection-1',
|
||||
'Name': 'Movie Collection',
|
||||
'Type': 'BoxSet',
|
||||
'MediaType': 'Video',
|
||||
'ParentId': 'boxsets-root',
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
return http.Response('not found', 404);
|
||||
}),
|
||||
);
|
||||
return _CollectionHarness._(database: database, client: client);
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:plezy/services/plex_client.dart';
|
||||
import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/theme/mono_theme.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/widgets/card_inflation_budget.dart';
|
||||
import 'package:plezy/widgets/focusable_media_card.dart';
|
||||
import 'package:plezy/widgets/media_card_sliver_layout.dart';
|
||||
@@ -54,9 +55,12 @@ void main() {
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
CardInflationBudget.reset();
|
||||
TvDetectionService.debugSetAppleTVOverride(false);
|
||||
await SettingsService.getInstance();
|
||||
});
|
||||
|
||||
tearDown(() => TvDetectionService.debugSetAppleTVOverride(null));
|
||||
|
||||
testWidgets('grid lazily builds playlist cards and preserves focus navigation', (tester) async {
|
||||
final harness = _PlaylistHarness();
|
||||
addTearDown(harness.dispose);
|
||||
@@ -154,6 +158,8 @@ void main() {
|
||||
final harness = _PlaylistHarness(playlistType: 'audio');
|
||||
addTearDown(harness.dispose);
|
||||
addTearDown(harness.rebuild.dispose);
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
|
||||
|
||||
await _pumpTab(tester, harness: harness, library: _musicLibrary, onBack: () {}, onSidebar: () {});
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ void main() {
|
||||
serverName: 'Server',
|
||||
),
|
||||
]);
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
|
||||
|
||||
await tester.pumpWidget(
|
||||
harness.wrap(
|
||||
|
||||
Reference in New Issue
Block a user