mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 03:28:27 +01:00
chore: linting fixes && github actions for linting (#612)
This commit is contained in:
@@ -26,11 +26,11 @@ export default function menuLinks() {
|
||||
const getMenuLinks = useCallback(async () => {
|
||||
try {
|
||||
const response = await api?.axiosInstance.get(
|
||||
api?.basePath + "/web/config.json",
|
||||
`${api?.basePath}/web/config.json`,
|
||||
);
|
||||
const config = response?.data;
|
||||
|
||||
if (!config && !config.hasOwnProperty("menuLinks")) {
|
||||
if (!config && !Object.hasOwn(config, "menuLinks")) {
|
||||
console.error("Menu links not found");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function SearchLayout() {
|
||||
backgroundColor: "black",
|
||||
},
|
||||
headerBlurEffect: "prominent",
|
||||
headerTransparent: Platform.OS === "ios" ? true : false,
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function IndexLayout() {
|
||||
{!Platform.isTV && (
|
||||
<>
|
||||
<Chromecast.Chromecast />
|
||||
{user && user.Policy?.IsAdministrator && <SessionsButton />}
|
||||
{user?.Policy?.IsAdministrator && <SessionsButton />}
|
||||
<SettingsButton />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function page() {
|
||||
try {
|
||||
return (
|
||||
downloadedFiles
|
||||
?.filter((f) => f.item.SeriesId == seriesId)
|
||||
?.filter((f) => f.item.SeriesId === seriesId)
|
||||
?.sort(
|
||||
(a, b) => a?.item.ParentIndexNumber! - b.item.ParentIndexNumber!,
|
||||
) || []
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function page() {
|
||||
</View>
|
||||
);
|
||||
|
||||
if (!sessions || sessions.length == 0)
|
||||
if (!sessions || sessions.length === 0)
|
||||
return (
|
||||
<View className='h-full w-full flex justify-center items-center'>
|
||||
<Text className='text-lg text-neutral-500'>
|
||||
@@ -175,7 +175,7 @@ const SessionCard = ({ session }: SessionCardProps) => {
|
||||
</View>
|
||||
<View className='align-bottom bg-gray-800 h-1'>
|
||||
<View
|
||||
className={`bg-purple-600 h-full`}
|
||||
className={"bg-purple-600 h-full"}
|
||||
style={{
|
||||
width: `${getProgressPercentage()}%`,
|
||||
}}
|
||||
@@ -298,7 +298,7 @@ const TranscodingStreamView = ({
|
||||
const TranscodingView = ({ session }: SessionCardProps) => {
|
||||
const videoStream = useMemo(() => {
|
||||
return session.NowPlayingItem?.MediaStreams?.filter(
|
||||
(s) => s.Type == "Video",
|
||||
(s) => s.Type === "Video",
|
||||
)[0];
|
||||
}, [session]);
|
||||
|
||||
@@ -318,7 +318,7 @@ const TranscodingView = ({ session }: SessionCardProps) => {
|
||||
|
||||
const isTranscoding = useMemo(() => {
|
||||
return (
|
||||
session.PlayState?.PlayMethod == "Transcode" && session.TranscodingInfo
|
||||
session.PlayState?.PlayMethod === "Transcode" && session.TranscodingInfo
|
||||
);
|
||||
}, [session.PlayState?.PlayMethod, session.TranscodingInfo]);
|
||||
|
||||
@@ -341,9 +341,7 @@ const TranscodingView = ({ session }: SessionCardProps) => {
|
||||
codec: session.TranscodingInfo?.VideoCodec,
|
||||
}}
|
||||
isTranscoding={
|
||||
isTranscoding && !session.TranscodingInfo?.IsVideoDirect
|
||||
? true
|
||||
: false
|
||||
!!(isTranscoding && !session.TranscodingInfo?.IsVideoDirect)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -360,24 +358,20 @@ const TranscodingView = ({ session }: SessionCardProps) => {
|
||||
audioChannels: session.TranscodingInfo?.AudioChannels?.toString(),
|
||||
}}
|
||||
isTranscoding={
|
||||
isTranscoding && !session.TranscodingInfo?.IsVideoDirect
|
||||
? true
|
||||
: false
|
||||
!!(isTranscoding && !session.TranscodingInfo?.IsVideoDirect)
|
||||
}
|
||||
/>
|
||||
|
||||
{subtitleStream && (
|
||||
<>
|
||||
<TranscodingStreamView
|
||||
title='Subtitle'
|
||||
isTranscoding={false}
|
||||
properties={{
|
||||
language: subtitleStream?.Language,
|
||||
codec: subtitleStream?.Codec,
|
||||
}}
|
||||
transcodeValue={null}
|
||||
/>
|
||||
</>
|
||||
<TranscodingStreamView
|
||||
title='Subtitle'
|
||||
isTranscoding={false}
|
||||
properties={{
|
||||
language: subtitleStream?.Language,
|
||||
codec: subtitleStream?.Codec,
|
||||
}}
|
||||
transcodeValue={null}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,73 +1,77 @@
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import {LogLevel, useLog, writeErrorLog} from "@/utils/log";
|
||||
import { FilterButton } from "@/components/filters/FilterButton";
|
||||
import { LogLevel, useLog, writeErrorLog } from "@/utils/log";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { useNavigation } from "expo-router";
|
||||
import * as Sharing from "expo-sharing";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {ScrollView, TouchableOpacity, View} from "react-native";
|
||||
import { ScrollView, TouchableOpacity, View } from "react-native";
|
||||
import Collapsible from "react-native-collapsible";
|
||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||
import {FilterButton} from "@/components/filters/FilterButton";
|
||||
import {useNavigation} from "expo-router";
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as Sharing from 'expo-sharing';
|
||||
import {Loader} from "@/components/Loader";
|
||||
|
||||
export default function page() {
|
||||
const navigation = useNavigation();
|
||||
const { logs } = useLog();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const defaultLevels: LogLevel[] = ["INFO", "ERROR", "DEBUG", "WARN"]
|
||||
const defaultLevels: LogLevel[] = ["INFO", "ERROR", "DEBUG", "WARN"];
|
||||
const codeBlockStyle = {
|
||||
backgroundColor: '#000',
|
||||
backgroundColor: "#000",
|
||||
padding: 10,
|
||||
fontFamily: 'monospace',
|
||||
maxHeight: 300
|
||||
}
|
||||
fontFamily: "monospace",
|
||||
maxHeight: 300,
|
||||
};
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [state, setState] = useState<Record<string, boolean>>({})
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [state, setState] = useState<Record<string, boolean>>({});
|
||||
|
||||
const [order, setOrder] = useState<"asc" | "desc">("desc");
|
||||
const [levels, setLevels] = useState<LogLevel[]>(defaultLevels);
|
||||
|
||||
const filteredLogs = useMemo(
|
||||
() => logs
|
||||
?.filter(log => levels.includes(log.level))
|
||||
// Already in asc order as they are recorded. just reverse for desc
|
||||
?.[order === "desc" ? "reverse" : "concat"]?.(),
|
||||
[logs, order, levels]
|
||||
)
|
||||
() =>
|
||||
logs
|
||||
?.filter((log) => levels.includes(log.level))
|
||||
?.[
|
||||
// Already in asc order as they are recorded. just reverse for desc
|
||||
order === "desc" ? "reverse" : "concat"
|
||||
]?.(),
|
||||
[logs, order, levels],
|
||||
);
|
||||
|
||||
// Sharing it as txt while its formatted allows us to share it with many more applications
|
||||
const share = useCallback(async () => {
|
||||
const uri = FileSystem.documentDirectory + "logs.txt"
|
||||
const uri = `${FileSystem.documentDirectory}logs.txt`;
|
||||
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
FileSystem.writeAsStringAsync(uri, JSON.stringify(filteredLogs))
|
||||
.then(() => {
|
||||
setLoading(false)
|
||||
Sharing.shareAsync(uri, {mimeType: "txt", UTI: "txt"})
|
||||
setLoading(false);
|
||||
Sharing.shareAsync(uri, { mimeType: "txt", UTI: "txt" });
|
||||
})
|
||||
.catch((e) => writeErrorLog("Something went wrong attempting to export", e))
|
||||
.finally(() => setLoading(false))
|
||||
}, [filteredLogs])
|
||||
.catch((e) =>
|
||||
writeErrorLog("Something went wrong attempting to export", e),
|
||||
)
|
||||
.finally(() => setLoading(false));
|
||||
}, [filteredLogs]);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
loading
|
||||
? <Loader/>
|
||||
: (
|
||||
headerRight: () =>
|
||||
loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<TouchableOpacity onPress={share}>
|
||||
<Text>{t("home.settings.logs.export_logs")}</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
),
|
||||
),
|
||||
});
|
||||
}, [share, loading]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className="flex flex-row justify-end py-2 px-4 space-x-2">
|
||||
<View className='flex flex-row justify-end py-2 px-4 space-x-2'>
|
||||
<FilterButton
|
||||
id='order'
|
||||
queryKey='log'
|
||||
@@ -93,25 +97,30 @@ export default function page() {
|
||||
<ScrollView className='pb-4 px-4'>
|
||||
<View className='flex flex-col space-y-2'>
|
||||
{filteredLogs?.map((log, index) => (
|
||||
<View
|
||||
className='bg-neutral-900 rounded-xl p-3'
|
||||
key={index}
|
||||
>
|
||||
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
|
||||
<TouchableOpacity
|
||||
disabled={!log.data}
|
||||
onPress={() => setState((v) => ({...v, [log.timestamp]: !v[log.timestamp]}))}
|
||||
onPress={() =>
|
||||
setState((v) => ({
|
||||
...v,
|
||||
[log.timestamp]: !v[log.timestamp],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<View className="flex flex-row justify-between">
|
||||
<View className='flex flex-row justify-between'>
|
||||
<Text
|
||||
className={`mb-1
|
||||
${log.level === "INFO" && "text-blue-500"}
|
||||
${log.level === "ERROR" && "text-red-500"}
|
||||
${log.level === "DEBUG" && "text-purple-500"}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
{log.level}
|
||||
</Text>
|
||||
|
||||
<Text className="text-xs">{new Date(log.timestamp).toLocaleString()}</Text>
|
||||
<Text className='text-xs'>
|
||||
{new Date(log.timestamp).toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
<Text uiTextView selectable className='text-xs'>
|
||||
{log.message}
|
||||
@@ -121,14 +130,14 @@ export default function page() {
|
||||
{log.data && (
|
||||
<>
|
||||
{!state[log.timestamp] && (
|
||||
<Text className="text-xs mt-0.5">{t("home.settings.logs.click_for_more_info")}</Text>
|
||||
<Text className='text-xs mt-0.5'>
|
||||
{t("home.settings.logs.click_for_more_info")}
|
||||
</Text>
|
||||
)}
|
||||
<Collapsible collapsed={!state[log.timestamp]}>
|
||||
<View className="mt-2 flex flex-col space-y-2">
|
||||
<ScrollView className="rounded-xl" style={codeBlockStyle}>
|
||||
<Text>
|
||||
{JSON.stringify(log.data, null, 2)}
|
||||
</Text>
|
||||
<View className='mt-2 flex flex-col space-y-2'>
|
||||
<ScrollView className='rounded-xl' style={codeBlockStyle}>
|
||||
<Text>{JSON.stringify(log.data, null, 2)}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Collapsible>
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function page() {
|
||||
showText={!pluginSettings?.searchEngine?.locked}
|
||||
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
|
||||
>
|
||||
<View className={`flex flex-row items-center bg-neutral-900 h-11 pr-4`}>
|
||||
<View className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}>
|
||||
<Text className='mr-4'>
|
||||
{t("home.settings.plugins.marlin_search.url")}
|
||||
</Text>
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function page() {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedUrl = newVal.endsWith("/") ? newVal : newVal + "/";
|
||||
const updatedUrl = newVal.endsWith("/") ? newVal : `${newVal}/`;
|
||||
|
||||
updateSettings({
|
||||
optimizedVersionsServerUrl: updatedUrl,
|
||||
|
||||
@@ -133,7 +133,7 @@ const page: React.FC = () => {
|
||||
queryFn={fetchItems}
|
||||
queryKey={["actor", "movies", actorId]}
|
||||
/>
|
||||
<View className='h-12'></View>
|
||||
<View className='h-12' />
|
||||
</View>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
|
||||
@@ -157,9 +157,8 @@ const page: React.FC = () => {
|
||||
|
||||
if (accumulatedItems < totalItems) {
|
||||
return lastPage?.Items?.length * pages.length;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
initialPageParam: 0,
|
||||
enabled: !!api && !!user?.Id && !!collection,
|
||||
@@ -412,7 +411,7 @@ const page: React.FC = () => {
|
||||
width: 10,
|
||||
height: 10,
|
||||
}}
|
||||
></View>
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -93,19 +93,19 @@ const Page: React.FC = () => {
|
||||
height: item?.Type === "Episode" ? 300 : 450,
|
||||
}}
|
||||
className='bg-transparent rounded-lg mb-4 w-full'
|
||||
></View>
|
||||
<View className='h-6 bg-neutral-900 rounded mb-4 w-14'></View>
|
||||
<View className='h-10 bg-neutral-900 rounded-lg mb-2 w-1/2'></View>
|
||||
<View className='h-3 bg-neutral-900 rounded mb-3 w-8'></View>
|
||||
/>
|
||||
<View className='h-6 bg-neutral-900 rounded mb-4 w-14' />
|
||||
<View className='h-10 bg-neutral-900 rounded-lg mb-2 w-1/2' />
|
||||
<View className='h-3 bg-neutral-900 rounded mb-3 w-8' />
|
||||
<View className='flex flex-row space-x-1 mb-8'>
|
||||
<View className='h-6 bg-neutral-900 rounded mb-3 w-14'></View>
|
||||
<View className='h-6 bg-neutral-900 rounded mb-3 w-14'></View>
|
||||
<View className='h-6 bg-neutral-900 rounded mb-3 w-14'></View>
|
||||
<View className='h-6 bg-neutral-900 rounded mb-3 w-14' />
|
||||
<View className='h-6 bg-neutral-900 rounded mb-3 w-14' />
|
||||
<View className='h-6 bg-neutral-900 rounded mb-3 w-14' />
|
||||
</View>
|
||||
<View className='h-3 bg-neutral-900 rounded w-2/3 mb-1'></View>
|
||||
<View className='h-10 bg-neutral-900 rounded-lg w-full mb-2'></View>
|
||||
<View className='h-12 bg-neutral-900 rounded-lg w-full mb-2'></View>
|
||||
<View className='h-24 bg-neutral-900 rounded-lg mb-1 w-full'></View>
|
||||
<View className='h-3 bg-neutral-900 rounded w-2/3 mb-1' />
|
||||
<View className='h-10 bg-neutral-900 rounded-lg w-full mb-2' />
|
||||
<View className='h-12 bg-neutral-900 rounded-lg w-full mb-2' />
|
||||
<View className='h-24 bg-neutral-900 rounded-lg mb-1 w-full' />
|
||||
</Animated.View>
|
||||
{item && <ItemContent item={item} />}
|
||||
</View>
|
||||
|
||||
@@ -33,9 +33,11 @@ export default function page() {
|
||||
};
|
||||
|
||||
return jellyseerrApi?.discover(
|
||||
(type == DiscoverSliderType.NETWORKS
|
||||
? Endpoints.DISCOVER_TV_NETWORK
|
||||
: Endpoints.DISCOVER_MOVIES_STUDIO) + `/${companyId}`,
|
||||
`${
|
||||
type === DiscoverSliderType.NETWORKS
|
||||
? Endpoints.DISCOVER_TV_NETWORK
|
||||
: Endpoints.DISCOVER_MOVIES_STUDIO
|
||||
}/${companyId}`,
|
||||
params,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function page() {
|
||||
};
|
||||
|
||||
return jellyseerrApi?.discover(
|
||||
type == DiscoverSliderType.MOVIE_GENRES
|
||||
type === DiscoverSliderType.MOVIE_GENRES
|
||||
? Endpoints.DISCOVER_MOVIES
|
||||
: Endpoints.DISCOVER_TV,
|
||||
params,
|
||||
|
||||
@@ -240,7 +240,7 @@ const Page: React.FC = () => {
|
||||
<GenreTags genres={details?.genres?.map((g) => g.name) || []} />
|
||||
</View>
|
||||
{isLoading || isFetching ? (
|
||||
<Button loading={true} disabled={true} color='purple'></Button>
|
||||
<Button loading={true} disabled={true} color='purple' />
|
||||
) : canRequest ? (
|
||||
<Button color='purple' onPress={request}>
|
||||
{t("jellyseerr.request_button")}
|
||||
|
||||
@@ -124,7 +124,7 @@ export default function page() {
|
||||
height: HOUR_HEIGHT,
|
||||
}}
|
||||
className='bg-neutral-800'
|
||||
></View>
|
||||
/>
|
||||
{channels?.Items?.map((c, i) => (
|
||||
<View className='h-16 w-16 mr-4 rounded-lg overflow-hidden' key={i}>
|
||||
<ItemImage
|
||||
|
||||
@@ -87,23 +87,21 @@ const page: React.FC = () => {
|
||||
<View className='flex flex-row items-center space-x-2'>
|
||||
<AddToFavorites item={item} />
|
||||
{!Platform.isTV && (
|
||||
<>
|
||||
<DownloadItems
|
||||
size='large'
|
||||
title={t("item_card.download.download_series")}
|
||||
items={allEpisodes || []}
|
||||
MissingDownloadIconComponent={() => (
|
||||
<Ionicons name='download' size={22} color='white' />
|
||||
)}
|
||||
DownloadedIconComponent={() => (
|
||||
<Ionicons
|
||||
name='checkmark-done-outline'
|
||||
size={24}
|
||||
color='#9333ea'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
<DownloadItems
|
||||
size='large'
|
||||
title={t("item_card.download.download_series")}
|
||||
items={allEpisodes || []}
|
||||
MissingDownloadIconComponent={() => (
|
||||
<Ionicons name='download' size={22} color='white' />
|
||||
)}
|
||||
DownloadedIconComponent={() => (
|
||||
<Ionicons
|
||||
name='checkmark-done-outline'
|
||||
size={24}
|
||||
color='#9333ea'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
),
|
||||
@@ -127,20 +125,18 @@ const page: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
logo={
|
||||
<>
|
||||
{logoUrl ? (
|
||||
<Image
|
||||
source={{
|
||||
uri: logoUrl,
|
||||
}}
|
||||
style={{
|
||||
height: 130,
|
||||
width: "100%",
|
||||
resizeMode: "contain",
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
logoUrl ? (
|
||||
<Image
|
||||
source={{
|
||||
uri: logoUrl,
|
||||
}}
|
||||
style={{
|
||||
height: 130,
|
||||
width: "100%",
|
||||
resizeMode: "contain",
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<View className='flex flex-col pt-4'>
|
||||
|
||||
@@ -216,9 +216,8 @@ const Page = () => {
|
||||
|
||||
if (accumulatedItems < totalItems) {
|
||||
return lastPage?.Items?.length * pages.length;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
initialPageParam: 0,
|
||||
enabled: !!api && !!user?.Id && !!library,
|
||||
@@ -478,7 +477,7 @@ const Page = () => {
|
||||
width: 10,
|
||||
height: 10,
|
||||
}}
|
||||
></View>
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function IndexLayout() {
|
||||
headerLargeStyle: {
|
||||
backgroundColor: "black",
|
||||
},
|
||||
headerTransparent: Platform.OS === "ios" ? true : false,
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
headerRight: () =>
|
||||
!pluginSettings?.libraryOptions?.locked &&
|
||||
@@ -159,7 +159,7 @@ export default function IndexLayout() {
|
||||
updateSettings({
|
||||
libraryOptions: {
|
||||
...settings.libraryOptions,
|
||||
showTitles: newValue === "on" ? true : false,
|
||||
showTitles: newValue === "on",
|
||||
},
|
||||
});
|
||||
}}
|
||||
@@ -176,7 +176,7 @@ export default function IndexLayout() {
|
||||
updateSettings({
|
||||
libraryOptions: {
|
||||
...settings.libraryOptions,
|
||||
showStats: newValue === "on" ? true : false,
|
||||
showStats: newValue === "on",
|
||||
},
|
||||
});
|
||||
}}
|
||||
@@ -200,7 +200,7 @@ export default function IndexLayout() {
|
||||
title: "",
|
||||
headerShown: true,
|
||||
headerBlurEffect: "prominent",
|
||||
headerTransparent: Platform.OS === "ios" ? true : false,
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
/>
|
||||
@@ -213,7 +213,7 @@ export default function IndexLayout() {
|
||||
title: "",
|
||||
headerShown: true,
|
||||
headerBlurEffect: "prominent",
|
||||
headerTransparent: Platform.OS === "ios" ? true : false,
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function index() {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
}}
|
||||
className='bg-neutral-800 mx-2 my-4'
|
||||
></View>
|
||||
/>
|
||||
) : (
|
||||
<View className='h-4' />
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function SearchLayout() {
|
||||
backgroundColor: "black",
|
||||
},
|
||||
headerBlurEffect: "prominent",
|
||||
headerTransparent: Platform.OS === "ios" ? true : false,
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
/>
|
||||
@@ -33,7 +33,7 @@ export default function SearchLayout() {
|
||||
title: "",
|
||||
headerShown: true,
|
||||
headerBlurEffect: "prominent",
|
||||
headerTransparent: Platform.OS === "ios" ? true : false,
|
||||
headerTransparent: Platform.OS === "ios",
|
||||
headerShadowVisible: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -13,8 +13,7 @@ import SeriesPoster from "@/components/posters/SeriesPoster";
|
||||
import { LoadingSkeleton } from "@/components/search/LoadingSkeleton";
|
||||
import { SearchItemWrapper } from "@/components/search/SearchItemWrapper";
|
||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { sortOrderOptions } from "@/utils/atoms/filters";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { eventBus } from "@/utils/eventBus";
|
||||
import type {
|
||||
@@ -64,7 +63,6 @@ export default function search() {
|
||||
const [debouncedSearch] = useDebounce(search, 500);
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
|
||||
const [settings] = useSettings();
|
||||
const { jellyseerrApi } = useJellyseerr();
|
||||
@@ -83,7 +81,9 @@ export default function search() {
|
||||
}, [settings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (q && q.length > 0) setSearch(q);
|
||||
if (q && q.length > 0) {
|
||||
setSearch(q);
|
||||
}
|
||||
}, [q]);
|
||||
|
||||
const searchFn = useCallback(
|
||||
@@ -94,7 +94,9 @@ export default function search() {
|
||||
types: BaseItemKind[];
|
||||
query: string;
|
||||
}): Promise<BaseItemDto[]> => {
|
||||
if (!api || !query) return [];
|
||||
if (!api || !query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
if (searchEngine === "Jellyfin") {
|
||||
@@ -105,28 +107,31 @@ export default function search() {
|
||||
});
|
||||
|
||||
return (searchApi.data.SearchHints as BaseItemDto[]) || [];
|
||||
} else {
|
||||
if (!settings?.marlinServerUrl) return [];
|
||||
|
||||
const url = `${
|
||||
settings.marlinServerUrl
|
||||
}/search?q=${encodeURIComponent(query)}&includeItemTypes=${types
|
||||
.map((type) => encodeURIComponent(type))
|
||||
.join("&includeItemTypes=")}`;
|
||||
|
||||
const response1 = await axios.get(url);
|
||||
|
||||
const ids = response1.data.ids;
|
||||
|
||||
if (!ids || !ids.length) return [];
|
||||
|
||||
const response2 = await getItemsApi(api).getItems({
|
||||
ids,
|
||||
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
|
||||
});
|
||||
|
||||
return (response2.data.Items as BaseItemDto[]) || [];
|
||||
}
|
||||
if (!settings?.marlinServerUrl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const url = `${
|
||||
settings.marlinServerUrl
|
||||
}/search?q=${encodeURIComponent(query)}&includeItemTypes=${types
|
||||
.map((type) => encodeURIComponent(type))
|
||||
.join("&includeItemTypes=")}`;
|
||||
|
||||
const response1 = await axios.get(url);
|
||||
|
||||
const ids = response1.data.ids;
|
||||
|
||||
if (!ids || !ids.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const response2 = await getItemsApi(api).getItems({
|
||||
ids,
|
||||
enableImageTypes: ["Primary", "Backdrop", "Thumb"],
|
||||
});
|
||||
|
||||
return (response2.data.Items as BaseItemDto[]) || [];
|
||||
} catch (error) {
|
||||
console.error("Error during search:", error);
|
||||
return []; // Ensure an empty array is returned in case of an error
|
||||
@@ -162,8 +167,10 @@ export default function search() {
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = eventBus.on("searchTabPressed", () => {
|
||||
// Screen not actuve
|
||||
if (!searchBarRef.current) return;
|
||||
// Screen not active
|
||||
if (!searchBarRef.current) {
|
||||
return;
|
||||
}
|
||||
// Screen is active, focus search bar
|
||||
searchBarRef.current?.focus();
|
||||
});
|
||||
@@ -254,64 +261,62 @@ export default function search() {
|
||||
}}
|
||||
>
|
||||
{jellyseerrApi && (
|
||||
<>
|
||||
<ScrollView
|
||||
horizontal
|
||||
className='flex flex-row flex-wrap space-x-2 px-4 mb-2'
|
||||
>
|
||||
<TouchableOpacity onPress={() => setSearchType("Library")}>
|
||||
<Tag
|
||||
text={t("search.library")}
|
||||
textClass='p-1'
|
||||
className={
|
||||
searchType === "Library" ? "bg-purple-600" : undefined
|
||||
}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSearchType("Discover")}>
|
||||
<Tag
|
||||
text={t("search.discover")}
|
||||
textClass='p-1'
|
||||
className={
|
||||
searchType === "Discover" ? "bg-purple-600" : undefined
|
||||
}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{searchType === "Discover" &&
|
||||
!loading &&
|
||||
noResults &&
|
||||
debouncedSearch.length > 0 && (
|
||||
<View className='flex flex-row justify-end items-center space-x-1'>
|
||||
<FilterButton
|
||||
id='search'
|
||||
queryKey='jellyseerr_search'
|
||||
queryFn={async () =>
|
||||
Object.keys(JellyseerrSearchSort).filter((v) =>
|
||||
isNaN(Number(v)),
|
||||
)
|
||||
}
|
||||
set={(value) => setJellyseerrOrderBy(value[0])}
|
||||
values={[jellyseerrOrderBy]}
|
||||
title={t("library.filters.sort_by")}
|
||||
renderItemLabel={(item) =>
|
||||
t(`home.settings.plugins.jellyseerr.order_by.${item}`)
|
||||
}
|
||||
showSearch={false}
|
||||
/>
|
||||
<FilterButton
|
||||
id='order'
|
||||
queryKey='jellysearr_search'
|
||||
queryFn={async () => ["asc", "desc"]}
|
||||
set={(value) => setJellyseerrSortOrder(value[0])}
|
||||
values={[jellyseerrSortOrder]}
|
||||
title={t("library.filters.sort_order")}
|
||||
renderItemLabel={(item) => t(`library.filters.${item}`)}
|
||||
showSearch={false}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</>
|
||||
<ScrollView
|
||||
horizontal
|
||||
className='flex flex-row flex-wrap space-x-2 px-4 mb-2'
|
||||
>
|
||||
<TouchableOpacity onPress={() => setSearchType("Library")}>
|
||||
<Tag
|
||||
text={t("search.library")}
|
||||
textClass='p-1'
|
||||
className={
|
||||
searchType === "Library" ? "bg-purple-600" : undefined
|
||||
}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => setSearchType("Discover")}>
|
||||
<Tag
|
||||
text={t("search.discover")}
|
||||
textClass='p-1'
|
||||
className={
|
||||
searchType === "Discover" ? "bg-purple-600" : undefined
|
||||
}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{searchType === "Discover" &&
|
||||
!loading &&
|
||||
noResults &&
|
||||
debouncedSearch.length > 0 && (
|
||||
<View className='flex flex-row justify-end items-center space-x-1'>
|
||||
<FilterButton
|
||||
id='search'
|
||||
queryKey='jellyseerr_search'
|
||||
queryFn={async () =>
|
||||
Object.keys(JellyseerrSearchSort).filter((v) =>
|
||||
Number.isNaN(Number(v)),
|
||||
)
|
||||
}
|
||||
set={(value) => setJellyseerrOrderBy(value[0])}
|
||||
values={[jellyseerrOrderBy]}
|
||||
title={t("library.filters.sort_by")}
|
||||
renderItemLabel={(item) =>
|
||||
t(`home.settings.plugins.jellyseerr.order_by.${item}`)
|
||||
}
|
||||
showSearch={false}
|
||||
/>
|
||||
<FilterButton
|
||||
id='order'
|
||||
queryKey='jellysearr_search'
|
||||
queryFn={async () => ["asc", "desc"]}
|
||||
set={(value) => setJellyseerrSortOrder(value[0])}
|
||||
values={[jellyseerrSortOrder]}
|
||||
title={t("library.filters.sort_order")}
|
||||
renderItemLabel={(item) => t(`library.filters.${item}`)}
|
||||
showSearch={false}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<View className='mt-2'>
|
||||
@@ -411,32 +416,29 @@ export default function search() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{searchType === "Library" && (
|
||||
<>
|
||||
{!loading && noResults && debouncedSearch.length > 0 ? (
|
||||
<View>
|
||||
<Text className='text-center text-lg font-bold mt-4'>
|
||||
{t("search.no_results_found_for")}
|
||||
</Text>
|
||||
<Text className='text-xs text-purple-600 text-center'>
|
||||
"{debouncedSearch}"
|
||||
</Text>
|
||||
</View>
|
||||
) : debouncedSearch.length === 0 ? (
|
||||
<View className='mt-4 flex flex-col items-center space-y-2'>
|
||||
{exampleSearches.map((e) => (
|
||||
<TouchableOpacity
|
||||
onPress={() => setSearch(e)}
|
||||
key={e}
|
||||
className='mb-2'
|
||||
>
|
||||
<Text className='text-purple-600'>{e}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{searchType === "Library" &&
|
||||
(!loading && noResults && debouncedSearch.length > 0 ? (
|
||||
<View>
|
||||
<Text className='text-center text-lg font-bold mt-4'>
|
||||
{t("search.no_results_found_for")}
|
||||
</Text>
|
||||
<Text className='text-xs text-purple-600 text-center'>
|
||||
"{debouncedSearch}"
|
||||
</Text>
|
||||
</View>
|
||||
) : debouncedSearch.length === 0 ? (
|
||||
<View className='mt-4 flex flex-col items-center space-y-2'>
|
||||
{exampleSearches.map((e) => (
|
||||
<TouchableOpacity
|
||||
onPress={() => setSearch(e)}
|
||||
key={e}
|
||||
className='mb-2'
|
||||
>
|
||||
<Text className='text-purple-600'>{e}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
) : null)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</>
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function TabLayout() {
|
||||
options={{
|
||||
title: t("tabs.home"),
|
||||
tabBarIcon:
|
||||
Platform.OS == "android"
|
||||
Platform.OS === "android"
|
||||
? ({ color, focused, size }) =>
|
||||
require("@/assets/icons/house.fill.png")
|
||||
: ({ focused }) =>
|
||||
@@ -91,7 +91,7 @@ export default function TabLayout() {
|
||||
options={{
|
||||
title: t("tabs.search"),
|
||||
tabBarIcon:
|
||||
Platform.OS == "android"
|
||||
Platform.OS === "android"
|
||||
? ({ color, focused, size }) =>
|
||||
require("@/assets/icons/magnifyingglass.png")
|
||||
: ({ focused }) =>
|
||||
@@ -105,7 +105,7 @@ export default function TabLayout() {
|
||||
options={{
|
||||
title: t("tabs.favorites"),
|
||||
tabBarIcon:
|
||||
Platform.OS == "android"
|
||||
Platform.OS === "android"
|
||||
? ({ color, focused, size }) =>
|
||||
focused
|
||||
? require("@/assets/icons/heart.fill.png")
|
||||
@@ -121,7 +121,7 @@ export default function TabLayout() {
|
||||
options={{
|
||||
title: t("tabs.library"),
|
||||
tabBarIcon:
|
||||
Platform.OS == "android"
|
||||
Platform.OS === "android"
|
||||
? ({ color, focused, size }) =>
|
||||
require("@/assets/icons/server.rack.png")
|
||||
: ({ focused }) =>
|
||||
@@ -135,9 +135,9 @@ export default function TabLayout() {
|
||||
options={{
|
||||
title: t("tabs.custom_links"),
|
||||
// @ts-expect-error
|
||||
tabBarItemHidden: settings?.showCustomMenuLinks ? false : true,
|
||||
tabBarItemHidden: !settings?.showCustomMenuLinks,
|
||||
tabBarIcon:
|
||||
Platform.OS == "android"
|
||||
Platform.OS === "android"
|
||||
? ({ focused }) => require("@/assets/icons/list.png")
|
||||
: ({ focused }) =>
|
||||
focused
|
||||
|
||||
@@ -16,7 +16,12 @@ import {
|
||||
BACKGROUND_FETCH_TASK_SESSIONS,
|
||||
registerBackgroundFetchAsyncSessions,
|
||||
} from "@/utils/background-tasks";
|
||||
import {LogProvider, writeDebugLog, writeErrorLog, writeToLog} from "@/utils/log";
|
||||
import {
|
||||
LogProvider,
|
||||
writeDebugLog,
|
||||
writeErrorLog,
|
||||
writeToLog,
|
||||
} from "@/utils/log";
|
||||
import { storage } from "@/utils/mmkv";
|
||||
import { cancelJobById, getAllJobsByDeviceId } from "@/utils/optimize-server";
|
||||
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
||||
@@ -31,8 +36,8 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
const BackgroundFetch = !Platform.isTV
|
||||
? require("expo-background-fetch")
|
||||
: null;
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import * as Device from "expo-device";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
const Notifications = !Platform.isTV ? require("expo-notifications") : null;
|
||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||
import { Stack, router, useSegments } from "expo-router";
|
||||
@@ -161,7 +166,7 @@ if (!Platform.isTV) {
|
||||
|
||||
for (const job of jobs) {
|
||||
if (job.status === "completed") {
|
||||
const downloadUrl = url + "download/" + job.id;
|
||||
const downloadUrl = `${url}download/${job.id}`;
|
||||
const tasks = await BackGroundDownloader.checkForExistingDownloads();
|
||||
|
||||
if (tasks.find((task: { id: string }) => task.id === job.id)) {
|
||||
@@ -194,7 +199,7 @@ if (!Platform.isTV) {
|
||||
title: job.item.Name,
|
||||
body: "Download completed",
|
||||
data: {
|
||||
url: `/downloads`,
|
||||
url: "/downloads",
|
||||
},
|
||||
},
|
||||
trigger: null,
|
||||
@@ -208,7 +213,7 @@ if (!Platform.isTV) {
|
||||
title: job.item.Name,
|
||||
body: "Download failed",
|
||||
data: {
|
||||
url: `/downloads`,
|
||||
url: "/downloads",
|
||||
},
|
||||
},
|
||||
trigger: null,
|
||||
@@ -333,7 +338,7 @@ function Layout() {
|
||||
}
|
||||
|
||||
// only create push token for real devices (pointless for emulators)
|
||||
if(Device.isDevice) {
|
||||
if (Device.isDevice) {
|
||||
Notifications?.getExpoPushTokenAsync()
|
||||
.then((token: ExpoPushToken) => token && setExpoPushToken(token))
|
||||
.catch((reason: any) => console.log("Failed to get token", reason));
|
||||
@@ -357,9 +362,12 @@ function Layout() {
|
||||
Notifications?.addNotificationResponseReceivedListener(
|
||||
(response: NotificationResponse) => {
|
||||
// Currently the notifications supported by the plugin will send data for deep links.
|
||||
const {title, data} = response.notification.request.content;
|
||||
const { title, data } = response.notification.request.content;
|
||||
|
||||
writeDebugLog(`Notification ${title} opened`, response.notification.request.content)
|
||||
writeDebugLog(
|
||||
`Notification ${title} opened`,
|
||||
response.notification.request.content,
|
||||
);
|
||||
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const type = data?.type?.toLower?.();
|
||||
@@ -367,12 +375,12 @@ function Layout() {
|
||||
|
||||
switch (type) {
|
||||
case "movie":
|
||||
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`)
|
||||
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`);
|
||||
break;
|
||||
case "episode":
|
||||
// We just clicked a notification for an individual episode.
|
||||
if (itemId) {
|
||||
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`)
|
||||
router.push(`/(auth)/(tabs)/home/items/page?id=${itemId}`);
|
||||
}
|
||||
// summarized season notification for multiple episodes. Bring them to series season
|
||||
else {
|
||||
@@ -380,10 +388,11 @@ function Layout() {
|
||||
const seasonIndex = data.seasonIndex;
|
||||
|
||||
if (seasonIndex) {
|
||||
router.push(`/(auth)/(tabs)/home/series/${seriesId}?seasonIndex=${seasonIndex}`)
|
||||
}
|
||||
else {
|
||||
router.push(`/(auth)/(tabs)/home/series/${seriesId}`)
|
||||
router.push(
|
||||
`/(auth)/(tabs)/home/series/${seriesId}?seasonIndex=${seasonIndex}`,
|
||||
);
|
||||
} else {
|
||||
router.push(`/(auth)/(tabs)/home/series/${seriesId}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -218,16 +218,14 @@ const Login: React.FC = () => {
|
||||
<View className='px-4 -mt-20 w-full'>
|
||||
<View className='flex flex-col space-y-2'>
|
||||
<Text className='text-2xl font-bold -mb-2'>
|
||||
<>
|
||||
{serverName ? (
|
||||
<>
|
||||
{t("login.login_to_title") + " "}
|
||||
<Text className='text-purple-600'>{serverName}</Text>
|
||||
</>
|
||||
) : (
|
||||
t("login.login_title")
|
||||
)}
|
||||
</>
|
||||
{serverName ? (
|
||||
<>
|
||||
{`${t("login.login_to_title")} `}
|
||||
<Text className='text-purple-600'>{serverName}</Text>
|
||||
</>
|
||||
) : (
|
||||
t("login.login_title")
|
||||
)}
|
||||
</Text>
|
||||
<Text className='text-xs text-neutral-400'>
|
||||
{api.basePath}
|
||||
@@ -284,7 +282,7 @@ const Login: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='absolute bottom-0 left-0 w-full px-4 mb-2'></View>
|
||||
<View className='absolute bottom-0 left-0 w-full px-4 mb-2' />
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user