mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-23 23:36:54 +01:00
fix(chapters): sort chapter list entries, localize strings, fix tick keys
This commit is contained in:
@@ -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 (
|
||||
<Modal
|
||||
@@ -69,17 +70,17 @@ export function ChapterList({
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: "#fff", fontSize: 17, fontWeight: "700" }}>
|
||||
Chapters
|
||||
{t("chapters.title")}
|
||||
</Text>
|
||||
<Pressable onPress={onClose} hitSlop={10}>
|
||||
<Ionicons name='close' size={24} color='#fff' />
|
||||
</Pressable>
|
||||
</View>
|
||||
<FlatList
|
||||
data={list}
|
||||
data={entries}
|
||||
keyExtractor={(_, i) => String(i)}
|
||||
renderItem={({ item, index }) => {
|
||||
const positionMs = starts[index] ?? 0;
|
||||
const positionMs = item.positionMs;
|
||||
const isActive = index === activeIndex;
|
||||
return (
|
||||
<Pressable
|
||||
@@ -104,7 +105,8 @@ export function ChapterList({
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.Name || `Chapter ${index + 1}`}
|
||||
{item.chapter.Name ||
|
||||
t("chapters.chapter_number", { number: index + 1 })}
|
||||
</Text>
|
||||
<Text style={{ color: "#999", fontSize: 13, marginLeft: 12 }}>
|
||||
{formatChapterTime(positionMs)}
|
||||
|
||||
@@ -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) => (
|
||||
<View
|
||||
key={marker.positionMs}
|
||||
key={`${marker.positionMs}-${index}`}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: `${marker.percent}%`,
|
||||
|
||||
@@ -610,6 +610,10 @@
|
||||
"downloaded_file_no": "No",
|
||||
"downloaded_file_cancel": "Cancel"
|
||||
},
|
||||
"chapters": {
|
||||
"title": "Chapters",
|
||||
"chapter_number": "Chapter {{number}}"
|
||||
},
|
||||
"item_card": {
|
||||
"next_up": "Next Up",
|
||||
"no_items_to_display": "No Items to Display",
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
chapterMarkers,
|
||||
currentChapterIndex,
|
||||
formatChapterTime,
|
||||
sortedChapters,
|
||||
} from "./chapters";
|
||||
|
||||
// Helper: a ChapterInfo with a start in milliseconds.
|
||||
@@ -48,6 +49,23 @@ describe("currentChapterIndex", () => {
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user