fix(chapters): address review findings + trickplay polish

Copilot + CodeRabbit review findings:
- React.memo ChapterTicks and ChapterList (project guideline: hot-path
  components must use React.memo to cut redraw work during control
  updates).
- chapterNameAt now sorts the chapter array once instead of twice per
  call. The previous version went through currentChapterIndex
  (chapterStartsMs + sort) then sortedChapters (sort again). Runs on
  every playback tick, so the duplicate work added up.
- Import getUserLibraryApi from the public barrel
  (@jellyfin/sdk/lib/utils/api) instead of the deep internal path
  (@jellyfin/sdk/lib/utils/api/user-library-api) to match the rest of
  the codebase and avoid coupling to SDK file layout.

TrickplayBubble polish:
- Sit just above the slider (bottom: 0) so the bubble no longer overlaps
  the progress bar.
- Move the chapter-name + timestamp overlay to the bottom-left of the
  preview frame, smaller font, in front of the surrounding overlays
  (zIndex + elevation).

BottomControls cleanup:
- Drop dev-only "pick one to test" comment in favour of a one-line note
  on TICK_HEIGHT.
- Inline scrubMs into its useMemo callback so the scrub-chapter-name
  lookup only recomputes while a slide is active.
This commit is contained in:
Gauvain
2026-05-27 20:08:21 +02:00
parent 558fb41833
commit e3f4eea132
6 changed files with 21 additions and 10 deletions

View File

@@ -9,7 +9,7 @@ import type {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api/user-library-api";
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
import { type Href } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai";

View File

@@ -6,7 +6,7 @@
import { Ionicons } from "@expo/vector-icons";
import type { ChapterInfo } from "@jellyfin/sdk/lib/generated-client/models";
import { useEffect, useMemo, useRef } from "react";
import { memo, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { FlatList, Modal, Pressable, StyleSheet, View } from "react-native";
import { Text } from "@/components/common/Text";
@@ -30,7 +30,7 @@ interface ChapterListProps {
const ROW_HEIGHT = 48;
export function ChapterList({
function ChapterListComponent({
visible,
chapters,
currentPositionMs,
@@ -151,6 +151,8 @@ export function ChapterList({
);
}
export const ChapterList = memo(ChapterListComponent);
const styles = StyleSheet.create({
backdrop: {
flex: 1,

View File

@@ -4,7 +4,7 @@
* so the slider underneath still receives touches.
*/
import { useState } from "react";
import { memo, useState } from "react";
import { type LayoutChangeEvent, PixelRatio, View } from "react-native";
import type { ChapterMarker } from "@/utils/chapters";
@@ -19,7 +19,7 @@ interface ChapterTicksProps {
width?: number;
}
export function ChapterTicks({
function ChapterTicksComponent({
markers,
// Semi-transparent black contrasts against both the filled progress
// (#fff) and the unfilled track (rgba(255,255,255,0.2)) so the ticks
@@ -83,3 +83,5 @@ export function ChapterTicks({
</View>
);
}
export const ChapterTicks = memo(ChapterTicksComponent);

View File

@@ -189,7 +189,6 @@ export const BottomControls: FC<BottomControlsProps> = ({
onPress={() => setChapterListVisible(true)}
hitSlop={10}
className='justify-center mr-4'
style={{ marginTop: 10 }}
accessibilityRole='button'
accessibilityLabel={t("chapters.open")}
>

View File

@@ -52,8 +52,9 @@ export const TrickplayBubble: FC<TrickplayBubbleProps> = ({
style={{
position: "absolute",
left: -62,
// Drop the bubble closer to the slider — less floating-high feel.
bottom: -20,
// Sit just above the slider — high enough not to overlap the
// progress bar, low enough to feel anchored to the thumb.
bottom: 0,
paddingTop: 12,
paddingBottom: 5,
width: tileWidth * 1.5,