diff --git a/front/apps/mobile/package.json b/front/apps/mobile/package.json
index 6de8d41f..e3daf666 100644
--- a/front/apps/mobile/package.json
+++ b/front/apps/mobile/package.json
@@ -35,7 +35,7 @@
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "~3.18.0",
"react-native-svg": "13.4.0",
- "yoshiki": "0.3.1"
+ "yoshiki": "0.3.2"
},
"devDependencies": {
"@babel/core": "^7.19.3",
diff --git a/front/apps/web/package.json b/front/apps/web/package.json
index cab06450..7e801e89 100644
--- a/front/apps/web/package.json
+++ b/front/apps/web/package.json
@@ -42,7 +42,7 @@
"react-native-web": "^0.18.10",
"solito": "^2.0.5",
"superjson": "^1.11.0",
- "yoshiki": "0.3.1",
+ "yoshiki": "0.3.2",
"zod": "^3.19.1"
},
"devDependencies": {
diff --git a/front/packages/primitives/src/icons.tsx b/front/packages/primitives/src/icons.tsx
index 094daa84..98a1f7f5 100644
--- a/front/packages/primitives/src/icons.tsx
+++ b/front/packages/primitives/src/icons.tsx
@@ -19,10 +19,10 @@
*/
import { ComponentProps, ComponentType } from "react";
-import { Platform, PressableProps, ViewStyle } from "react-native";
+import { Pressable, Platform, PressableProps, ViewStyle } from "react-native";
import { SvgProps } from "react-native-svg";
import { YoshikiStyle } from "yoshiki/dist/type";
-import { Pressable, px, useYoshiki } from "yoshiki/native";
+import { px, useYoshiki } from "yoshiki/native";
import { ts } from "./utils";
type IconProps = {
diff --git a/front/packages/primitives/src/slider.tsx b/front/packages/primitives/src/slider.tsx
index fc7d2c49..2b67cc3b 100644
--- a/front/packages/primitives/src/slider.tsx
+++ b/front/packages/primitives/src/slider.tsx
@@ -18,60 +18,92 @@
* along with Kyoo. If not, see .
*/
-import { useState } from "react";
-import { Platform, Pressable, View } from "react-native";
+import { useRef, useState } from "react";
+import { Platform, View } from "react-native";
import { percent, Stylable, useYoshiki } from "yoshiki/native";
import { ts } from "./utils";
-const calc =
- Platform.OS === "web"
- ? (first: number, operator: "+" | "-" | "*" | "/", second: number): number =>
- `calc(${first} ${operator} ${second})` as unknown as number
- : (first: number, operator: "+" | "-" | "*" | "/", second: number): number => {
- switch (operator) {
- case "+":
- return first + second;
- case "-":
- return first - second;
- case "*":
- return first * second;
- case "/":
- return first / second;
- }
- };
-
export const Slider = ({
progress,
subtleProgress,
max = 100,
markers,
+ setProgress,
+ startSeek,
+ endSeek,
...props
-}: { progress: number; max?: number; subtleProgress?: number; markers?: number[] } & Stylable) => {
+}: {
+ progress: number;
+ max?: number;
+ subtleProgress?: number;
+ markers?: number[];
+ setProgress: (progress: number) => void;
+ startSeek?: () => void;
+ endSeek?: () => void;
+} & Stylable) => {
const { css } = useYoshiki();
+ const ref = useRef(null);
+ const [layout, setLayout] = useState({ x: 0, width: 0 });
const [isSeeking, setSeek] = useState(false);
+ const [isHover, setHover] = useState(false);
+ const [isFocus, setFocus] = useState(false);
+ const smallBar = !(isSeeking || isHover || isFocus);
+ // TODO keyboard handling (left, right, up, down)
return (
- {
- // // prevent drag and drop of the UI.
- // event.preventDefault();
+ setHover(true)}
+ // @ts-ignore Web only
+ onMouseLeave={() => setHover(false)}
+ // TODO: This does not work
+ tabindex={0}
+ onFocus={() => setFocus(true)}
+ onBlur={() => setFocus(false)}
+ onStartShouldSetResponder={() => true}
+ onResponderGrant={() => {
setSeek(true);
+ startSeek?.call(null);
}}
+ onResponderRelease={() => {
+ setSeek(false);
+ endSeek?.call(null);
+ }}
+ onResponderMove={(event) => {
+ event.preventDefault();
+ const locationX = Platform.select({
+ android: event.nativeEvent.pageX - layout.x,
+ default: event.nativeEvent.locationX,
+ });
+ setProgress(Math.max(0, Math.min(locationX / layout.width, 100)) * max);
+ }}
+ onLayout={() =>
+ ref.current?.measure((_, __, width, ___, pageX) =>
+ setLayout({ width: width, x: pageX }),
+ )
+ }
{...css(
{
paddingVertical: ts(1),
+ focus: {
+ shadowRadius: 0,
+ },
},
props,
)}
>
theme.overlay0,
- })}
+ {...css([
+ {
+ width: percent(100),
+ height: ts(1),
+ bg: (theme) => theme.overlay0,
+ },
+ smallBar && { transform: [{ scaleY: 0.4 }] },
+ ])}
>
- {subtleProgress && (
+ {subtleProgress !== undefined && (
theme.overlay1,
@@ -84,14 +116,21 @@ export const Slider = ({
/>
)}
theme.accent,
- position: "absolute",
- top: 0,
- bottom: 0,
- left: 0,
- width: percent((progress / max) * 100),
- })}
+ {...css(
+ {
+ bg: (theme) => theme.accent,
+ position: "absolute",
+ top: 0,
+ bottom: 0,
+ left: 0,
+ },
+ {
+ // In an inline style because yoshiki's insertion can not catch up with the constant redraw
+ style: {
+ width: percent((progress / max) * 100),
+ },
+ },
+ )}
/>
{markers?.map((x) => (
theme.accent,
- width: ts(1),
+ width: ts(0.5),
height: ts(1),
- borderRadius: ts(0.5),
})}
/>
))}
theme.accent,
- width: ts(2),
- height: ts(2),
- borderRadius: ts(1),
- })}
+ {...css(
+ [
+ {
+ position: "absolute",
+ top: 0,
+ bottom: 0,
+ marginY: ts(.5),
+ bg: (theme) => theme.accent,
+ width: ts(2),
+ height: ts(2),
+ borderRadius: ts(1),
+ },
+ smallBar && { opacity: 0 },
+ ],
+ {
+ style: {
+ left: percent((progress / max) * 100),
+ },
+ },
+ )}
/>
-
+
);
};
diff --git a/front/packages/ui/src/player/components/left-buttons.tsx b/front/packages/ui/src/player/components/left-buttons.tsx
index 0c898566..005e495d 100644
--- a/front/packages/ui/src/player/components/left-buttons.tsx
+++ b/front/packages/ui/src/player/components/left-buttons.tsx
@@ -142,6 +142,6 @@ const ProgressText = () => {
const toTimerString = (timer?: number, duration?: number) => {
if (timer === undefined) return "??:??";
if (!duration) duration = timer;
- if (duration >= 3600) return new Date(timer).toISOString().substring(11, 19);
+ if (duration >= 3600_000) return new Date(timer).toISOString().substring(11, 19);
return new Date(timer).toISOString().substring(14, 19);
};
diff --git a/front/packages/ui/src/player/components/progress-bar.tsx b/front/packages/ui/src/player/components/progress-bar.tsx
index 6cd6c38a..3fbdf05b 100644
--- a/front/packages/ui/src/player/components/progress-bar.tsx
+++ b/front/packages/ui/src/player/components/progress-bar.tsx
@@ -20,20 +20,23 @@
import { Chapter } from "@kyoo/models";
import { ts, Slider } from "@kyoo/primitives";
-import { useAtom, useAtomValue } from "jotai";
+import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { useEffect, useRef, useState } from "react";
import { NativeTouchEvent, Pressable, View } from "react-native";
import { useYoshiki, px, percent } from "yoshiki/native";
-import { bufferedAtom, durationAtom, progressAtom } from "../state";
+import { bufferedAtom, durationAtom, playAtom, progressAtom } from "../state";
export const ProgressBar = ({ chapters }: { chapters?: Chapter[] }) => {
const [progress, setProgress] = useAtom(progressAtom);
const buffered = useAtomValue(bufferedAtom);
const duration = useAtomValue(durationAtom);
+ const setPlay = useSetAtom(playAtom);
return (
setPlay(false)}
+ endSeek={() => setPlay(true)}
setProgress={setProgress}
subtleProgress={buffered}
max={duration}
diff --git a/front/yarn.lock b/front/yarn.lock
index c9161b13..469a631b 100644
--- a/front/yarn.lock
+++ b/front/yarn.lock
@@ -9992,7 +9992,7 @@ __metadata:
react-native-svg: 13.4.0
react-native-svg-transformer: ^1.0.0
typescript: ^4.6.3
- yoshiki: 0.3.1
+ yoshiki: 0.3.2
languageName: unknown
linkType: soft
@@ -13677,7 +13677,7 @@ __metadata:
superjson: ^1.11.0
typescript: ^4.9.3
webpack: ^5.75.0
- yoshiki: 0.3.1
+ yoshiki: 0.3.2
zod: ^3.19.1
languageName: unknown
linkType: soft
@@ -14002,9 +14002,9 @@ __metadata:
languageName: node
linkType: hard
-"yoshiki@npm:0.3.1":
- version: 0.3.1
- resolution: "yoshiki@npm:0.3.1"
+"yoshiki@npm:0.3.2":
+ version: 0.3.2
+ resolution: "yoshiki@npm:0.3.2"
dependencies:
"@types/node": 18.x.x
"@types/react": 18.x.x
@@ -14019,7 +14019,7 @@ __metadata:
optional: true
react-native-web:
optional: true
- checksum: 9448b628b61bbcc4485af7aed667a1c0f8490a2066fa35953b4a02126f1d31d94f90e27a592797f8ecedc0ce2220976a7651ba989f4ff3c68513496b2f9fdd0b
+ checksum: a723473e5593e9871d4903cfc9186240c6745cd97ca45ba9f1f0233e7717afefb908c08a0a6e2dc37ac2bbb0c9df269364767db6c885a8f2e19748d295c42a57
languageName: node
linkType: hard