mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add slider touch handlers
This commit is contained in:
parent
2c16fdad19
commit
856eaffda6
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -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 = {
|
||||
|
@ -18,60 +18,92 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<View>(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 (
|
||||
<Pressable
|
||||
onTouchStart={(event) => {
|
||||
// // prevent drag and drop of the UI.
|
||||
// event.preventDefault();
|
||||
<View
|
||||
ref={ref}
|
||||
// @ts-ignore Web only
|
||||
onMouseEnter={() => 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,
|
||||
)}
|
||||
>
|
||||
<View
|
||||
{...css({
|
||||
width: percent(100),
|
||||
height: ts(1),
|
||||
bg: (theme) => theme.overlay0,
|
||||
})}
|
||||
{...css([
|
||||
{
|
||||
width: percent(100),
|
||||
height: ts(1),
|
||||
bg: (theme) => theme.overlay0,
|
||||
},
|
||||
smallBar && { transform: [{ scaleY: 0.4 }] },
|
||||
])}
|
||||
>
|
||||
{subtleProgress && (
|
||||
{subtleProgress !== undefined && (
|
||||
<View
|
||||
{...css({
|
||||
bg: (theme) => theme.overlay1,
|
||||
@ -84,14 +116,21 @@ export const Slider = ({
|
||||
/>
|
||||
)}
|
||||
<View
|
||||
{...css({
|
||||
bg: (theme) => 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) => (
|
||||
<View
|
||||
@ -102,26 +141,34 @@ export const Slider = ({
|
||||
bottom: 0,
|
||||
left: percent(Math.min(100, (x / max) * 100)),
|
||||
bg: (theme) => theme.accent,
|
||||
width: ts(1),
|
||||
width: ts(0.5),
|
||||
height: ts(1),
|
||||
borderRadius: ts(0.5),
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<View
|
||||
{...css({
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
margin: "auto",
|
||||
left: calc(percent((progress / max) * 100), "-", ts(1)),
|
||||
bg: (theme) => 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),
|
||||
},
|
||||
},
|
||||
)}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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 (
|
||||
<Slider
|
||||
progress={progress}
|
||||
startSeek={() => setPlay(false)}
|
||||
endSeek={() => setPlay(true)}
|
||||
setProgress={setProgress}
|
||||
subtleProgress={buffered}
|
||||
max={duration}
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user