fix: overlay sheet position, scroll-to-selected in bottom sheets
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:plezy/widgets/app_icon.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../models/plex_filter.dart';
|
||||
import '../../utils/scroll_utils.dart';
|
||||
import '../../widgets/app_bar_back_button.dart';
|
||||
import '../../widgets/bottom_sheet_header.dart';
|
||||
import '../../widgets/focusable_list_tile.dart';
|
||||
@@ -38,6 +39,8 @@ class _FiltersBottomSheetState extends State<FiltersBottomSheet> {
|
||||
static const int _maxCachedDisplayNames = 1000;
|
||||
late List<PlexFilter> _sortedFilters;
|
||||
late final FocusNode _initialFocusNode;
|
||||
final _valuesFirstItemKey = GlobalKey();
|
||||
final _valuesScrollController = ScrollController();
|
||||
|
||||
String _cacheKey(String filter, String value) => '${widget.serverId}:${widget.libraryKey}:$filter:$value';
|
||||
|
||||
@@ -51,6 +54,7 @@ class _FiltersBottomSheetState extends State<FiltersBottomSheet> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_valuesScrollController.dispose();
|
||||
_initialFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -83,6 +87,15 @@ class _FiltersBottomSheetState extends State<FiltersBottomSheet> {
|
||||
_filterValues = values;
|
||||
_isLoadingValues = false;
|
||||
});
|
||||
// Scroll to selected value if any
|
||||
final selectedValue = _tempSelectedFilters[filter.filter];
|
||||
if (selectedValue != null) {
|
||||
// +1 because index 0 is the "All" row
|
||||
final idx = values.indexWhere((v) => _extractFilterValue(v.key, filter.filter) == selectedValue) + 1;
|
||||
if (idx > 0) {
|
||||
scrollToCurrentItem(_valuesScrollController, _valuesFirstItemKey, idx);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
@@ -134,12 +147,14 @@ class _FiltersBottomSheetState extends State<FiltersBottomSheet> {
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _valuesScrollController,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: _filterValues.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
final isSelected = !_tempSelectedFilters.containsKey(_currentFilter!.filter);
|
||||
return FocusableListTile(
|
||||
key: _valuesFirstItemKey,
|
||||
focusNode: _initialFocusNode,
|
||||
title: Text(t.libraries.all),
|
||||
selected: isSelected,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../focus/dpad_navigator.dart';
|
||||
import '../../focus/input_mode_tracker.dart';
|
||||
import '../../models/plex_sort.dart';
|
||||
import '../../utils/scroll_utils.dart';
|
||||
import '../../widgets/bottom_sheet_header.dart';
|
||||
import '../../widgets/focusable_list_tile.dart';
|
||||
import '../../widgets/overlay_sheet.dart';
|
||||
@@ -33,6 +34,8 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
||||
late PlexSort? _currentSort;
|
||||
late bool _currentDescending;
|
||||
late final FocusNode _initialFocusNode;
|
||||
final _firstItemKey = GlobalKey();
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -40,6 +43,15 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
||||
_currentSort = widget.selectedSort;
|
||||
_currentDescending = widget.isSortDescending;
|
||||
_initialFocusNode = FocusNode(debugLabel: 'SortBottomSheetInitialFocus');
|
||||
|
||||
// Scroll to selected item, then handle focus
|
||||
final selectedIndex = widget.selectedSort != null
|
||||
? widget.sortOptions.indexWhere((s) => s.key == widget.selectedSort!.key)
|
||||
: -1;
|
||||
if (selectedIndex > 0) {
|
||||
scrollToCurrentItem(_scrollController, _firstItemKey, selectedIndex);
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
if (!InputModeTracker.isKeyboardMode(context)) return;
|
||||
@@ -57,6 +69,7 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_initialFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -102,6 +115,7 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
||||
if (value != null) _handleSortSelect(value);
|
||||
},
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemCount: widget.sortOptions.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -109,6 +123,7 @@ class _SortBottomSheetState extends State<SortBottomSheet> {
|
||||
final isSelected = _currentSort?.key == sort.key;
|
||||
|
||||
return Focus(
|
||||
key: index == 0 ? _firstItemKey : null,
|
||||
canRequestFocus: false,
|
||||
skipTraversal: true,
|
||||
onKeyEvent: (node, event) {
|
||||
|
||||
@@ -12,6 +12,22 @@ void scrollContextToCenter(BuildContext? context) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Jump a vertical [ListView] so that [currentIndex] is visible.
|
||||
///
|
||||
/// Measures the first item (via [firstItemKey]) to get the real item height,
|
||||
/// then scrolls to `currentIndex * itemHeight`, clamped to max extent.
|
||||
/// Call once after the first build; the callback is a no-op if the key or
|
||||
/// controller aren't ready yet.
|
||||
void scrollToCurrentItem(ScrollController controller, GlobalKey firstItemKey, int currentIndex) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!controller.hasClients) return;
|
||||
final itemHeight = (firstItemKey.currentContext?.findRenderObject() as RenderBox?)?.size.height;
|
||||
if (itemHeight == null) return;
|
||||
final target = (currentIndex * itemHeight).clamp(0.0, controller.position.maxScrollExtent);
|
||||
controller.jumpTo(target);
|
||||
});
|
||||
}
|
||||
|
||||
/// Scroll a horizontal list to center the item at the given index.
|
||||
///
|
||||
/// Assumes items are laid out with [leadingPadding] before the first item,
|
||||
|
||||
@@ -582,17 +582,17 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
canRequestFocus: false,
|
||||
skipTraversal: true,
|
||||
onKeyEvent: _handleKeyEvent,
|
||||
child: AnimatedBuilder(
|
||||
animation: _slideCurve,
|
||||
builder: (context, child) {
|
||||
final slideOffset = Offset.lerp(slideBegin, Offset.zero, _slideCurve.value)!;
|
||||
return FractionalTranslation(
|
||||
translation: slideOffset,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Align(
|
||||
alignment: _alignment,
|
||||
child: Align(
|
||||
alignment: _alignment,
|
||||
child: AnimatedBuilder(
|
||||
animation: _slideCurve,
|
||||
builder: (context, child) {
|
||||
final slideOffset = Offset.lerp(slideBegin, Offset.zero, _slideCurve.value)!;
|
||||
return FractionalTranslation(
|
||||
translation: slideOffset,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, _dragOffset.clamp(0, double.infinity)),
|
||||
child: SafeArea(
|
||||
|
||||
@@ -43,6 +43,7 @@ class TrackSelectionHelper {
|
||||
required BuildContext context,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
Key? key,
|
||||
FocusNode? focusNode,
|
||||
VoidCallback? onLongPress,
|
||||
VoidCallback? onSecondaryTap,
|
||||
@@ -50,6 +51,7 @@ class TrackSelectionHelper {
|
||||
}) {
|
||||
return _buildSelectableTile(
|
||||
context: context,
|
||||
key: key,
|
||||
label: 'Off',
|
||||
isSelected: isSelected,
|
||||
onTap: onTap,
|
||||
@@ -66,6 +68,7 @@ class TrackSelectionHelper {
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
Key? key,
|
||||
FocusNode? focusNode,
|
||||
VoidCallback? onLongPress,
|
||||
VoidCallback? onSecondaryTap,
|
||||
@@ -73,6 +76,7 @@ class TrackSelectionHelper {
|
||||
}) {
|
||||
return _buildSelectableTile(
|
||||
context: context,
|
||||
key: key,
|
||||
label: label,
|
||||
isSelected: isSelected,
|
||||
onTap: onTap,
|
||||
@@ -103,6 +107,7 @@ class TrackSelectionHelper {
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
Key? key,
|
||||
FocusNode? focusNode,
|
||||
VoidCallback? onLongPress,
|
||||
VoidCallback? onSecondaryTap,
|
||||
@@ -117,6 +122,7 @@ class TrackSelectionHelper {
|
||||
}
|
||||
|
||||
Widget tile = FocusableListTile(
|
||||
key: key,
|
||||
focusNode: focusNode,
|
||||
title: Text(label, style: TextStyle(color: isSelected ? primaryColor : null)),
|
||||
trailing: trailing,
|
||||
|
||||
@@ -47,6 +47,6 @@ class BaseVideoControlSheet extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(height: MediaQuery.of(context).size.height * 0.75, child: content);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../../theme/mono_tokens.dart';
|
||||
import '../../../utils/formatters.dart';
|
||||
import '../../../utils/player_utils.dart';
|
||||
import '../../../utils/provider_extensions.dart';
|
||||
import '../../../utils/scroll_utils.dart';
|
||||
import '../../../widgets/focusable_list_tile.dart';
|
||||
import '../../../widgets/overlay_sheet.dart';
|
||||
import 'base_video_control_sheet.dart';
|
||||
@@ -40,6 +41,16 @@ class ChapterSheet extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChapterSheetState extends State<ChapterSheet> {
|
||||
final _firstItemKey = GlobalKey();
|
||||
final _scrollController = ScrollController();
|
||||
bool _didInitialScroll = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _handleChapterTap(Duration position) async {
|
||||
final clamped = clampSeekPosition(widget.player, position);
|
||||
await widget.player.seek(clamped);
|
||||
@@ -86,7 +97,13 @@ class _ChapterSheetState extends State<ChapterSheet> {
|
||||
child: Text(t.videoControls.noChaptersAvailable, style: TextStyle(color: tokens(context).textMuted)),
|
||||
);
|
||||
} else {
|
||||
if (!_didInitialScroll && currentChapterIndex != null && currentChapterIndex > 0) {
|
||||
_didInitialScroll = true;
|
||||
scrollToCurrentItem(_scrollController, _firstItemKey, currentChapterIndex);
|
||||
}
|
||||
|
||||
content = ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: widget.chapters.length,
|
||||
itemBuilder: (context, index) {
|
||||
final chapter = widget.chapters[index];
|
||||
@@ -98,6 +115,7 @@ class _ChapterSheetState extends State<ChapterSheet> {
|
||||
: null;
|
||||
|
||||
return FocusableListTile(
|
||||
key: index == 0 ? _firstItemKey : null,
|
||||
leading: chapter.thumb != null
|
||||
? SizedBox(
|
||||
width: 60,
|
||||
|
||||
@@ -8,21 +8,36 @@ import '../../../models/plex_metadata.dart';
|
||||
import '../../../providers/playback_state_provider.dart';
|
||||
import '../../../theme/mono_tokens.dart';
|
||||
import '../../../utils/provider_extensions.dart';
|
||||
import '../../../utils/scroll_utils.dart';
|
||||
import '../../../widgets/focusable_list_tile.dart';
|
||||
import '../../../widgets/overlay_sheet.dart';
|
||||
import 'base_video_control_sheet.dart';
|
||||
import '../../plex_optimized_image.dart';
|
||||
|
||||
const _kEstimatedItemHeight = 72.0;
|
||||
const _kThumbWidth = 60.0;
|
||||
const _kThumbHeight = 34.0;
|
||||
|
||||
/// Bottom sheet for viewing and navigating the play queue
|
||||
class QueueSheet extends StatelessWidget {
|
||||
class QueueSheet extends StatefulWidget {
|
||||
final Function(PlexMetadata) onItemSelected;
|
||||
|
||||
const QueueSheet({super.key, required this.onItemSelected});
|
||||
|
||||
@override
|
||||
State<QueueSheet> createState() => _QueueSheetState();
|
||||
}
|
||||
|
||||
class _QueueSheetState extends State<QueueSheet> {
|
||||
final _firstItemKey = GlobalKey();
|
||||
final _scrollController = ScrollController();
|
||||
bool _didInitialScroll = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<PlaybackStateProvider>(
|
||||
@@ -36,13 +51,14 @@ class QueueSheet extends StatelessWidget {
|
||||
child: Text(t.videoControls.noQueueItems, style: TextStyle(color: tokens(context).textMuted)),
|
||||
);
|
||||
} else {
|
||||
// Find current index for initial scroll
|
||||
final currentIndex = items.indexWhere((item) => item.playQueueItemID == currentItemID);
|
||||
if (!_didInitialScroll && currentIndex > 0) {
|
||||
_didInitialScroll = true;
|
||||
scrollToCurrentItem(_scrollController, _firstItemKey, currentIndex);
|
||||
}
|
||||
|
||||
content = ListView.builder(
|
||||
controller: currentIndex > 0
|
||||
? ScrollController(initialScrollOffset: currentIndex * _kEstimatedItemHeight)
|
||||
: null,
|
||||
controller: _scrollController,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
@@ -50,6 +66,7 @@ class QueueSheet extends StatelessWidget {
|
||||
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
return FocusableListTile(
|
||||
key: index == 0 ? _firstItemKey : null,
|
||||
leading: _buildThumbnail(context, item, isCurrent),
|
||||
title: Text(
|
||||
item.title!,
|
||||
@@ -71,7 +88,7 @@ class QueueSheet extends StatelessWidget {
|
||||
),
|
||||
trailing: isCurrent ? AppIcon(Symbols.play_circle_rounded, fill: 1, color: primaryColor) : null,
|
||||
onTap: () {
|
||||
onItemSelected(item);
|
||||
widget.onItemSelected(item);
|
||||
OverlaySheetController.of(context).close();
|
||||
},
|
||||
);
|
||||
@@ -88,7 +105,7 @@ class QueueSheet extends StatelessWidget {
|
||||
if (item.thumb == null) return null;
|
||||
|
||||
// Try to get client for thumbnails, may fail in offline mode
|
||||
final client = _tryGetClient(context, item);
|
||||
final client = context.tryGetClientForServer(item.serverId);
|
||||
|
||||
return SizedBox(
|
||||
width: _kThumbWidth,
|
||||
@@ -133,8 +150,4 @@ class QueueSheet extends StatelessWidget {
|
||||
}
|
||||
return item.mediaType.name;
|
||||
}
|
||||
|
||||
static dynamic _tryGetClient(BuildContext context, PlexMetadata item) {
|
||||
return context.tryGetClientForServer(item.serverId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../../../mpv/mpv.dart';
|
||||
import '../../../i18n/strings.g.dart';
|
||||
import '../../../utils/scroll_utils.dart';
|
||||
import '../../../utils/track_label_builder.dart';
|
||||
import '../../../widgets/overlay_sheet.dart';
|
||||
import 'base_video_control_sheet.dart';
|
||||
@@ -125,7 +126,7 @@ class TrackSheet extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AudioColumn extends StatelessWidget {
|
||||
class _AudioColumn extends StatefulWidget {
|
||||
final List<AudioTrack> tracks;
|
||||
final TrackSelection selection;
|
||||
final Player player;
|
||||
@@ -140,18 +141,42 @@ class _AudioColumn extends StatelessWidget {
|
||||
required this.showHeader,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_AudioColumn> createState() => _AudioColumnState();
|
||||
}
|
||||
|
||||
class _AudioColumnState extends State<_AudioColumn> {
|
||||
final _firstItemKey = GlobalKey();
|
||||
final _scrollController = ScrollController();
|
||||
bool _didInitialScroll = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedId = selection.audio?.id ?? '';
|
||||
final selectedId = widget.selection.audio?.id ?? '';
|
||||
|
||||
if (!_didInitialScroll) {
|
||||
final selectedIndex = widget.tracks.indexWhere((t) => t.id == selectedId);
|
||||
if (selectedIndex > 0) {
|
||||
_didInitialScroll = true;
|
||||
scrollToCurrentItem(_scrollController, _firstItemKey, selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (showHeader) _ColumnHeader(label: t.videoControls.audioLabel),
|
||||
if (widget.showHeader) _ColumnHeader(label: t.videoControls.audioLabel),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: tracks.length,
|
||||
controller: _scrollController,
|
||||
itemCount: widget.tracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
final track = tracks[index];
|
||||
final track = widget.tracks[index];
|
||||
final label = TrackLabelBuilder.buildAudioLabel(
|
||||
title: track.title,
|
||||
language: track.language,
|
||||
@@ -161,11 +186,12 @@ class _AudioColumn extends StatelessWidget {
|
||||
);
|
||||
return TrackSelectionHelper.buildTrackTile<AudioTrack>(
|
||||
context: context,
|
||||
key: index == 0 ? _firstItemKey : null,
|
||||
label: label,
|
||||
isSelected: track.id == selectedId,
|
||||
onTap: () {
|
||||
player.selectAudioTrack(track);
|
||||
onTrackChanged?.call(track);
|
||||
widget.player.selectAudioTrack(track);
|
||||
widget.onTrackChanged?.call(track);
|
||||
OverlaySheetController.of(context).close();
|
||||
},
|
||||
);
|
||||
@@ -177,7 +203,7 @@ class _AudioColumn extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _SubtitleColumn extends StatelessWidget {
|
||||
class _SubtitleColumn extends StatefulWidget {
|
||||
final List<SubtitleTrack> tracks;
|
||||
final TrackSelection selection;
|
||||
final Player player;
|
||||
@@ -196,54 +222,80 @@ class _SubtitleColumn extends StatelessWidget {
|
||||
required this.showHeader,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_SubtitleColumn> createState() => _SubtitleColumnState();
|
||||
}
|
||||
|
||||
class _SubtitleColumnState extends State<_SubtitleColumn> {
|
||||
final _firstItemKey = GlobalKey();
|
||||
final _scrollController = ScrollController();
|
||||
bool _didInitialScroll = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedSub = selection.subtitle;
|
||||
final secondarySub = selection.secondarySubtitle;
|
||||
final selectedSub = widget.selection.subtitle;
|
||||
final secondarySub = widget.selection.secondarySubtitle;
|
||||
final isOffSelected = selectedSub == null || selectedSub.id == 'no';
|
||||
final hasSecondary = supportsSecondary && secondarySub != null;
|
||||
final hasSecondary = widget.supportsSecondary && secondarySub != null;
|
||||
|
||||
// +1 for "Off" row
|
||||
final itemCount = tracks.length + 1;
|
||||
final itemCount = widget.tracks.length + 1;
|
||||
|
||||
if (!_didInitialScroll && !isOffSelected) {
|
||||
// +1 because index 0 is the "Off" row
|
||||
final selectedIndex = widget.tracks.indexWhere((t) => t.id == selectedSub.id) + 1;
|
||||
if (selectedIndex > 0) {
|
||||
_didInitialScroll = true;
|
||||
scrollToCurrentItem(_scrollController, _firstItemKey, selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (showHeader) _ColumnHeader(label: t.videoControls.subtitlesLabel),
|
||||
if (widget.showHeader) _ColumnHeader(label: t.videoControls.subtitlesLabel),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
// "Off" row
|
||||
if (index == 0) {
|
||||
return TrackSelectionHelper.buildOffTile<SubtitleTrack>(
|
||||
context: context,
|
||||
key: _firstItemKey,
|
||||
isSelected: isOffSelected,
|
||||
onTap: () {
|
||||
// Turning off primary also clears secondary
|
||||
if (hasSecondary) {
|
||||
player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
widget.onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
}
|
||||
player.selectSubtitleTrack(SubtitleTrack.off);
|
||||
onTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSubtitleTrack(SubtitleTrack.off);
|
||||
widget.onTrackChanged?.call(SubtitleTrack.off);
|
||||
OverlaySheetController.of(context).close();
|
||||
},
|
||||
onLongPress: supportsSecondary && hasSecondary
|
||||
onLongPress: widget.supportsSecondary && hasSecondary
|
||||
? () {
|
||||
player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
widget.onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
}
|
||||
: null,
|
||||
onSecondaryTap: supportsSecondary && hasSecondary
|
||||
onSecondaryTap: widget.supportsSecondary && hasSecondary
|
||||
? () {
|
||||
player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
widget.onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
final track = tracks[index - 1];
|
||||
final track = widget.tracks[index - 1];
|
||||
final isPrimary = !isOffSelected && track.id == selectedSub.id;
|
||||
final isSecondary = hasSecondary && track.id == secondarySub.id;
|
||||
final label = TrackLabelBuilder.buildSubtitleLabel(
|
||||
@@ -255,7 +307,7 @@ class _SubtitleColumn extends StatelessWidget {
|
||||
|
||||
// Determine badge
|
||||
Widget? badge;
|
||||
if (supportsSecondary && hasSecondary) {
|
||||
if (widget.supportsSecondary && hasSecondary) {
|
||||
if (isPrimary) {
|
||||
badge = TrackSelectionHelper.buildTrackBadge(context, 1);
|
||||
} else if (isSecondary) {
|
||||
@@ -271,34 +323,34 @@ class _SubtitleColumn extends StatelessWidget {
|
||||
onTap: () {
|
||||
// If tapping a track that is currently the secondary, clear secondary first
|
||||
if (isSecondary) {
|
||||
player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
widget.onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
}
|
||||
player.selectSubtitleTrack(track);
|
||||
onTrackChanged?.call(track);
|
||||
widget.player.selectSubtitleTrack(track);
|
||||
widget.onTrackChanged?.call(track);
|
||||
OverlaySheetController.of(context).close();
|
||||
},
|
||||
onLongPress: supportsSecondary
|
||||
onLongPress: widget.supportsSecondary
|
||||
? () {
|
||||
if (isSecondary) {
|
||||
// Already secondary — clear it
|
||||
player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
widget.onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
} else if (!isPrimary) {
|
||||
// Set as secondary (don't close sheet so user sees badge update)
|
||||
player.selectSecondarySubtitleTrack(track);
|
||||
onSecondaryTrackChanged?.call(track);
|
||||
widget.player.selectSecondarySubtitleTrack(track);
|
||||
widget.onSecondaryTrackChanged?.call(track);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
onSecondaryTap: supportsSecondary
|
||||
onSecondaryTap: widget.supportsSecondary
|
||||
? () {
|
||||
if (isSecondary) {
|
||||
player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
widget.player.selectSecondarySubtitleTrack(SubtitleTrack.off);
|
||||
widget.onSecondaryTrackChanged?.call(SubtitleTrack.off);
|
||||
} else if (!isPrimary) {
|
||||
player.selectSecondarySubtitleTrack(track);
|
||||
onSecondaryTrackChanged?.call(track);
|
||||
widget.player.selectSecondarySubtitleTrack(track);
|
||||
widget.onSecondaryTrackChanged?.call(track);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
|
||||
Reference in New Issue
Block a user