diff --git a/components/chapters/ChapterList.tsx b/components/chapters/ChapterList.tsx index c9143ee45..7c1d7a2fc 100644 --- a/components/chapters/ChapterList.tsx +++ b/components/chapters/ChapterList.tsx @@ -6,12 +6,13 @@ import { Ionicons } from "@expo/vector-icons"; import type { ChapterInfo } from "@jellyfin/sdk/lib/generated-client/models"; +import { useTranslation } from "react-i18next"; import { FlatList, Modal, Pressable, View } from "react-native"; import { Text } from "@/components/common/Text"; import { - chapterStartsMs, currentChapterIndex, formatChapterTime, + sortedChapters, } from "@/utils/chapters"; interface ChapterListProps { @@ -31,9 +32,9 @@ export function ChapterList({ onSeek, onClose, }: ChapterListProps) { - const starts = chapterStartsMs(chapters); + const { t } = useTranslation(); + const entries = sortedChapters(chapters); const activeIndex = currentChapterIndex(currentPositionMs, chapters); - const list = chapters ?? []; return ( - Chapters + {t("chapters.title")} String(i)} renderItem={({ item, index }) => { - const positionMs = starts[index] ?? 0; + const positionMs = item.positionMs; const isActive = index === activeIndex; return ( - {item.Name || `Chapter ${index + 1}`} + {item.chapter.Name || + t("chapters.chapter_number", { number: index + 1 })} {formatChapterTime(positionMs)} diff --git a/components/chapters/ChapterTicks.tsx b/components/chapters/ChapterTicks.tsx index 8e6571ea5..c83de0b42 100644 --- a/components/chapters/ChapterTicks.tsx +++ b/components/chapters/ChapterTicks.tsx @@ -30,9 +30,9 @@ export function ChapterTicks({ pointerEvents='none' style={{ position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }} > - {markers.map((marker) => ( + {markers.map((marker, index) => ( { }); }); +describe("sortedChapters", () => { + test("pairs each chapter with its ms start, sorted ascending", () => { + const a = ch(60_000, "C"); + const b = ch(0, "A"); + const c = ch(30_000, "B"); + expect(sortedChapters([a, b, c])).toEqual([ + { chapter: b, positionMs: 0 }, + { chapter: c, positionMs: 30_000 }, + { chapter: a, positionMs: 60_000 }, + ]); + }); + test("returns [] for null/undefined", () => { + expect(sortedChapters(null)).toEqual([]); + expect(sortedChapters(undefined)).toEqual([]); + }); +}); + describe("formatChapterTime", () => { test("formats m:ss and h:mm:ss", () => { expect(formatChapterTime(65_000)).toBe("1:05"); diff --git a/utils/chapters.ts b/utils/chapters.ts index bab4a8c5d..6e4b7f8da 100644 --- a/utils/chapters.ts +++ b/utils/chapters.ts @@ -14,6 +14,23 @@ export interface ChapterMarker { percent: number; } +export interface ChapterEntry { + chapter: ChapterInfo; + /** Chapter start, in milliseconds. */ + positionMs: number; +} + +/** Chapters paired with their millisecond start, sorted ascending by start. */ +export const sortedChapters = ( + chapters: ChapterInfo[] | null | undefined, +): ChapterEntry[] => + (chapters ?? []) + .map((chapter) => ({ + chapter, + positionMs: (chapter.StartPositionTicks ?? 0) / TICKS_PER_MS, + })) + .sort((a, b) => a.positionMs - b.positionMs); + /** Chapter start positions in milliseconds, ascending. */ export const chapterStartsMs = ( chapters: ChapterInfo[] | null | undefined,