Compare commits

...

1 Commits

Author SHA1 Message Date
Fredrik Burmester
0e73299429 Revert "feat: haptic feedback settings and custom hook" 2025-01-05 15:51:04 +01:00
20 changed files with 47 additions and 134 deletions

View File

@@ -13,7 +13,7 @@ import { SubtitleToggles } from "@/components/settings/SubtitleToggles";
import { UserInfo } from "@/components/settings/UserInfo"; import { UserInfo } from "@/components/settings/UserInfo";
import { useJellyfin } from "@/providers/JellyfinProvider"; import { useJellyfin } from "@/providers/JellyfinProvider";
import { clearLogs } from "@/utils/log"; import { clearLogs } from "@/utils/log";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { useNavigation, useRouter } from "expo-router"; import { useNavigation, useRouter } from "expo-router";
import { useEffect } from "react"; import { useEffect } from "react";
import { ScrollView, TouchableOpacity, View } from "react-native"; import { ScrollView, TouchableOpacity, View } from "react-native";
@@ -23,11 +23,10 @@ export default function settings() {
const router = useRouter(); const router = useRouter();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { logout } = useJellyfin(); const { logout } = useJellyfin();
const successHapticFeedback = useHaptic("success");
const onClearLogsClicked = async () => { const onClearLogsClicked = async () => {
clearLogs(); clearLogs();
successHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
}; };
const navigation = useNavigation(); const navigation = useNavigation();

View File

@@ -27,7 +27,7 @@ import {
getUserLibraryApi, getUserLibraryApi,
} from "@jellyfin/sdk/lib/utils/api"; } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { useFocusEffect, useGlobalSearchParams } from "expo-router"; import { useFocusEffect, useGlobalSearchParams } from "expo-router";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import React, { import React, {
@@ -68,11 +68,9 @@ export default function page() {
const { getDownloadedItem } = useDownload(); const { getDownloadedItem } = useDownload();
const revalidateProgressCache = useInvalidatePlaybackProgressCache(); const revalidateProgressCache = useInvalidatePlaybackProgressCache();
const lightHapticFeedback = useHaptic("light");
const setShowControls = useCallback((show: boolean) => { const setShowControls = useCallback((show: boolean) => {
_setShowControls(show); _setShowControls(show);
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}, []); }, []);
const { const {
@@ -177,7 +175,7 @@ export default function page() {
const togglePlay = useCallback(async () => { const togglePlay = useCallback(async () => {
if (!api) return; if (!api) return;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (isPlaying) { if (isPlaying) {
await videoRef.current?.pause(); await videoRef.current?.pause();

View File

@@ -17,7 +17,7 @@ import {
getUserLibraryApi, getUserLibraryApi,
} from "@jellyfin/sdk/lib/utils/api"; } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { useFocusEffect, useLocalSearchParams } from "expo-router"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
@@ -45,8 +45,6 @@ export default function page() {
const isSeeking = useSharedValue(false); const isSeeking = useSharedValue(false);
const cacheProgress = useSharedValue(0); const cacheProgress = useSharedValue(0);
const lightHapticFeedback = useHaptic("light");
const { const {
itemId, itemId,
audioIndex: audioIndexStr, audioIndex: audioIndexStr,
@@ -126,7 +124,7 @@ export default function page() {
const togglePlay = useCallback( const togglePlay = useCallback(
async (ticks: number) => { async (ticks: number) => {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (isPlaying) { if (isPlaying) {
videoRef.current?.pause(); videoRef.current?.pause();
await getPlaystateApi(api!).onPlaybackProgress({ await getPlaystateApi(api!).onPlaybackProgress({

View File

@@ -20,7 +20,7 @@ import {
getUserLibraryApi, getUserLibraryApi,
} from "@jellyfin/sdk/lib/utils/api"; } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { useFocusEffect, useLocalSearchParams } from "expo-router"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import React, { import React, {
@@ -48,7 +48,6 @@ const Player = () => {
const firstTime = useRef(true); const firstTime = useRef(true);
const revalidateProgressCache = useInvalidatePlaybackProgressCache(); const revalidateProgressCache = useInvalidatePlaybackProgressCache();
const lightHapticFeedback = useHaptic("light");
const [isPlaybackStopped, setIsPlaybackStopped] = useState(false); const [isPlaybackStopped, setIsPlaybackStopped] = useState(false);
const [showControls, _setShowControls] = useState(true); const [showControls, _setShowControls] = useState(true);
@@ -59,7 +58,7 @@ const Player = () => {
const setShowControls = useCallback((show: boolean) => { const setShowControls = useCallback((show: boolean) => {
_setShowControls(show); _setShowControls(show);
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}, []); }, []);
const progress = useSharedValue(0); const progress = useSharedValue(0);
@@ -168,7 +167,7 @@ const Player = () => {
const videoSource = useVideoSource(item, api, poster, stream?.url); const videoSource = useVideoSource(item, api, poster, stream?.url);
const togglePlay = useCallback(async () => { const togglePlay = useCallback(async () => {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (isPlaying) { if (isPlaying) {
videoRef.current?.pause(); videoRef.current?.pause();
await getPlaystateApi(api!).onPlaybackProgress({ await getPlaystateApi(api!).onPlaybackProgress({

View File

@@ -1,4 +1,4 @@
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import React, { PropsWithChildren, ReactNode, useMemo } from "react"; import React, { PropsWithChildren, ReactNode, useMemo } from "react";
import { Text, TouchableOpacity, View } from "react-native"; import { Text, TouchableOpacity, View } from "react-native";
import { Loader } from "./Loader"; import { Loader } from "./Loader";
@@ -43,8 +43,6 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
} }
}, [color]); }, [color]);
const lightHapticFeedback = useHaptic("light");
return ( return (
<TouchableOpacity <TouchableOpacity
className={` className={`
@@ -56,7 +54,7 @@ export const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
onPress={() => { onPress={() => {
if (!loading && !disabled && onPress) { if (!loading && !disabled && onPress) {
onPress(); onPress();
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
} }
}} }}
disabled={disabled || loading} disabled={disabled || loading}

View File

@@ -32,7 +32,7 @@ import Animated, {
import { Button } from "./Button"; import { Button } from "./Button";
import { SelectedOptions } from "./ItemContent"; import { SelectedOptions } from "./ItemContent";
import { chromecastProfile } from "@/utils/profiles/chromecast"; import { chromecastProfile } from "@/utils/profiles/chromecast";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
interface Props extends React.ComponentProps<typeof Button> { interface Props extends React.ComponentProps<typeof Button> {
item: BaseItemDto; item: BaseItemDto;
@@ -64,7 +64,6 @@ export const PlayButton: React.FC<Props> = ({
const widthProgress = useSharedValue(0); const widthProgress = useSharedValue(0);
const colorChangeProgress = useSharedValue(0); const colorChangeProgress = useSharedValue(0);
const [settings] = useSettings(); const [settings] = useSettings();
const lightHapticFeedback = useHaptic("light");
const goToPlayer = useCallback( const goToPlayer = useCallback(
(q: string, bitrateValue: number | undefined) => { (q: string, bitrateValue: number | undefined) => {
@@ -80,7 +79,7 @@ export const PlayButton: React.FC<Props> = ({
const onPress = useCallback(async () => { const onPress = useCallback(async () => {
if (!item) return; if (!item) return;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
const queryParams = new URLSearchParams({ const queryParams = new URLSearchParams({
itemId: item.Id!, itemId: item.Id!,

View File

@@ -6,7 +6,7 @@ import {
TouchableOpacity, TouchableOpacity,
TouchableOpacityProps, TouchableOpacityProps,
} from "react-native"; } from "react-native";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
interface Props extends TouchableOpacityProps { interface Props extends TouchableOpacityProps {
onPress?: () => void; onPress?: () => void;
@@ -29,11 +29,10 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
}) => { }) => {
const buttonSize = size === "large" ? "h-10 w-10" : "h-9 w-9"; const buttonSize = size === "large" ? "h-10 w-10" : "h-9 w-9";
const fillColorClass = fillColor === "primary" ? "bg-purple-600" : ""; const fillColorClass = fillColor === "primary" ? "bg-purple-600" : "";
const lightHapticFeedback = useHaptic("light");
const handlePress = () => { const handlePress = () => {
if (hapticFeedback) { if (hapticFeedback) {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
} }
onPress?.(); onPress?.();
}; };

View File

@@ -1,5 +1,5 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import React, { useCallback, useMemo } from "react"; import React, { useCallback, useMemo } from "react";
import { TouchableOpacity, TouchableOpacityProps, View } from "react-native"; import { TouchableOpacity, TouchableOpacityProps, View } from "react-native";
import { import {
@@ -26,7 +26,6 @@ export const EpisodeCard: React.FC<EpisodeCardProps> = ({ item, ...props }) => {
const { deleteFile } = useDownload(); const { deleteFile } = useDownload();
const { openFile } = useDownloadedFileOpener(); const { openFile } = useDownloadedFileOpener();
const { showActionSheetWithOptions } = useActionSheet(); const { showActionSheetWithOptions } = useActionSheet();
const successHapticFeedback = useHaptic("success");
const base64Image = useMemo(() => { const base64Image = useMemo(() => {
return storage.getString(item.Id!); return storage.getString(item.Id!);
@@ -42,7 +41,7 @@ export const EpisodeCard: React.FC<EpisodeCardProps> = ({ item, ...props }) => {
const handleDeleteFile = useCallback(() => { const handleDeleteFile = useCallback(() => {
if (item.Id) { if (item.Id) {
deleteFile(item.Id); deleteFile(item.Id);
successHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} }
}, [deleteFile, item.Id]); }, [deleteFile, item.Id]);

View File

@@ -3,7 +3,7 @@ import {
useActionSheet, useActionSheet,
} from "@expo/react-native-action-sheet"; } from "@expo/react-native-action-sheet";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import React, { useCallback, useMemo } from "react"; import React, { useCallback, useMemo } from "react";
import { TouchableOpacity, View } from "react-native"; import { TouchableOpacity, View } from "react-native";
@@ -28,7 +28,6 @@ export const MovieCard: React.FC<MovieCardProps> = ({ item }) => {
const { deleteFile } = useDownload(); const { deleteFile } = useDownload();
const { openFile } = useDownloadedFileOpener(); const { openFile } = useDownloadedFileOpener();
const { showActionSheetWithOptions } = useActionSheet(); const { showActionSheetWithOptions } = useActionSheet();
const successHapticFeedback = useHaptic("success");
const handleOpenFile = useCallback(() => { const handleOpenFile = useCallback(() => {
openFile(item); openFile(item);
@@ -44,7 +43,7 @@ export const MovieCard: React.FC<MovieCardProps> = ({ item }) => {
const handleDeleteFile = useCallback(() => { const handleDeleteFile = useCallback(() => {
if (item.Id) { if (item.Id) {
deleteFile(item.Id); deleteFile(item.Id);
successHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} }
}, [deleteFile, item.Id]); }, [deleteFile, item.Id]);

View File

@@ -22,7 +22,7 @@ import { itemRouter, TouchableItemRouter } from "../common/TouchableItemRouter";
import { Loader } from "../Loader"; import { Loader } from "../Loader";
import { Gesture, GestureDetector } from "react-native-gesture-handler"; import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { useRouter, useSegments } from "expo-router"; import { useRouter, useSegments } from "expo-router";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
interface Props extends ViewProps {} interface Props extends ViewProps {}
@@ -128,7 +128,6 @@ const RenderItem: React.FC<{ item: BaseItemDto }> = ({ item }) => {
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
const router = useRouter(); const router = useRouter();
const screenWidth = Dimensions.get("screen").width; const screenWidth = Dimensions.get("screen").width;
const lightHapticFeedback = useHaptic("light");
const uri = useMemo(() => { const uri = useMemo(() => {
if (!api) return null; if (!api) return null;
@@ -154,7 +153,7 @@ const RenderItem: React.FC<{ item: BaseItemDto }> = ({ item }) => {
const handleRoute = useCallback(() => { const handleRoute = useCallback(() => {
if (!from) return; if (!from) return;
const url = itemRouter(item, from); const url = itemRouter(item, from);
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
// @ts-ignore // @ts-ignore
if (url) router.push(url); if (url) router.push(url);
}, [item, from]); }, [item, from]);

View File

@@ -178,15 +178,6 @@ export const OtherSettings: React.FC = () => {
} }
/> />
</ListItem> </ListItem>
<ListItem title="Disable Haptic Feedback">
<Switch
value={settings.disableHapticFeedback}
onValueChange={(value) =>
updateSettings({ disableHapticFeedback: value })
}
/>
</ListItem>
</ListGroup> </ListGroup>
); );
}; };

View File

@@ -7,7 +7,7 @@ import {
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";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import React, { useCallback, useRef, useState } from "react"; import React, { useCallback, useRef, useState } from "react";
import { Alert, View, ViewProps } from "react-native"; import { Alert, View, ViewProps } from "react-native";
@@ -23,8 +23,6 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
const [user] = useAtom(userAtom); const [user] = useAtom(userAtom);
const [quickConnectCode, setQuickConnectCode] = useState<string>(); const [quickConnectCode, setQuickConnectCode] = useState<string>();
const bottomSheetModalRef = useRef<BottomSheetModal>(null); const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const successHapticFeedback = useHaptic("success");
const errorHapticFeedback = useHaptic("error");
const renderBackdrop = useCallback( const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => ( (props: BottomSheetBackdropProps) => (
@@ -45,16 +43,16 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
userId: user?.Id, userId: user?.Id,
}); });
if (res.status === 200) { if (res.status === 200) {
successHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
Alert.alert("Success", "Quick connect authorized"); Alert.alert("Success", "Quick connect authorized");
setQuickConnectCode(undefined); setQuickConnectCode(undefined);
bottomSheetModalRef?.current?.close(); bottomSheetModalRef?.current?.close();
} else { } else {
errorHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
Alert.alert("Error", "Invalid code"); Alert.alert("Error", "Invalid code");
} }
} catch (e) { } catch (e) {
errorHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
Alert.alert("Error", "Invalid code"); Alert.alert("Error", "Invalid code");
} }
} }

View File

@@ -4,7 +4,7 @@ import { useDownload } from "@/providers/DownloadProvider";
import { clearLogs } from "@/utils/log"; import { clearLogs } from "@/utils/log";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { View } from "react-native"; import { View } from "react-native";
import * as Progress from "react-native-progress"; import * as Progress from "react-native-progress";
import { toast } from "sonner-native"; import { toast } from "sonner-native";
@@ -13,8 +13,6 @@ import { ListItem } from "../list/ListItem";
export const StorageSettings = () => { export const StorageSettings = () => {
const { deleteAllFiles, appSizeUsage } = useDownload(); const { deleteAllFiles, appSizeUsage } = useDownload();
const successHapticFeedback = useHaptic("success");
const errorHapticFeedback = useHaptic("error");
const { data: size, isLoading: appSizeLoading } = useQuery({ const { data: size, isLoading: appSizeLoading } = useQuery({
queryKey: ["appSize", appSizeUsage], queryKey: ["appSize", appSizeUsage],
@@ -31,9 +29,9 @@ export const StorageSettings = () => {
const onDeleteClicked = async () => { const onDeleteClicked = async () => {
try { try {
await deleteAllFiles(); await deleteAllFiles();
successHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} catch (e) { } catch (e) {
errorHapticFeedback(); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
toast.error("Error deleting files"); toast.error("Error deleting files");
} }
}; };

View File

@@ -29,7 +29,7 @@ import {
BaseItemDto, BaseItemDto,
MediaSourceInfo, MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client"; } from "@jellyfin/sdk/lib/generated-client";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import { Image } from "expo-image"; import { Image } from "expo-image";
import { useLocalSearchParams, useRouter } from "expo-router"; import { useLocalSearchParams, useRouter } from "expo-router";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
@@ -157,12 +157,10 @@ export const Controls: React.FC<Props> = ({
isVlc isVlc
); );
const lightHapticFeedback = useHaptic("light");
const goToPreviousItem = useCallback(() => { const goToPreviousItem = useCallback(() => {
if (!previousItem || !settings) return; if (!previousItem || !settings) return;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
const previousIndexes: previousIndexes = { const previousIndexes: previousIndexes = {
subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined, subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined,
@@ -200,7 +198,7 @@ export const Controls: React.FC<Props> = ({
const goToNextItem = useCallback(() => { const goToNextItem = useCallback(() => {
if (!nextItem || !settings) return; if (!nextItem || !settings) return;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
const previousIndexes: previousIndexes = { const previousIndexes: previousIndexes = {
subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined, subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined,
@@ -328,7 +326,7 @@ export const Controls: React.FC<Props> = ({
const handleSkipBackward = useCallback(async () => { const handleSkipBackward = useCallback(async () => {
if (!settings?.rewindSkipTime) return; if (!settings?.rewindSkipTime) return;
wasPlayingRef.current = isPlaying; wasPlayingRef.current = isPlaying;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
try { try {
const curr = progress.value; const curr = progress.value;
if (curr !== undefined) { if (curr !== undefined) {
@@ -346,7 +344,7 @@ export const Controls: React.FC<Props> = ({
const handleSkipForward = useCallback(async () => { const handleSkipForward = useCallback(async () => {
if (!settings?.forwardSkipTime) return; if (!settings?.forwardSkipTime) return;
wasPlayingRef.current = isPlaying; wasPlayingRef.current = isPlaying;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
try { try {
const curr = progress.value; const curr = progress.value;
if (curr !== undefined) { if (curr !== undefined) {
@@ -363,7 +361,7 @@ export const Controls: React.FC<Props> = ({
const toggleIgnoreSafeAreas = useCallback(() => { const toggleIgnoreSafeAreas = useCallback(() => {
setIgnoreSafeAreas((prev) => !prev); setIgnoreSafeAreas((prev) => !prev);
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}, []); }, []);
const memoizedRenderBubble = useCallback(() => { const memoizedRenderBubble = useCallback(() => {
@@ -442,7 +440,7 @@ export const Controls: React.FC<Props> = ({
const gotoItem = await getItemById(api, itemId); const gotoItem = await getItemById(api, itemId);
if (!settings || !gotoItem) return; if (!settings || !gotoItem) return;
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
const previousIndexes: previousIndexes = { const previousIndexes: previousIndexes = {
subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined, subtitleIndex: subtitleIndex ? parseInt(subtitleIndex) : undefined,
@@ -586,7 +584,7 @@ export const Controls: React.FC<Props> = ({
)} )}
<TouchableOpacity <TouchableOpacity
onPress={async () => { onPress={async () => {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
router.back(); router.back();
}} }}
className="aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2" className="aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2"

View File

@@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider";
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin"; import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
import { writeToLog } from "@/utils/log"; import { writeToLog } from "@/utils/log";
import { msToSeconds, secondsToMs } from "@/utils/time"; import { msToSeconds, secondsToMs } from "@/utils/time";
import { useHaptic } from "./useHaptic"; import * as Haptics from "expo-haptics";
interface CreditTimestamps { interface CreditTimestamps {
Introduction: { Introduction: {
@@ -29,7 +29,6 @@ export const useCreditSkipper = (
) => { ) => {
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
const [showSkipCreditButton, setShowSkipCreditButton] = useState(false); const [showSkipCreditButton, setShowSkipCreditButton] = useState(false);
const lightHapticFeedback = useHaptic("light");
if (isVlc) { if (isVlc) {
currentTime = msToSeconds(currentTime); currentTime = msToSeconds(currentTime);
@@ -80,7 +79,7 @@ export const useCreditSkipper = (
if (!creditTimestamps) return; if (!creditTimestamps) return;
console.log(`Skipping credits to ${creditTimestamps.Credits.End}`); console.log(`Skipping credits to ${creditTimestamps.Credits.End}`);
try { try {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
wrappedSeek(creditTimestamps.Credits.End); wrappedSeek(creditTimestamps.Credits.End);
setTimeout(() => { setTimeout(() => {
play(); play();

View File

@@ -1,54 +0,0 @@
import { useCallback, useMemo } from "react";
import { Platform } from "react-native";
import * as Haptics from "expo-haptics";
import { useSettings } from "@/utils/atoms/settings";
export type HapticFeedbackType =
| "light"
| "medium"
| "heavy"
| "selection"
| "success"
| "warning"
| "error";
export const useHaptic = (feedbackType: HapticFeedbackType = "selection") => {
const [settings] = useSettings();
const createHapticHandler = useCallback(
(type: Haptics.ImpactFeedbackStyle) => {
return Platform.OS === "web" ? () => {} : () => Haptics.impactAsync(type);
},
[]
);
const createNotificationFeedback = useCallback(
(type: Haptics.NotificationFeedbackType) => {
return Platform.OS === "web"
? () => {}
: () => Haptics.notificationAsync(type);
},
[]
);
const hapticHandlers = useMemo(
() => ({
light: createHapticHandler(Haptics.ImpactFeedbackStyle.Light),
medium: createHapticHandler(Haptics.ImpactFeedbackStyle.Medium),
heavy: createHapticHandler(Haptics.ImpactFeedbackStyle.Heavy),
selection: Platform.OS === "web" ? () => {} : Haptics.selectionAsync,
success: createNotificationFeedback(
Haptics.NotificationFeedbackType.Success
),
warning: createNotificationFeedback(
Haptics.NotificationFeedbackType.Warning
),
error: createNotificationFeedback(Haptics.NotificationFeedbackType.Error),
}),
[createHapticHandler, createNotificationFeedback]
);
if (settings?.disableHapticFeedback) {
return () => {};
}
return hapticHandlers[feedbackType];
};

View File

@@ -5,7 +5,7 @@ import { apiAtom } from "@/providers/JellyfinProvider";
import { getAuthHeaders } from "@/utils/jellyfin/jellyfin"; import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
import { writeToLog } from "@/utils/log"; import { writeToLog } from "@/utils/log";
import { msToSeconds, secondsToMs } from "@/utils/time"; import { msToSeconds, secondsToMs } from "@/utils/time";
import { useHaptic } from "./useHaptic"; import * as Haptics from "expo-haptics";
interface IntroTimestamps { interface IntroTimestamps {
EpisodeId: string; EpisodeId: string;
@@ -33,7 +33,6 @@ export const useIntroSkipper = (
if (isVlc) { if (isVlc) {
currentTime = msToSeconds(currentTime); currentTime = msToSeconds(currentTime);
} }
const lightHapticFeedback = useHaptic("light");
const wrappedSeek = (seconds: number) => { const wrappedSeek = (seconds: number) => {
if (isVlc) { if (isVlc) {
@@ -79,7 +78,7 @@ export const useIntroSkipper = (
const skipIntro = useCallback(() => { const skipIntro = useCallback(() => {
if (!introTimestamps) return; if (!introTimestamps) return;
try { try {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
wrappedSeek(introTimestamps.IntroEnd); wrappedSeek(introTimestamps.IntroEnd);
setTimeout(() => { setTimeout(() => {
play(); play();

View File

@@ -3,14 +3,13 @@ import { markAsNotPlayed } from "@/utils/jellyfin/playstate/markAsNotPlayed";
import { markAsPlayed } from "@/utils/jellyfin/playstate/markAsPlayed"; import { markAsPlayed } from "@/utils/jellyfin/playstate/markAsPlayed";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useHaptic } from "./useHaptic"; import * as Haptics from "expo-haptics";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
export const useMarkAsPlayed = (item: BaseItemDto) => { export const useMarkAsPlayed = (item: BaseItemDto) => {
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom); const [user] = useAtom(userAtom);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const lightHapticFeedback = useHaptic("light");
const invalidateQueries = () => { const invalidateQueries = () => {
const queriesToInvalidate = [ const queriesToInvalidate = [
@@ -30,7 +29,7 @@ export const useMarkAsPlayed = (item: BaseItemDto) => {
}; };
const markAsPlayedStatus = async (played: boolean) => { const markAsPlayedStatus = async (played: boolean) => {
lightHapticFeedback(); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
// Optimistic update // Optimistic update
queryClient.setQueryData( queryClient.setQueryData(

View File

@@ -48,7 +48,7 @@ import useImageStorage from "@/hooks/useImageStorage";
import { storage } from "@/utils/mmkv"; import { storage } from "@/utils/mmkv";
import useDownloadHelper from "@/utils/download"; import useDownloadHelper from "@/utils/download";
import { FileInfo } from "expo-file-system"; import { FileInfo } from "expo-file-system";
import { useHaptic } from "@/hooks/useHaptic"; import * as Haptics from "expo-haptics";
import * as Application from "expo-application"; import * as Application from "expo-application";
export type DownloadedItem = { export type DownloadedItem = {
@@ -78,8 +78,6 @@ function useDownloadProvider() {
const [processes, setProcesses] = useAtom<JobStatus[]>(processesAtom); const [processes, setProcesses] = useAtom<JobStatus[]>(processesAtom);
const successHapticFeedback = useHaptic("success");
const authHeader = useMemo(() => { const authHeader = useMemo(() => {
return api?.accessToken; return api?.accessToken;
}, [api]); }, [api]);
@@ -534,7 +532,9 @@ function useDownloadProvider() {
if (i.Id) return deleteFile(i.Id); if (i.Id) return deleteFile(i.Id);
return; return;
}) })
).then(() => successHapticFeedback()); ).then(() =>
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
);
}; };
const cleanCacheDirectory = async () => { const cleanCacheDirectory = async () => {

View File

@@ -84,7 +84,6 @@ export type Settings = {
downloadMethod: "optimized" | "remux"; downloadMethod: "optimized" | "remux";
autoDownload: boolean; autoDownload: boolean;
showCustomMenuLinks: boolean; showCustomMenuLinks: boolean;
disableHapticFeedback: boolean;
subtitleSize: number; subtitleSize: number;
remuxConcurrentLimit: 1 | 2 | 3 | 4; remuxConcurrentLimit: 1 | 2 | 3 | 4;
safeAreaInControlsEnabled: boolean; safeAreaInControlsEnabled: boolean;
@@ -123,7 +122,6 @@ const loadSettings = (): Settings => {
downloadMethod: "remux", downloadMethod: "remux",
autoDownload: false, autoDownload: false,
showCustomMenuLinks: false, showCustomMenuLinks: false,
disableHapticFeedback: false,
subtitleSize: Platform.OS === "ios" ? 60 : 100, subtitleSize: Platform.OS === "ios" ? 60 : 100,
remuxConcurrentLimit: 1, remuxConcurrentLimit: 1,
safeAreaInControlsEnabled: true, safeAreaInControlsEnabled: true,