Merge pull request #450 from micahmo/feature/chapter-tooltips

Show previous and next chapter names as tooltips
This commit is contained in:
edde746
2026-02-13 13:09:26 +01:00
committed by GitHub
@@ -458,16 +458,24 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
),
),
// Previous chapter
Opacity(
opacity: widget.canControl ? 1.0 : 0.5,
child: _buildFocusableButton(
focusNode: _prevChapterFocusNode,
index: 1,
icon: Symbols.fast_rewind_rounded,
color: widget.chapters.isNotEmpty && widget.canControl ? Colors.white : Colors.white54,
onPressed: widget.canControl && widget.chapters.isNotEmpty ? widget.onSeekToPreviousChapter : null,
semanticLabel: t.videoControls.previousChapterButton,
),
StreamBuilder<Duration>(
stream: widget.player.streams.position,
initialData: widget.player.state.position,
builder: (context, posSnapshot) {
final prevLabel = _getPreviousChapterLabel(posSnapshot.data ?? Duration.zero);
return Opacity(
opacity: widget.canControl ? 1.0 : 0.5,
child: _buildFocusableButton(
focusNode: _prevChapterFocusNode,
index: 1,
icon: Symbols.fast_rewind_rounded,
color: widget.chapters.isNotEmpty && widget.canControl ? Colors.white : Colors.white54,
onPressed: widget.canControl && widget.chapters.isNotEmpty ? widget.onSeekToPreviousChapter : null,
semanticLabel: t.videoControls.previousChapterButton,
tooltip: prevLabel,
),
);
},
),
// Skip backward
Opacity(
@@ -517,16 +525,24 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
),
),
// Next chapter
Opacity(
opacity: widget.canControl ? 1.0 : 0.5,
child: _buildFocusableButton(
focusNode: _nextChapterFocusNode,
index: 5,
icon: Symbols.fast_forward_rounded,
color: widget.chapters.isNotEmpty && widget.canControl ? Colors.white : Colors.white54,
onPressed: widget.canControl && widget.chapters.isNotEmpty ? widget.onSeekToNextChapter : null,
semanticLabel: t.videoControls.nextChapterButton,
),
StreamBuilder<Duration>(
stream: widget.player.streams.position,
initialData: widget.player.state.position,
builder: (context, posSnapshot) {
final nextLabel = _getNextChapterLabel(posSnapshot.data ?? Duration.zero);
return Opacity(
opacity: widget.canControl ? 1.0 : 0.5,
child: _buildFocusableButton(
focusNode: _nextChapterFocusNode,
index: 5,
icon: Symbols.fast_forward_rounded,
color: widget.chapters.isNotEmpty && widget.canControl ? Colors.white : Colors.white54,
onPressed: widget.canControl && widget.chapters.isNotEmpty ? widget.onSeekToNextChapter : null,
semanticLabel: t.videoControls.nextChapterButton,
tooltip: nextLabel,
),
);
},
),
// Next item
Opacity(
@@ -632,6 +648,32 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
);
}
/// Returns the label of the next chapter the user would seek to, or null.
String? _getNextChapterLabel(Duration position) {
if (widget.chapters.isEmpty) return null;
final currentPositionMs = position.inMilliseconds;
for (final chapter in widget.chapters) {
final chapterStart = chapter.startTimeOffset ?? 0;
if (chapterStart > currentPositionMs) {
return chapter.label;
}
}
return null;
}
/// Returns the label of the previous chapter the user would seek to, or null.
String? _getPreviousChapterLabel(Duration position) {
if (widget.chapters.isEmpty) return null;
final currentPositionMs = position.inMilliseconds;
for (int i = widget.chapters.length - 1; i >= 0; i--) {
final chapterStart = widget.chapters[i].startTimeOffset ?? 0;
if (currentPositionMs > chapterStart + 3000) {
return widget.chapters[i].label;
}
}
return null;
}
Widget _buildFocusableButton({
required FocusNode focusNode,
required int index,
@@ -640,6 +682,7 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
required String semanticLabel,
Color color = Colors.white,
double iconSize = 24,
String? tooltip,
}) {
return FocusableWrapper(
focusNode: focusNode,
@@ -657,6 +700,7 @@ class DesktopVideoControlsState extends State<DesktopVideoControls> {
child: IconButton(
icon: AppIcon(icon, fill: 1, color: color, size: iconSize),
iconSize: iconSize,
tooltip: tooltip,
onPressed: onPressed,
),
),