@@ -8,8 +8,25 @@ class SliverErrorState extends StatelessWidget {
|
||||
final String message;
|
||||
final VoidCallback? onRetry;
|
||||
final String? retryLabel;
|
||||
final FocusNode? actionFocusNode;
|
||||
final VoidCallback? onActionNavigateUp;
|
||||
final VoidCallback? onActionNavigateDown;
|
||||
final VoidCallback? onActionNavigateLeft;
|
||||
final VoidCallback? onActionNavigateRight;
|
||||
final VoidCallback? onActionBack;
|
||||
|
||||
const SliverErrorState({super.key, required this.message, this.onRetry, this.retryLabel});
|
||||
const SliverErrorState({
|
||||
super.key,
|
||||
required this.message,
|
||||
this.onRetry,
|
||||
this.retryLabel,
|
||||
this.actionFocusNode,
|
||||
this.onActionNavigateUp,
|
||||
this.onActionNavigateDown,
|
||||
this.onActionNavigateLeft,
|
||||
this.onActionNavigateRight,
|
||||
this.onActionBack,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SliverFillRemaining(
|
||||
@@ -18,6 +35,12 @@ class SliverErrorState extends StatelessWidget {
|
||||
icon: Symbols.error_outline_rounded,
|
||||
onRetry: onRetry,
|
||||
retryLabel: retryLabel ?? t.common.retry,
|
||||
actionFocusNode: actionFocusNode,
|
||||
onActionNavigateUp: onActionNavigateUp,
|
||||
onActionNavigateDown: onActionNavigateDown,
|
||||
onActionNavigateLeft: onActionNavigateLeft,
|
||||
onActionNavigateRight: onActionNavigateRight,
|
||||
onActionBack: onActionBack,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -30,6 +53,12 @@ class SliverEmptyState extends StatelessWidget {
|
||||
final VoidCallback? onAction;
|
||||
final String? actionLabel;
|
||||
final IconData? actionIcon;
|
||||
final FocusNode? actionFocusNode;
|
||||
final VoidCallback? onActionNavigateUp;
|
||||
final VoidCallback? onActionNavigateDown;
|
||||
final VoidCallback? onActionNavigateLeft;
|
||||
final VoidCallback? onActionNavigateRight;
|
||||
final VoidCallback? onActionBack;
|
||||
|
||||
const SliverEmptyState({
|
||||
super.key,
|
||||
@@ -39,6 +68,12 @@ class SliverEmptyState extends StatelessWidget {
|
||||
this.onAction,
|
||||
this.actionLabel,
|
||||
this.actionIcon,
|
||||
this.actionFocusNode,
|
||||
this.onActionNavigateUp,
|
||||
this.onActionNavigateDown,
|
||||
this.onActionNavigateLeft,
|
||||
this.onActionNavigateRight,
|
||||
this.onActionBack,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -50,6 +85,12 @@ class SliverEmptyState extends StatelessWidget {
|
||||
onAction: onAction,
|
||||
actionLabel: actionLabel,
|
||||
actionIcon: actionIcon,
|
||||
actionFocusNode: actionFocusNode,
|
||||
onActionNavigateUp: onActionNavigateUp,
|
||||
onActionNavigateDown: onActionNavigateDown,
|
||||
onActionNavigateLeft: onActionNavigateLeft,
|
||||
onActionNavigateRight: onActionNavigateRight,
|
||||
onActionBack: onActionBack,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,13 +25,15 @@ import '../../theme/mono_tokens.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../widgets/loading_indicator_box.dart';
|
||||
|
||||
/// Individual item in the folder tree
|
||||
/// Can be either a folder (expandable) or a file (tappable)
|
||||
/// Individual item in the folder tree. [isExpandable] controls hierarchy
|
||||
/// behavior independently from [isFolder], which identifies plain directory
|
||||
/// rows and their folder-specific visuals/actions.
|
||||
class FolderTreeItem extends StatefulWidget {
|
||||
final MediaItem item;
|
||||
final int depth;
|
||||
final bool isExpanded;
|
||||
final bool isFolder;
|
||||
final bool isExpandable;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onExpand;
|
||||
final VoidCallback? onPlayAll;
|
||||
@@ -50,6 +52,7 @@ class FolderTreeItem extends StatefulWidget {
|
||||
required this.depth,
|
||||
this.isExpanded = false,
|
||||
this.isFolder = false,
|
||||
this.isExpandable = false,
|
||||
this.onTap,
|
||||
this.onExpand,
|
||||
this.onPlayAll,
|
||||
@@ -69,10 +72,8 @@ class FolderTreeItem extends StatefulWidget {
|
||||
|
||||
class _FolderTreeItemState extends State<FolderTreeItem> with ContextMenuTapMixin {
|
||||
/// Whether the row is a real media item that gets the standard media
|
||||
/// context menu. Jellyfin series/seasons act as expandable folders in the
|
||||
/// tree but are still real media items.
|
||||
bool get _isMediaRow =>
|
||||
!widget.isFolder || widget.item.kind == MediaKind.show || widget.item.kind == MediaKind.season;
|
||||
/// presentation and context menu, even when it is also expandable.
|
||||
bool get _isMediaRow => !widget.isFolder;
|
||||
|
||||
/// Plain folders only offer the Play/Shuffle actions of their trailing buttons.
|
||||
bool get _hasFolderMenu => !_isMediaRow && (widget.onPlayAll != null || widget.onShuffle != null);
|
||||
@@ -109,7 +110,7 @@ class _FolderTreeItemState extends State<FolderTreeItem> with ContextMenuTapMixi
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
if (widget.isFolder) {
|
||||
if (widget.isExpandable) {
|
||||
widget.onExpand?.call();
|
||||
} else {
|
||||
widget.onTap?.call();
|
||||
@@ -259,6 +260,7 @@ class _FolderTreeItemState extends State<FolderTreeItem> with ContextMenuTapMixi
|
||||
final episodePosterMode = svc.read(SettingsService.episodePosterMode);
|
||||
final hideSpoilers = svc.read(SettingsService.hideSpoilers);
|
||||
final showUnwatchedCount = svc.read(SettingsService.showUnwatchedCount);
|
||||
final expandIcon = widget.isExpanded ? Symbols.keyboard_arrow_down_rounded : Symbols.keyboard_arrow_right_rounded;
|
||||
|
||||
final isWide = widget.item.usesWideAspectRatio(episodePosterMode);
|
||||
final thumbWidth = isWide ? 130.0 : 53.0;
|
||||
@@ -272,6 +274,13 @@ class _FolderTreeItemState extends State<FolderTreeItem> with ContextMenuTapMixi
|
||||
child: Row(
|
||||
crossAxisAlignment: .center,
|
||||
children: [
|
||||
if (widget.isExpandable) ...[
|
||||
SizedBox(
|
||||
width: 24,
|
||||
child: widget.isLoading ? const LoadingIndicatorBox(size: 16) : AppIcon(expandIcon, fill: 1, size: 20),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
// Thumbnail with progress overlay
|
||||
SizedBox(
|
||||
width: thumbWidth,
|
||||
|
||||
@@ -26,6 +26,7 @@ class FolderTreeView extends StatefulWidget {
|
||||
final FocusNode? firstItemFocusNode;
|
||||
final VoidCallback? onNavigateUp;
|
||||
final VoidCallback? onNavigateLeft;
|
||||
final VoidCallback? onBack;
|
||||
|
||||
const FolderTreeView({
|
||||
super.key,
|
||||
@@ -36,6 +37,7 @@ class FolderTreeView extends StatefulWidget {
|
||||
this.firstItemFocusNode,
|
||||
this.onNavigateUp,
|
||||
this.onNavigateLeft,
|
||||
this.onBack,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -65,7 +67,7 @@ class FolderTreeViewState extends State<FolderTreeView> {
|
||||
/// Stable expand/cache key for an expandable row: the backend folder key
|
||||
/// where one exists (Plex `/folder` rows), the item id otherwise.
|
||||
String? _folderIdentity(MediaItem item) {
|
||||
if (!_isFolder(item)) return null;
|
||||
if (!_isExpandable(item)) return null;
|
||||
return item.backendFolderKey ?? item.id;
|
||||
}
|
||||
|
||||
@@ -278,14 +280,18 @@ class FolderTreeViewState extends State<FolderTreeView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Expandable rows: directory rows (classified as [MediaKind.folder] by the
|
||||
/// backend's folder fetchers) plus Jellyfin shows/seasons, which surface as
|
||||
/// expandable media containers in folder browsing.
|
||||
bool _isFolder(MediaItem item) {
|
||||
/// Expandable rows: directory rows plus Jellyfin media containers whose
|
||||
/// direct children form the folder tree. Music libraries expose folder-
|
||||
/// backed artists and albums as MusicArtist/MusicAlbum rather than generic
|
||||
/// Folder DTOs, so those rows must expand instead of opening empty details.
|
||||
bool _isExpandable(MediaItem item) {
|
||||
return item.kind == MediaKind.folder || (item.backend == MediaBackend.jellyfin && _isJellyfinMediaContainer(item));
|
||||
}
|
||||
|
||||
bool _isJellyfinMediaContainer(MediaItem item) => item.kind == MediaKind.show || item.kind == MediaKind.season;
|
||||
bool _isJellyfinMediaContainer(MediaItem item) {
|
||||
if (item.kind == MediaKind.show || item.kind == MediaKind.season) return true;
|
||||
return widget.libraryKind?.isMusic == true && (item.kind == MediaKind.artist || item.kind == MediaKind.album);
|
||||
}
|
||||
|
||||
bool _canPlayFolder(MediaItem item) {
|
||||
if (item.backend == MediaBackend.plex) return true;
|
||||
@@ -324,7 +330,7 @@ class FolderTreeViewState extends State<FolderTreeView> {
|
||||
out.add((item: item, depth: depth, path: itemPath, parent: parent));
|
||||
|
||||
final folderKey = _folderIdentity(item);
|
||||
if (_isFolder(item) &&
|
||||
if (_isExpandable(item) &&
|
||||
folderKey != null &&
|
||||
_expandedFolders.contains(folderKey) &&
|
||||
_childrenCache.containsKey(folderKey)) {
|
||||
@@ -347,6 +353,10 @@ class FolderTreeViewState extends State<FolderTreeView> {
|
||||
icon: Symbols.error_outline_rounded,
|
||||
onRetry: _loadRootFolders,
|
||||
retryLabel: t.common.retry,
|
||||
actionFocusNode: widget.firstItemFocusNode,
|
||||
onActionNavigateUp: widget.onNavigateUp,
|
||||
onActionNavigateLeft: widget.onNavigateLeft,
|
||||
onActionBack: widget.onBack,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -368,12 +378,13 @@ class FolderTreeViewState extends State<FolderTreeView> {
|
||||
itemBuilder: (context, index) {
|
||||
final entry = flattened[index];
|
||||
final item = entry.item;
|
||||
final isFolder = _isFolder(item);
|
||||
final isExpandable = _isExpandable(item);
|
||||
final isPlainFolder = item.kind == MediaKind.folder;
|
||||
final folderKey = _folderIdentity(item);
|
||||
final isExpanded = folderKey != null && _expandedFolders.contains(folderKey);
|
||||
final isLoading = folderKey != null && _loadingFolders.contains(folderKey);
|
||||
final isFirstRootItem = index == 0;
|
||||
final canPlayFolder = isFolder && _canPlayFolder(item);
|
||||
final canPlayFolder = isPlainFolder && _canPlayFolder(item);
|
||||
|
||||
return FolderTreeItem(
|
||||
// Path alone isn't unique enough as identity (the same Plex item
|
||||
@@ -381,12 +392,13 @@ class FolderTreeViewState extends State<FolderTreeView> {
|
||||
key: ValueKey('${entry.path}:${item.id}'),
|
||||
item: item,
|
||||
depth: entry.depth,
|
||||
isFolder: isFolder,
|
||||
isFolder: isPlainFolder,
|
||||
isExpandable: isExpandable,
|
||||
isExpanded: isExpanded,
|
||||
isLoading: isLoading,
|
||||
serverId: widget.serverId,
|
||||
onExpand: isFolder ? () => _toggleFolder(item) : null,
|
||||
onTap: !isFolder ? () => _handleItemTap(item) : null,
|
||||
onExpand: isExpandable ? () => _toggleFolder(item) : null,
|
||||
onTap: !isExpandable ? () => _handleItemTap(item) : null,
|
||||
onPlayAll: canPlayFolder ? () => _handleFolderPlay(item) : null,
|
||||
onShuffle: canPlayFolder ? () => _handleFolderShuffle(item) : null,
|
||||
focusNode: isFirstRootItem ? widget.firstItemFocusNode : null,
|
||||
|
||||
@@ -37,7 +37,7 @@ class LibraryFilterSortLoader {
|
||||
Future<LoadedFiltersAndSorts> load(MediaLibrary library, {String? sortLibraryType}) async {
|
||||
final client = clientFor(library);
|
||||
final results = await Future.wait([
|
||||
client.fetchLibraryFiltersWithValues(library.id),
|
||||
client.fetchLibraryFiltersWithValues(library.id, libraryKind: library.kind),
|
||||
client.fetchSortOptions(library.id, libraryType: sortLibraryType ?? library.kind.id),
|
||||
]);
|
||||
final filterResult = results.first as LibraryFilterResult;
|
||||
|
||||
@@ -36,6 +36,12 @@ class StateMessageWidget extends StatelessWidget {
|
||||
|
||||
/// Optional icon for the action button
|
||||
final IconData? actionIcon;
|
||||
final FocusNode? actionFocusNode;
|
||||
final VoidCallback? onActionNavigateUp;
|
||||
final VoidCallback? onActionNavigateDown;
|
||||
final VoidCallback? onActionNavigateLeft;
|
||||
final VoidCallback? onActionNavigateRight;
|
||||
final VoidCallback? onActionBack;
|
||||
|
||||
const StateMessageWidget({
|
||||
super.key,
|
||||
@@ -48,6 +54,12 @@ class StateMessageWidget extends StatelessWidget {
|
||||
this.subtitleColor,
|
||||
this.onAction,
|
||||
this.actionLabel,
|
||||
this.actionFocusNode,
|
||||
this.onActionNavigateUp,
|
||||
this.onActionNavigateDown,
|
||||
this.onActionNavigateLeft,
|
||||
this.onActionNavigateRight,
|
||||
this.onActionBack,
|
||||
this.actionIcon,
|
||||
});
|
||||
|
||||
@@ -87,6 +99,12 @@ class StateMessageWidget extends StatelessWidget {
|
||||
if (onAction != null && actionLabel != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
FocusableButton(
|
||||
focusNode: actionFocusNode,
|
||||
onNavigateUp: onActionNavigateUp,
|
||||
onNavigateDown: onActionNavigateDown,
|
||||
onNavigateLeft: onActionNavigateLeft,
|
||||
onNavigateRight: onActionNavigateRight,
|
||||
onBack: onActionBack,
|
||||
onPressed: onAction,
|
||||
child: FilledButton.icon(
|
||||
onPressed: onAction,
|
||||
@@ -124,6 +142,12 @@ class EmptyStateWidget extends StatelessWidget {
|
||||
|
||||
/// Optional icon for the action button (defaults to a generic add icon)
|
||||
final IconData? actionIcon;
|
||||
final FocusNode? actionFocusNode;
|
||||
final VoidCallback? onActionNavigateUp;
|
||||
final VoidCallback? onActionNavigateDown;
|
||||
final VoidCallback? onActionNavigateLeft;
|
||||
final VoidCallback? onActionNavigateRight;
|
||||
final VoidCallback? onActionBack;
|
||||
|
||||
const EmptyStateWidget({
|
||||
super.key,
|
||||
@@ -134,6 +158,12 @@ class EmptyStateWidget extends StatelessWidget {
|
||||
this.onAction,
|
||||
this.actionLabel,
|
||||
this.actionIcon,
|
||||
this.actionFocusNode,
|
||||
this.onActionNavigateUp,
|
||||
this.onActionNavigateDown,
|
||||
this.onActionNavigateLeft,
|
||||
this.onActionNavigateRight,
|
||||
this.onActionBack,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -146,6 +176,12 @@ class EmptyStateWidget extends StatelessWidget {
|
||||
onAction: onAction,
|
||||
actionLabel: actionLabel,
|
||||
actionIcon: actionIcon ?? Symbols.add_rounded,
|
||||
actionFocusNode: actionFocusNode,
|
||||
onActionNavigateUp: onActionNavigateUp,
|
||||
onActionNavigateDown: onActionNavigateDown,
|
||||
onActionNavigateLeft: onActionNavigateLeft,
|
||||
onActionNavigateRight: onActionNavigateRight,
|
||||
onActionBack: onActionBack,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -163,8 +199,26 @@ class ErrorStateWidget extends StatelessWidget {
|
||||
|
||||
/// Optional label for the retry button
|
||||
final String? retryLabel;
|
||||
final FocusNode? actionFocusNode;
|
||||
final VoidCallback? onActionNavigateUp;
|
||||
final VoidCallback? onActionNavigateDown;
|
||||
final VoidCallback? onActionNavigateLeft;
|
||||
final VoidCallback? onActionNavigateRight;
|
||||
final VoidCallback? onActionBack;
|
||||
|
||||
const ErrorStateWidget({super.key, required this.message, this.icon, this.onRetry, this.retryLabel});
|
||||
const ErrorStateWidget({
|
||||
super.key,
|
||||
required this.message,
|
||||
this.icon,
|
||||
this.onRetry,
|
||||
this.retryLabel,
|
||||
this.actionFocusNode,
|
||||
this.onActionNavigateUp,
|
||||
this.onActionNavigateDown,
|
||||
this.onActionNavigateLeft,
|
||||
this.onActionNavigateRight,
|
||||
this.onActionBack,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -176,6 +230,12 @@ class ErrorStateWidget extends StatelessWidget {
|
||||
onAction: onRetry,
|
||||
actionLabel: retryLabel ?? 'Retry',
|
||||
actionIcon: Symbols.refresh_rounded,
|
||||
actionFocusNode: actionFocusNode,
|
||||
onActionNavigateUp: onActionNavigateUp,
|
||||
onActionNavigateDown: onActionNavigateDown,
|
||||
onActionNavigateLeft: onActionNavigateLeft,
|
||||
onActionNavigateRight: onActionNavigateRight,
|
||||
onActionBack: onActionBack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,6 +426,9 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
@override
|
||||
Widget buildContent(List<MediaItem> items) => const SizedBox.shrink();
|
||||
|
||||
bool get _hasFocusableStateAction =>
|
||||
(errorMessage != null && loadedItems.isEmpty) || (totalSize == 0 && !isLoading && _selectedFilters.isNotEmpty);
|
||||
|
||||
/// Focus the first item in the grid/list/folder tree (for tab activation)
|
||||
@override
|
||||
void focusFirstItem() {
|
||||
@@ -442,11 +445,11 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadedItems.isNotEmpty) {
|
||||
if (loadedItems.isNotEmpty || _hasFocusableStateAction) {
|
||||
// Request immediately, then once more on the next frame to handle cases
|
||||
// where the grid/list attaches after the initial focus attempt.
|
||||
void request() {
|
||||
if (mounted && loadedItems.isNotEmpty && !firstItemFocusNode.hasFocus) {
|
||||
if (mounted && (loadedItems.isNotEmpty || _hasFocusableStateAction) && !firstItemFocusNode.hasFocus) {
|
||||
firstItemFocusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
@@ -457,7 +460,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
}
|
||||
|
||||
@override
|
||||
bool get hasFocusableContent => _selectedGrouping == 'folders' || loadedItems.isNotEmpty;
|
||||
bool get hasFocusableContent => _selectedGrouping == 'folders' || loadedItems.isNotEmpty || _hasFocusableStateAction;
|
||||
|
||||
@override
|
||||
void focusContentOrChrome() {
|
||||
@@ -610,7 +613,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
final client = context.getMediaClientForLibrary(widget.library);
|
||||
unawaited(
|
||||
client
|
||||
.fetchLibraryFiltersWithValues(widget.library.id)
|
||||
.fetchLibraryFiltersWithValues(widget.library.id, libraryKind: widget.library.kind)
|
||||
.then((result) {
|
||||
if (generation != _contentRequestId || !mounted) return;
|
||||
setState(() {
|
||||
@@ -1113,10 +1116,11 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
return;
|
||||
}
|
||||
|
||||
if (totalSize == 0) return;
|
||||
if (totalSize == 0 && !_hasFocusableStateAction) return;
|
||||
|
||||
final targetIndex =
|
||||
shouldRestoreGridFocus && lastFocusedGridIndex! < totalSize && loadedItems.containsKey(lastFocusedGridIndex!)
|
||||
final targetIndex = totalSize == 0
|
||||
? 0
|
||||
: shouldRestoreGridFocus && lastFocusedGridIndex! < totalSize && loadedItems.containsKey(lastFocusedGridIndex!)
|
||||
? lastFocusedGridIndex!
|
||||
: 0;
|
||||
|
||||
@@ -1748,6 +1752,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
firstItemFocusNode: firstItemFocusNode,
|
||||
onNavigateUp: _navigateToChips,
|
||||
onNavigateLeft: _navigateToSidebar,
|
||||
onBack: widget.onBack,
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -1757,7 +1762,16 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
}
|
||||
|
||||
if (errorMessage != null && loadedItems.isEmpty) {
|
||||
return [SliverErrorState(message: errorMessage!, onRetry: _loadContent)];
|
||||
return [
|
||||
SliverErrorState(
|
||||
message: errorMessage!,
|
||||
onRetry: _loadContent,
|
||||
actionFocusNode: firstItemFocusNode,
|
||||
onActionNavigateUp: _navigateToChips,
|
||||
onActionNavigateLeft: _navigateToSidebar,
|
||||
onActionBack: widget.onBack,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (totalSize == 0 && !isLoading) {
|
||||
@@ -1769,6 +1783,10 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
onAction: _resetFilters,
|
||||
actionLabel: t.libraries.resetFilters,
|
||||
actionIcon: Symbols.clear_all_rounded,
|
||||
actionFocusNode: firstItemFocusNode,
|
||||
onActionNavigateUp: _navigateToChips,
|
||||
onActionNavigateLeft: _navigateToSidebar,
|
||||
onActionBack: widget.onBack,
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -1869,6 +1887,10 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
_selectedGrouping == browseGroupingAlbums ||
|
||||
_selectedGrouping == browseGroupingTracks;
|
||||
final browseShape = isMusicGrouping ? CardShape.square : null;
|
||||
// Full-bleed TV cards intentionally hide captions. Music artwork alone
|
||||
// is not a reliable identity, so artist/album/track grids always keep the
|
||||
// standard captioned card while preserving their circular/square artwork.
|
||||
final useFullCardLayout = fullCardLayout && !isMusicGrouping;
|
||||
|
||||
if (viewMode == ViewMode.list) {
|
||||
_setListScrollMetrics(density: libraryDensity, usesWideAspectRatio: useWideRatio, shape: browseShape);
|
||||
@@ -1882,7 +1904,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
padding: EdgeInsets.fromLTRB(8, topPadding, rightPadding, 8),
|
||||
useWideAspectRatio: useWideRatio,
|
||||
shape: browseShape,
|
||||
fullBleedImage: fullCardLayout,
|
||||
fullBleedImage: useFullCardLayout,
|
||||
crossAxisExtentForColumnCount: hasAlphaBarReservation
|
||||
? (crossAxisExtent) => crossAxisExtent + (rightPadding - 8.0)
|
||||
: null,
|
||||
@@ -1899,7 +1921,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
ViewMode.grid,
|
||||
geometry.columnCount,
|
||||
itemCount,
|
||||
fullCardLayout,
|
||||
useFullCardLayout,
|
||||
useWideRatio,
|
||||
browseShape,
|
||||
libraryDensity,
|
||||
@@ -1951,7 +1973,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
isLastColumn: position.isLastColumn,
|
||||
columnCount: position.columnCount,
|
||||
itemCount: itemCount,
|
||||
fullBleedImage: fullCardLayout,
|
||||
fullBleedImage: useFullCardLayout,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user