mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-31 19:18:26 +01:00
feat: improve quick connect bottom sheet (#1000)
Co-authored-by: Gauvain <68083474+Gauvino@users.noreply.github.com> Co-authored-by: lostb1t <coding-mosses0z@icloud.com>
This commit is contained in:
123
components/inputs/PinInput.tsx
Normal file
123
components/inputs/PinInput.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
||||||
|
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
||||||
|
import { StyleSheet, Text, type TextInputProps, View } from "react-native";
|
||||||
|
|
||||||
|
interface PinInputProps extends Omit<TextInputProps, "value" | "onChangeText"> {
|
||||||
|
value: string;
|
||||||
|
onChangeText: (text: string) => void;
|
||||||
|
length?: number;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PinInputRef {
|
||||||
|
focus: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PinInputComponent = React.forwardRef<PinInputRef, PinInputProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const {
|
||||||
|
value,
|
||||||
|
onChangeText,
|
||||||
|
length = 6,
|
||||||
|
style,
|
||||||
|
autoFocus,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const inputRef = useRef<any>(null);
|
||||||
|
const activeIndex = value.length;
|
||||||
|
|
||||||
|
const handlePress = useCallback(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
focus: () => inputRef.current?.focus(),
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, style]}>
|
||||||
|
<BottomSheetTextInput
|
||||||
|
ref={inputRef}
|
||||||
|
value={value}
|
||||||
|
onChangeText={onChangeText}
|
||||||
|
keyboardType='number-pad'
|
||||||
|
maxLength={length}
|
||||||
|
style={styles.hiddenInput}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<View style={styles.cells} onTouchStart={handlePress}>
|
||||||
|
{Array(length)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, i) => (
|
||||||
|
<View
|
||||||
|
key={i}
|
||||||
|
style={[
|
||||||
|
styles.cell,
|
||||||
|
i === activeIndex && styles.activeCell,
|
||||||
|
i === activeIndex - 1 && styles.filledCell,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={styles.digit}>{value[i]}</Text>
|
||||||
|
{i === activeIndex && <View style={styles.cursor} />}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
PinInputComponent.displayName = "PinInput";
|
||||||
|
|
||||||
|
export const PinInput = PinInputComponent;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
hiddenInput: {
|
||||||
|
position: "absolute",
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
cells: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
cell: {
|
||||||
|
width: 40,
|
||||||
|
height: 48,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#374151",
|
||||||
|
borderRadius: 8,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: "#1F2937",
|
||||||
|
},
|
||||||
|
activeCell: {
|
||||||
|
borderColor: "#6366F1",
|
||||||
|
},
|
||||||
|
filledCell: {
|
||||||
|
borderColor: "#4B5563",
|
||||||
|
},
|
||||||
|
digit: {
|
||||||
|
fontSize: 24,
|
||||||
|
color: "white",
|
||||||
|
fontWeight: "500",
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
position: "absolute",
|
||||||
|
width: 2,
|
||||||
|
height: 24,
|
||||||
|
backgroundColor: "#6366F1",
|
||||||
|
animation: "blink 1s infinite",
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
BottomSheetBackdrop,
|
BottomSheetBackdrop,
|
||||||
type BottomSheetBackdropProps,
|
type BottomSheetBackdropProps,
|
||||||
BottomSheetModal,
|
BottomSheetModal,
|
||||||
BottomSheetTextInput,
|
|
||||||
BottomSheetView,
|
BottomSheetView,
|
||||||
} from "@gorhom/bottom-sheet";
|
} from "@gorhom/bottom-sheet";
|
||||||
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
@@ -10,17 +9,19 @@ import { useAtom } from "jotai";
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useRef, useState } from "react";
|
import { useCallback, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Alert, View, type ViewProps } from "react-native";
|
import { Alert, Platform, View, type ViewProps } from "react-native";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
|
import { PinInput } from "../inputs/PinInput";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
import { ListItem } from "../list/ListItem";
|
import { ListItem } from "../list/ListItem";
|
||||||
|
|
||||||
interface Props extends ViewProps {}
|
interface Props extends ViewProps {}
|
||||||
|
|
||||||
export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
||||||
|
const isTv = Platform.isTV;
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const [quickConnectCode, setQuickConnectCode] = useState<string>();
|
const [quickConnectCode, setQuickConnectCode] = useState<string>();
|
||||||
@@ -73,11 +74,17 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
}
|
}
|
||||||
}, [api, user, quickConnectCode]);
|
}, [api, user, quickConnectCode]);
|
||||||
|
|
||||||
|
if (isTv) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...props}>
|
<View {...props}>
|
||||||
<ListGroup title={t("home.settings.quick_connect.quick_connect_title")}>
|
<ListGroup title={t("home.settings.quick_connect.quick_connect_title")}>
|
||||||
<ListItem
|
<ListItem
|
||||||
onPress={() => bottomSheetModalRef?.current?.present()}
|
onPress={() => {
|
||||||
|
// Reset the code when opening the sheet
|
||||||
|
setQuickConnectCode("");
|
||||||
|
bottomSheetModalRef?.current?.present();
|
||||||
|
}}
|
||||||
title={t("home.settings.quick_connect.authorize_button")}
|
title={t("home.settings.quick_connect.authorize_button")}
|
||||||
textColor='blue'
|
textColor='blue'
|
||||||
/>
|
/>
|
||||||
@@ -93,6 +100,9 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
backdropComponent={renderBackdrop}
|
backdropComponent={renderBackdrop}
|
||||||
|
keyboardBehavior='interactive'
|
||||||
|
keyboardBlurBehavior='restore'
|
||||||
|
android_keyboardInputMode='adjustResize'
|
||||||
>
|
>
|
||||||
<BottomSheetView>
|
<BottomSheetView>
|
||||||
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
|
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
|
||||||
@@ -102,16 +112,17 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='flex flex-col space-y-2'>
|
<View className='flex flex-col space-y-2'>
|
||||||
<View className='p-4 border border-neutral-800 rounded-xl bg-neutral-900 w-full'>
|
<View className='p-4 border border-neutral-800 rounded-xl bg-neutral-900 w-full space-y-4'>
|
||||||
<BottomSheetTextInput
|
<Text className='text-neutral-400 text-center'>
|
||||||
style={{ color: "white" }}
|
{t(
|
||||||
clearButtonMode='always'
|
|
||||||
placeholder={t(
|
|
||||||
"home.settings.quick_connect.enter_the_quick_connect_code",
|
"home.settings.quick_connect.enter_the_quick_connect_code",
|
||||||
)}
|
)}
|
||||||
placeholderTextColor='#9CA3AF'
|
</Text>
|
||||||
value={quickConnectCode}
|
<PinInput
|
||||||
|
value={quickConnectCode || ""}
|
||||||
onChangeText={setQuickConnectCode}
|
onChangeText={setQuickConnectCode}
|
||||||
|
style={{ paddingHorizontal: 16 }}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user