fix: respect view mode in detail screens, disable scale in list mode

close #631
This commit is contained in:
edde746
2026-03-05 17:38:38 +01:00
parent a4008d3d6b
commit 22a733280a
7 changed files with 73 additions and 1 deletions
@@ -4,6 +4,7 @@ import '../focus/focusable_action_bar.dart';
import '../focus/input_mode_tracker.dart';
import '../mixins/grid_focus_node_mixin.dart';
import '../providers/settings_provider.dart';
import '../services/settings_service.dart' show ViewMode;
import '../utils/grid_size_calculator.dart';
import '../widgets/focusable_media_card.dart';
import '../widgets/media_grid_delegate.dart';
@@ -130,6 +131,36 @@ mixin FocusableDetailScreenMixin<T extends StatefulWidget> on State<T>, GridFocu
}) {
return Consumer<SettingsProvider>(
builder: (context, settingsProvider, child) {
final isListMode = settingsProvider.viewMode == ViewMode.list;
if (isListMode) {
return SliverPadding(
padding: const EdgeInsets.all(8),
sliver: SliverList.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final focusNode = index == 0
? firstItemFocusNode
: getGridItemFocusNode(index, prefix: 'detail_grid_item');
return FocusableMediaCard(
key: Key(item.ratingKey),
item: item,
focusNode: focusNode,
disableScale: true,
onRefresh: onRefresh,
collectionId: collectionId,
onListRefresh: onListRefresh,
onNavigateUp: index == 0 ? navigateToAppBar : null,
onBack: handleBackFromContent,
onFocusChange: (hasFocus) => trackGridItemFocus(index, hasFocus),
);
},
),
);
}
final maxExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, settingsProvider.libraryDensity);
return SliverPadding(
padding: const EdgeInsets.all(8),
+27
View File
@@ -319,6 +319,7 @@ class _HubDetailScreenState extends State<HubDetailScreen>
Builder(
builder: (context) {
final settings = context.watch<SettingsProvider>();
final isListMode = settings.viewMode == ViewMode.list;
final episodePosterMode = settings.episodePosterMode;
// Determine hub content type for layout decisions
@@ -335,6 +336,32 @@ class _HubDetailScreenState extends State<HubDetailScreen>
final useWideLayout =
episodePosterMode == EpisodePosterMode.episodeThumbnail && (isEpisodeOnlyHub || isMixedHub);
if (isListMode) {
return SliverPadding(
padding: const EdgeInsets.all(8),
sliver: SliverList.builder(
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
final focusNode = index == 0
? firstItemFocusNode
: getGridItemFocusNode(index, prefix: 'hub_detail_item');
return FocusableMediaCard(
focusNode: focusNode,
item: item,
disableScale: true,
onRefresh: _handleItemRefresh,
onNavigateUp: index == 0 ? navigateToAppBar : null,
onBack: handleBackFromContent,
onFocusChange: (hasFocus) => trackGridItemFocus(index, hasFocus),
mixedHubContext: isMixedHub,
);
},
),
);
}
return SliverPadding(
padding: const EdgeInsets.all(8),
sliver: SliverLayoutBuilder(
@@ -14,10 +14,13 @@ class GridItemContext {
/// Whether this item is in the first column of the grid.
final bool isFirstColumn;
/// Whether items are displayed in list mode (single column).
final bool isListMode;
/// Callback to navigate to the sidebar (for first-column items).
final VoidCallback? navigateToSidebar;
const GridItemContext({required this.isFirstRow, required this.isFirstColumn, this.navigateToSidebar});
const GridItemContext({required this.isFirstRow, required this.isFirstColumn, this.isListMode = false, this.navigateToSidebar});
}
/// A widget that automatically switches between grid and list view
@@ -99,6 +102,7 @@ class AdaptiveMediaGrid<T> extends StatelessWidget {
? GridItemContext(
isFirstRow: index == 0,
isFirstColumn: true, // List view = single column
isListMode: true,
navigateToSidebar: () => _navigateToSidebar(context),
)
: null;
@@ -1214,6 +1214,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<PlexMetadata, LibraryBr
index,
isFirstRow: index == 0,
isFirstColumn: true, // List view = single column
disableScale: true,
),
),
);
@@ -1256,6 +1257,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<PlexMetadata, LibraryBr
required bool isFirstRow,
required bool isFirstColumn,
bool isLastColumn = false,
bool disableScale = false,
}) {
final item = _loadedItems[index];
@@ -1272,6 +1274,7 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<PlexMetadata, LibraryBr
key: Key(item.ratingKey),
item: item,
focusNode: focusNode,
disableScale: disableScale,
onRefresh: updateItem,
onNavigateUp: isFirstRow ? _navigateToChips : null,
onNavigateLeft: isFirstColumn ? _navigateToSidebar : null,
@@ -57,6 +57,7 @@ class _LibraryCollectionsTabState extends LibraryGridTabState<PlexMetadata, Libr
key: Key(item.ratingKey),
item: item,
focusNode: index == 0 ? firstItemFocusNode : null,
disableScale: gridContext?.isListMode ?? false,
onListRefresh: loadItems,
onBack: widget.onBack,
onNavigateLeft: gridContext?.isFirstColumn == true ? gridContext?.navigateToSidebar : null,
@@ -57,6 +57,7 @@ class _LibraryPlaylistsTabState extends LibraryGridTabState<PlexPlaylist, Librar
key: Key(playlist.ratingKey),
item: playlist,
focusNode: index == 0 ? firstItemFocusNode : null,
disableScale: gridContext?.isListMode ?? false,
onListRefresh: loadItems,
onBack: widget.onBack,
onNavigateLeft: gridContext?.isFirstColumn == true ? gridContext?.navigateToSidebar : null,
+5
View File
@@ -31,6 +31,9 @@ class FocusableMediaCard extends StatefulWidget {
/// Show server name in list view (multi-server)
final bool showServerName;
/// Whether to disable the scale animation on focus (e.g. in list view).
final bool disableScale;
/// Optional external focus node for programmatic focus control.
/// If not provided, an internal focus node is created.
final FocusNode? focusNode;
@@ -70,6 +73,7 @@ class FocusableMediaCard extends StatefulWidget {
this.isOffline = false,
this.mixedHubContext = false,
this.showServerName = false,
this.disableScale = false,
this.focusNode,
this.onNavigateUp,
this.onNavigateLeft,
@@ -98,6 +102,7 @@ class _FocusableMediaCardState extends State<FocusableMediaCard> {
onBack: widget.onBack,
onFocusChange: widget.onFocusChange,
enableLongPress: true,
disableScale: widget.disableScale,
useComfortableZone: !PlatformDetector.isTV(), // Always center on TV
scrollAlignment: 0.5,
child: MediaCard(