fix: respect view mode in detail screens, disable scale in list mode
close #631
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user