Compare commits
16 Commits
fix/no-ffm
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1368fbd935 | ||
|
|
cb95ccff3a | ||
|
|
d854699cc8 | ||
|
|
49c95a091c | ||
|
|
ed301a9152 | ||
|
|
ecc31c3593 | ||
|
|
b7a9c41a9a | ||
|
|
680838fee1 | ||
|
|
0041aa981b | ||
|
|
8ca9fba583 | ||
|
|
694a5d6d21 | ||
|
|
46ff07a800 | ||
|
|
2fe83b4209 | ||
|
|
b1c6842c8e | ||
|
|
437da25a63 | ||
|
|
03244f318d |
1
.gitignore
vendored
@@ -26,3 +26,4 @@ Streamyfin.app
|
|||||||
/android
|
/android
|
||||||
|
|
||||||
pc-api-7079014811501811218-719-3b9f15aeccf8.json
|
pc-api-7079014811501811218-719-3b9f15aeccf8.json
|
||||||
|
credentials.json
|
||||||
|
|||||||
10
README.md
@@ -21,14 +21,20 @@ Welcome to Streamyfin, a simple and user-friendly Jellyfin client built with Exp
|
|||||||
|
|
||||||
Streamyfin includes some exciting experimental features like media downloading and Chromecast support. These are still in development, and we appreciate your patience and feedback as we work to improve them.
|
Streamyfin includes some exciting experimental features like media downloading and Chromecast support. These are still in development, and we appreciate your patience and feedback as we work to improve them.
|
||||||
|
|
||||||
## 🛠️ TestFlight (pending review)
|
## 🛠️ Beta testing (iOS/Android)
|
||||||
|
|
||||||
Soon iOS users can test Streamyfin in beta via TestFlight. To join the beta program, click the link below.
|
## TestFlight
|
||||||
|
|
||||||
<a href="https://testflight.apple.com/join/CWBaAAK2">
|
<a href="https://testflight.apple.com/join/CWBaAAK2">
|
||||||
<img height=75 alt="Get the beta on TestFlight" src="./assets/Get_the_beta_on_Testflight.svg"/>
|
<img height=75 alt="Get the beta on TestFlight" src="./assets/Get_the_beta_on_Testflight.svg"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
## Play Store Open Beta
|
||||||
|
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=com.fredrikburmester.streamyfin">
|
||||||
|
<img height=75 alt="Get the beta on Google Play" src="./assets/en_badge_web_generic.png"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|||||||
46
app.json
@@ -2,7 +2,7 @@
|
|||||||
"expo": {
|
"expo": {
|
||||||
"name": "Streamyfin",
|
"name": "Streamyfin",
|
||||||
"slug": "streamyfin",
|
"slug": "streamyfin",
|
||||||
"version": "0.0.6",
|
"version": "0.2.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
"scheme": "streamyfin",
|
"scheme": "streamyfin",
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
"jsEngine": "hermes",
|
"jsEngine": "hermes",
|
||||||
"assetBundlePatterns": ["**/*"],
|
"assetBundlePatterns": ["**/*"],
|
||||||
"ios": {
|
"ios": {
|
||||||
"userInterfaceStyle": "dark",
|
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
"NSCameraUsageDescription": "The app needs access to your camera to scan barcodes.",
|
"NSCameraUsageDescription": "The app needs access to your camera to scan barcodes.",
|
||||||
"NSMicrophoneUsageDescription": "The app needs access to your microphone."
|
"NSMicrophoneUsageDescription": "The app needs access to your microphone."
|
||||||
@@ -25,12 +24,20 @@
|
|||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"jsEngine": "jsc",
|
"jsEngine": "jsc",
|
||||||
"userInterfaceStyle": "light",
|
"androidNavigationBar": {
|
||||||
"adaptiveIcon": {
|
"visible": true,
|
||||||
"foregroundImage": "./assets/images/icon.png",
|
"barStyle": "dark-content",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#000000"
|
||||||
},
|
},
|
||||||
"package": "com.fredrikburmester.streamyfin"
|
"adaptiveIcon": {
|
||||||
|
"foregroundImage": "./assets/images/icon.png"
|
||||||
|
},
|
||||||
|
"package": "com.fredrikburmester.streamyfin",
|
||||||
|
"permissions": [
|
||||||
|
"android.permission.FOREGROUND_SERVICE",
|
||||||
|
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
|
||||||
|
],
|
||||||
|
"versionCode": 6
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"bundler": "metro",
|
"bundler": "metro",
|
||||||
@@ -55,26 +62,17 @@
|
|||||||
"androidExtensions": {
|
"androidExtensions": {
|
||||||
"useExoplayerRtsp": false,
|
"useExoplayerRtsp": false,
|
||||||
"useExoplayerSmoothStreaming": false,
|
"useExoplayerSmoothStreaming": false,
|
||||||
"useExoplayerHls": false,
|
"useExoplayerHls": true,
|
||||||
"useExoplayerDash": false
|
"useExoplayerDash": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
|
||||||
"react-native-vlc-media-player",
|
|
||||||
{
|
|
||||||
"ios": {
|
|
||||||
"includeVLCKit": false
|
|
||||||
},
|
|
||||||
"android": {
|
|
||||||
"legacyJetifier": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"expo-build-properties",
|
"expo-build-properties",
|
||||||
{
|
{
|
||||||
"ios": { "deploymentTarget": "14.0" },
|
"ios": {
|
||||||
|
"deploymentTarget": "14.0"
|
||||||
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"minSdkVersion": 24,
|
"minSdkVersion": 24,
|
||||||
"packagingOptions": {
|
"packagingOptions": {
|
||||||
@@ -97,6 +95,12 @@
|
|||||||
"projectId": "e79219d1-797f-4fbe-9fa1-cfd360690a68"
|
"projectId": "e79219d1-797f-4fbe-9fa1-cfd360690a68"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"owner": "fredrikburmester"
|
"owner": "fredrikburmester",
|
||||||
|
"runtimeVersion": {
|
||||||
|
"policy": "appVersion"
|
||||||
|
},
|
||||||
|
"updates": {
|
||||||
|
"url": "https://u.expo.dev/e79219d1-797f-4fbe-9fa1-cfd360690a68"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { router, Tabs } from "expo-router";
|
import { router, Tabs } from "expo-router";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import * as NavigationBar from "expo-navigation-bar";
|
||||||
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
|
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import { TouchableOpacity } from "react-native";
|
import { Platform, TouchableOpacity } from "react-native";
|
||||||
import { Feather } from "@expo/vector-icons";
|
import { Feather } from "@expo/vector-icons";
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
|
useEffect(() => {
|
||||||
|
if (Platform.OS === "android") {
|
||||||
|
NavigationBar.setBackgroundColorAsync("#121212");
|
||||||
|
NavigationBar.setBorderColorAsync("#121212");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export default function index() {
|
|||||||
<RefreshControl refreshing={loading} onRefresh={refetch} />
|
<RefreshControl refreshing={loading} onRefresh={refetch} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View className="flex flex-col py-4 gap-y-4">
|
<View className="flex flex-col pt-4 pb-24 gap-y-4">
|
||||||
<View>
|
<View>
|
||||||
<Text className="px-4 text-2xl font-bold mb-2">
|
<Text className="px-4 text-2xl font-bold mb-2">
|
||||||
Continue Watching
|
Continue Watching
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export default function search() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView keyboardDismissMode="on-drag">
|
<ScrollView keyboardDismissMode="on-drag">
|
||||||
<View className="flex flex-col py-2">
|
<View className="flex flex-col pt-2 pb-20">
|
||||||
<View className="mb-4 px-4">
|
<View className="mb-4 px-4">
|
||||||
<Input
|
<Input
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
@@ -22,16 +22,29 @@ import { ParallaxScrollView } from "../../../../components/ParallaxPage";
|
|||||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||||
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
|
||||||
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
|
||||||
|
import { PlayButton } from "@/components/PlayButton";
|
||||||
|
import { Bitrate, BitrateSelector } from "@/components/BitrateSelector";
|
||||||
|
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
|
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
||||||
|
import { useCastDevice } from "react-native-google-cast";
|
||||||
|
import { chromecastProfile } from "@/utils/profiles/chromecast";
|
||||||
|
import ios12 from "@/utils/profiles/ios12";
|
||||||
|
import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar";
|
||||||
|
|
||||||
const page: React.FC = () => {
|
const page: React.FC = () => {
|
||||||
const local = useLocalSearchParams();
|
const local = useLocalSearchParams();
|
||||||
const { id } = local as { id: string };
|
const { id } = local as { id: string };
|
||||||
|
|
||||||
const [playbackURL, setPlaybackURL] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
|
|
||||||
|
const castDevice = useCastDevice();
|
||||||
|
|
||||||
|
const [maxBitrate, setMaxBitrate] = useState<Bitrate>({
|
||||||
|
key: "Max",
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
const { data: item, isLoading: l1 } = useQuery({
|
const { data: item, isLoading: l1 } = useQuery({
|
||||||
queryKey: ["item", id],
|
queryKey: ["item", id],
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
@@ -60,6 +73,52 @@ const page: React.FC = () => {
|
|||||||
[item],
|
[item],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: sessionData } = useQuery({
|
||||||
|
queryKey: ["sessionData", item?.Id],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!api || !user?.Id || !item?.Id) return null;
|
||||||
|
const playbackData = await getMediaInfoApi(api!).getPlaybackInfo({
|
||||||
|
itemId: item?.Id,
|
||||||
|
userId: user?.Id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return playbackData.data;
|
||||||
|
},
|
||||||
|
enabled: !!item?.Id && !!api && !!user?.Id,
|
||||||
|
staleTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: playbackUrl } = useQuery({
|
||||||
|
queryKey: ["playbackUrl", item?.Id, maxBitrate, castDevice],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!api || !user?.Id || !sessionData) return null;
|
||||||
|
|
||||||
|
const url = await getStreamUrl({
|
||||||
|
api,
|
||||||
|
userId: user.Id,
|
||||||
|
item,
|
||||||
|
startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0,
|
||||||
|
maxStreamingBitrate: maxBitrate.value,
|
||||||
|
sessionData,
|
||||||
|
deviceProfile: castDevice?.deviceId ? chromecastProfile : ios12,
|
||||||
|
});
|
||||||
|
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
enabled: !!sessionData,
|
||||||
|
staleTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
|
|
||||||
|
const onPressPlay = useCallback(() => {
|
||||||
|
if (!playbackUrl || !item) return;
|
||||||
|
setCp({
|
||||||
|
item,
|
||||||
|
playbackUrl,
|
||||||
|
});
|
||||||
|
}, [playbackUrl, item]);
|
||||||
|
|
||||||
if (l1)
|
if (l1)
|
||||||
return (
|
return (
|
||||||
<View className="justify-center items-center h-full">
|
<View className="justify-center items-center h-full">
|
||||||
@@ -151,20 +210,19 @@ const page: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="flex flex-row justify-between items-center w-full my-4">
|
<View className="flex flex-row justify-between items-center w-full my-4">
|
||||||
{playbackURL && (
|
{playbackUrl && (
|
||||||
<DownloadItem item={item} playbackURL={playbackURL} />
|
<DownloadItem item={item} playbackUrl={playbackUrl} />
|
||||||
)}
|
)}
|
||||||
<Chromecast />
|
<Chromecast />
|
||||||
</View>
|
</View>
|
||||||
<Text>{item.Overview}</Text>
|
<Text>{item.Overview}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="flex flex-col p-4">
|
<View className="flex flex-col p-4">
|
||||||
<VideoPlayer
|
<BitrateSelector
|
||||||
itemId={item.Id}
|
onChange={(val) => setMaxBitrate(val)}
|
||||||
onChangePlaybackURL={(val) => {
|
selected={maxBitrate}
|
||||||
setPlaybackURL(val);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
<PlayButton item={item} chromecastReady={false} onPress={onPressPlay} />
|
||||||
</View>
|
</View>
|
||||||
<ScrollView horizontal className="flex px-4 mb-4">
|
<ScrollView horizontal className="flex px-4 mb-4">
|
||||||
<View className="flex flex-row space-x-2 ">
|
<View className="flex flex-row space-x-2 ">
|
||||||
|
|||||||
@@ -10,12 +10,21 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
|
|
||||||
const page: React.FC = () => {
|
const page: React.FC = () => {
|
||||||
const params = useLocalSearchParams();
|
const params = useLocalSearchParams();
|
||||||
const { id: seriesId } = params as { id: string };
|
const { id: seriesId, seasonIndex } = params as {
|
||||||
|
id: string;
|
||||||
|
seasonIndex: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (seriesId) {
|
||||||
|
console.log("seasonIndex", seasonIndex);
|
||||||
|
}
|
||||||
|
}, [seriesId]);
|
||||||
|
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
@@ -84,7 +93,7 @@ const page: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<View className="flex flex-col pt-4 pb-12">
|
<View className="flex flex-col pt-4 pb-24">
|
||||||
<View className="px-4 py-4">
|
<View className="px-4 py-4">
|
||||||
<Text className="text-3xl font-bold">{item?.Name}</Text>
|
<Text className="text-3xl font-bold">{item?.Name}</Text>
|
||||||
<Text className="">{item?.Overview}</Text>
|
<Text className="">{item?.Overview}</Text>
|
||||||
|
|||||||
@@ -64,19 +64,17 @@ export default function settings() {
|
|||||||
<Text className="font-bold text-2xl">Logs</Text>
|
<Text className="font-bold text-2xl">Logs</Text>
|
||||||
<View className="flex flex-col space-y-2">
|
<View className="flex flex-col space-y-2">
|
||||||
{logs?.map((log, index) => (
|
{logs?.map((log, index) => (
|
||||||
<View
|
<View key={index} className="bg-neutral-900 rounded-xl p-3">
|
||||||
key={index}
|
|
||||||
className="bg-neutral-800 border border-neutral-900 rounded p-2"
|
|
||||||
>
|
|
||||||
<Text
|
<Text
|
||||||
className={`
|
className={`
|
||||||
|
mb-1
|
||||||
${log.level === "INFO" && "text-blue-500"}
|
${log.level === "INFO" && "text-blue-500"}
|
||||||
${log.level === "ERROR" && "text-red-500"}
|
${log.level === "ERROR" && "text-red-500"}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{log.level}
|
{log.level}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>{log.message}</Text>
|
<Text className="text-xs">{log.message}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
{logs?.length === 0 && (
|
{logs?.length === 0 && (
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
import { DarkTheme, ThemeProvider } from "@react-navigation/native";
|
|
||||||
import { useFonts } from "expo-font";
|
|
||||||
import { router, Stack } from "expo-router";
|
|
||||||
import * as SplashScreen from "expo-splash-screen";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import "react-native-reanimated";
|
|
||||||
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
||||||
import { Provider as JotaiProvider } from "jotai";
|
|
||||||
import { JellyfinProvider } from "@/providers/JellyfinProvider";
|
import { JellyfinProvider } from "@/providers/JellyfinProvider";
|
||||||
import { TouchableOpacity } from "react-native";
|
import { DarkTheme, ThemeProvider } from "@react-navigation/native";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { useFonts } from "expo-font";
|
||||||
|
import * as NavigationBar from "expo-navigation-bar";
|
||||||
|
import { Stack, router } from "expo-router";
|
||||||
|
import * as SplashScreen from "expo-splash-screen";
|
||||||
|
import { Provider as JotaiProvider } from "jotai";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { Platform, TouchableOpacity } from "react-native";
|
||||||
|
import "react-native-reanimated";
|
||||||
|
|
||||||
import Feather from "@expo/vector-icons/Feather";
|
import Feather from "@expo/vector-icons/Feather";
|
||||||
import { StatusBar } from "expo-status-bar";
|
import { StatusBar } from "expo-status-bar";
|
||||||
|
import { Colors } from "@/constants/Colors";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { Text } from "@/components/common/Text";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import Video from "react-native-video";
|
||||||
|
import { CurrentlyPlayingBar } from "@/components/CurrentlyPlayingBar";
|
||||||
|
|
||||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
@@ -39,6 +46,8 @@ export default function RootLayout() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
SplashScreen.hideAsync();
|
SplashScreen.hideAsync();
|
||||||
@@ -53,9 +62,9 @@ export default function RootLayout() {
|
|||||||
<QueryClientProvider client={queryClientRef.current}>
|
<QueryClientProvider client={queryClientRef.current}>
|
||||||
<JotaiProvider>
|
<JotaiProvider>
|
||||||
<JellyfinProvider>
|
<JellyfinProvider>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="light" backgroundColor="#000" />
|
||||||
<ThemeProvider value={DarkTheme}>
|
<ThemeProvider value={DarkTheme}>
|
||||||
<Stack>
|
<Stack screenOptions={{}}>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="(auth)/(tabs)"
|
name="(auth)/(tabs)"
|
||||||
options={{
|
options={{
|
||||||
@@ -68,12 +77,8 @@ export default function RootLayout() {
|
|||||||
options={{
|
options={{
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
presentation: "modal",
|
headerStyle: { backgroundColor: "black" },
|
||||||
headerLeft: () => (
|
headerShadowVisible: false,
|
||||||
<TouchableOpacity onPress={() => router.back()}>
|
|
||||||
<Feather name="x-circle" size={24} color="white" />
|
|
||||||
</TouchableOpacity>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@@ -81,14 +86,8 @@ export default function RootLayout() {
|
|||||||
options={{
|
options={{
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
title: "Downloads",
|
title: "Downloads",
|
||||||
}}
|
headerStyle: { backgroundColor: "black" },
|
||||||
/>
|
headerShadowVisible: false,
|
||||||
<Stack.Screen
|
|
||||||
name="(auth)/player/offline/page"
|
|
||||||
options={{
|
|
||||||
title: "",
|
|
||||||
headerShown: true,
|
|
||||||
headerStyle: { backgroundColor: "transparent" },
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@@ -103,7 +102,7 @@ export default function RootLayout() {
|
|||||||
options={{
|
options={{
|
||||||
title: "",
|
title: "",
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
headerStyle: { backgroundColor: "transparent" },
|
headerStyle: { backgroundColor: "black" },
|
||||||
headerShadowVisible: false,
|
headerShadowVisible: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -120,6 +119,7 @@ export default function RootLayout() {
|
|||||||
/>
|
/>
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<CurrentlyPlayingBar />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</JellyfinProvider>
|
</JellyfinProvider>
|
||||||
</JotaiProvider>
|
</JotaiProvider>
|
||||||
|
|||||||
164
app/login.tsx
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Input } from "@/components/common/Input";
|
||||||
|
import { Text } from "@/components/common/Text";
|
||||||
|
import { apiAtom, useJellyfin } from "@/providers/JellyfinProvider";
|
||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { KeyboardAvoidingView, Platform, View } from "react-native";
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const CredentialsSchema = z.object({
|
||||||
|
username: z.string().min(1, "Username is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const Login: React.FC = () => {
|
||||||
|
const { setServer, login, removeServer } = useJellyfin();
|
||||||
|
const [api] = useAtom(apiAtom);
|
||||||
|
|
||||||
|
const [serverURL, setServerURL] = useState<string>("");
|
||||||
|
const [credentials, setCredentials] = useState<{
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}>({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = CredentialsSchema.safeParse(credentials);
|
||||||
|
if (result.success) {
|
||||||
|
await login(credentials.username, credentials.password);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsedServerURL = useMemo(() => {
|
||||||
|
let parsedServerURL = serverURL.trim();
|
||||||
|
|
||||||
|
if (parsedServerURL) {
|
||||||
|
parsedServerURL = parsedServerURL.endsWith("/")
|
||||||
|
? parsedServerURL.replace("/", "")
|
||||||
|
: parsedServerURL;
|
||||||
|
parsedServerURL = parsedServerURL.startsWith("http")
|
||||||
|
? parsedServerURL
|
||||||
|
: "http://" + parsedServerURL;
|
||||||
|
|
||||||
|
return parsedServerURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}, [serverURL]);
|
||||||
|
|
||||||
|
const handleConnect = (url: string) => {
|
||||||
|
setServer({ address: url });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (api?.basePath) {
|
||||||
|
return (
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<View className="flex flex-col px-4 justify-center h-full gap-y-2">
|
||||||
|
<View>
|
||||||
|
<Text className="text-3xl font-bold">Jellyfin</Text>
|
||||||
|
<Text className="opacity-50 mb-2">Server: {api.basePath}</Text>
|
||||||
|
<Button
|
||||||
|
color="black"
|
||||||
|
onPress={() => {
|
||||||
|
removeServer();
|
||||||
|
setServerURL("");
|
||||||
|
}}
|
||||||
|
justify="between"
|
||||||
|
iconLeft={
|
||||||
|
<Ionicons name="arrow-back-outline" size={18} color={"white"} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Change server
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
<View className="flex flex-col space-y-2">
|
||||||
|
<Text className="text-2xl font-bold">Log in</Text>
|
||||||
|
<Input
|
||||||
|
placeholder="Username"
|
||||||
|
onChangeText={(text) =>
|
||||||
|
setCredentials({ ...credentials, username: text })
|
||||||
|
}
|
||||||
|
value={credentials.username}
|
||||||
|
autoFocus
|
||||||
|
secureTextEntry={false}
|
||||||
|
keyboardType="default"
|
||||||
|
returnKeyType="done"
|
||||||
|
autoCapitalize="none"
|
||||||
|
textContentType="username"
|
||||||
|
clearButtonMode="while-editing"
|
||||||
|
maxLength={500}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
className="mb-2"
|
||||||
|
placeholder="Password"
|
||||||
|
onChangeText={(text) =>
|
||||||
|
setCredentials({ ...credentials, password: text })
|
||||||
|
}
|
||||||
|
value={credentials.password}
|
||||||
|
secureTextEntry
|
||||||
|
keyboardType="default"
|
||||||
|
returnKeyType="done"
|
||||||
|
autoCapitalize="none"
|
||||||
|
textContentType="password"
|
||||||
|
clearButtonMode="while-editing"
|
||||||
|
maxLength={500}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button onPress={handleLogin} loading={loading}>
|
||||||
|
Log in
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KeyboardAvoidingView
|
||||||
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<View className="flex flex-col px-4 justify-center h-full">
|
||||||
|
<View className="flex flex-col gap-y-2">
|
||||||
|
<Text className="text-3xl font-bold">Jellyfin</Text>
|
||||||
|
<Text className="opacity-50">Enter a server adress</Text>
|
||||||
|
<Input
|
||||||
|
className="mb-2"
|
||||||
|
placeholder="http(s)://..."
|
||||||
|
onChangeText={setServerURL}
|
||||||
|
value={serverURL}
|
||||||
|
autoFocus
|
||||||
|
secureTextEntry={false}
|
||||||
|
keyboardType="url"
|
||||||
|
returnKeyType="done"
|
||||||
|
autoCapitalize="none"
|
||||||
|
textContentType="URL"
|
||||||
|
maxLength={500}
|
||||||
|
/>
|
||||||
|
<Button onPress={() => handleConnect(parsedServerURL)}>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Login;
|
||||||
BIN
assets/en_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/images/featured.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
assets/images/icon.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
assets/images/icon_512x512.jpg
Normal file
|
After Width: | Height: | Size: 81 KiB |
75
components/BitrateSelector.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { TouchableOpacity, View } from "react-native";
|
||||||
|
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||||
|
import { Text } from "./common/Text";
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
|
||||||
|
export type Bitrate = {
|
||||||
|
key: string;
|
||||||
|
value: number | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BITRATES: Bitrate[] = [
|
||||||
|
{
|
||||||
|
key: "Max",
|
||||||
|
value: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "4 Mb/s",
|
||||||
|
value: 4000000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "2 Mb/s",
|
||||||
|
value: 2000000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "500 Kb/s",
|
||||||
|
value: 500000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onChange: (value: Bitrate) => void;
|
||||||
|
selected: Bitrate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitrateSelector: React.FC<Props> = ({ onChange, selected }) => {
|
||||||
|
return (
|
||||||
|
<View className="flex flex-row items-center justify-between">
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger>
|
||||||
|
<View className="flex flex-col mb-2">
|
||||||
|
<Text className="opacity-50 mb-1 text-xs">Bitrate</Text>
|
||||||
|
<View className="flex flex-row">
|
||||||
|
<TouchableOpacity className="bg-neutral-900 rounded-2xl border-neutral-900 border px-3 py-2 flex flex-row items-center justify-between">
|
||||||
|
<Text>
|
||||||
|
{BITRATES.find((b) => b.value === selected.value)?.key}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content
|
||||||
|
loop={true}
|
||||||
|
side="bottom"
|
||||||
|
align="start"
|
||||||
|
alignOffset={0}
|
||||||
|
avoidCollisions={true}
|
||||||
|
collisionPadding={8}
|
||||||
|
sideOffset={8}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Label>Bitrates</DropdownMenu.Label>
|
||||||
|
{BITRATES?.map((b, index: number) => (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
key={index.toString()}
|
||||||
|
onSelect={() => {
|
||||||
|
onChange(b);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenu.ItemTitle>{b.key}</DropdownMenu.ItemTitle>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
288
components/CurrentlyPlayingBar.tsx
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Platform,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from "react-native";
|
||||||
|
import { Text } from "./common/Text";
|
||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import Video, { OnProgressData, VideoRef } from "react-native-video";
|
||||||
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useCastDevice, useRemoteMediaClient } from "react-native-google-cast";
|
||||||
|
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||||
|
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
|
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
||||||
|
import { chromecastProfile } from "@/utils/profiles/chromecast";
|
||||||
|
import ios12 from "@/utils/profiles/ios12";
|
||||||
|
import { reportPlaybackProgress } from "@/utils/jellyfin/playstate/reportPlaybackProgress";
|
||||||
|
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
||||||
|
import Animated, {
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withTiming,
|
||||||
|
} from "react-native-reanimated";
|
||||||
|
import { useRouter, useSegments } from "expo-router";
|
||||||
|
import { BlurView } from "expo-blur";
|
||||||
|
|
||||||
|
export const currentlyPlayingItemAtom = atom<{
|
||||||
|
item: BaseItemDto;
|
||||||
|
playbackUrl: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
export const CurrentlyPlayingBar: React.FC = () => {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const [api] = useAtom(apiAtom);
|
||||||
|
const [user] = useAtom(userAtom);
|
||||||
|
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
|
|
||||||
|
const castDevice = useCastDevice();
|
||||||
|
const client = useRemoteMediaClient();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const segments = useSegments();
|
||||||
|
|
||||||
|
const videoRef = useRef<VideoRef | null>(null);
|
||||||
|
const [paused, setPaused] = useState(true);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
const aBottom = useSharedValue(0);
|
||||||
|
const aPadding = useSharedValue(0);
|
||||||
|
const aHeight = useSharedValue(100);
|
||||||
|
const router = useRouter();
|
||||||
|
const animatedOuterStyle = useAnimatedStyle(() => {
|
||||||
|
return {
|
||||||
|
bottom: withTiming(aBottom.value, { duration: 500 }),
|
||||||
|
height: withTiming(aHeight.value, { duration: 500 }),
|
||||||
|
padding: withTiming(aPadding.value, { duration: 500 }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const aPaddingBottom = useSharedValue(30);
|
||||||
|
const aPaddingInner = useSharedValue(12);
|
||||||
|
const aBorderRadiusBottom = useSharedValue(12);
|
||||||
|
const animatedInnerStyle = useAnimatedStyle(() => {
|
||||||
|
return {
|
||||||
|
padding: withTiming(aPaddingInner.value, { duration: 500 }),
|
||||||
|
paddingBottom: withTiming(aPaddingBottom.value, { duration: 500 }),
|
||||||
|
borderBottomLeftRadius: withTiming(aBorderRadiusBottom.value, {
|
||||||
|
duration: 500,
|
||||||
|
}),
|
||||||
|
borderBottomRightRadius: withTiming(aBorderRadiusBottom.value, {
|
||||||
|
duration: 500,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (segments.find((s) => s.includes("tabs"))) {
|
||||||
|
// Tab screen - i.e. home
|
||||||
|
aBottom.value = Platform.OS === "ios" ? 78 : 50;
|
||||||
|
aHeight.value = 80;
|
||||||
|
aPadding.value = 8;
|
||||||
|
aPaddingBottom.value = 8;
|
||||||
|
aPaddingInner.value = 8;
|
||||||
|
} else {
|
||||||
|
// Inside a normal screen
|
||||||
|
aBottom.value = Platform.OS === "ios" ? 0 : 0;
|
||||||
|
aHeight.value = Platform.OS === "ios" ? 110 : 80;
|
||||||
|
aPadding.value = Platform.OS === "ios" ? 0 : 8;
|
||||||
|
aPaddingInner.value = Platform.OS === "ios" ? 12 : 8;
|
||||||
|
aPaddingBottom.value = Platform.OS === "ios" ? 40 : 12;
|
||||||
|
}
|
||||||
|
}, [segments]);
|
||||||
|
|
||||||
|
const { data: item } = useQuery({
|
||||||
|
queryKey: ["item", cp?.item.Id],
|
||||||
|
queryFn: async () =>
|
||||||
|
await getUserItemData({
|
||||||
|
api,
|
||||||
|
userId: user?.Id,
|
||||||
|
itemId: cp?.item.Id,
|
||||||
|
}),
|
||||||
|
enabled: !!cp?.item.Id && !!api,
|
||||||
|
staleTime: 60,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: sessionData } = useQuery({
|
||||||
|
queryKey: ["sessionData", cp?.item.Id],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!cp?.item.Id) return null;
|
||||||
|
const playbackData = await getMediaInfoApi(api!).getPlaybackInfo({
|
||||||
|
itemId: cp?.item.Id,
|
||||||
|
userId: user?.Id,
|
||||||
|
});
|
||||||
|
return playbackData.data;
|
||||||
|
},
|
||||||
|
enabled: !!cp?.item.Id && !!api && !!user?.Id,
|
||||||
|
staleTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onProgress = useCallback(
|
||||||
|
({ currentTime }: OnProgressData) => {
|
||||||
|
if (!currentTime || !sessionData?.PlaySessionId || paused) return;
|
||||||
|
const newProgress = currentTime * 10000000;
|
||||||
|
setProgress(newProgress);
|
||||||
|
reportPlaybackProgress({
|
||||||
|
api,
|
||||||
|
itemId: cp?.item.Id,
|
||||||
|
positionTicks: newProgress,
|
||||||
|
sessionId: sessionData.PlaySessionId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[sessionData?.PlaySessionId, item, api, paused],
|
||||||
|
);
|
||||||
|
|
||||||
|
const play = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.resume();
|
||||||
|
setPaused(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pause = useCallback(() => {
|
||||||
|
videoRef.current?.pause();
|
||||||
|
setPaused(true);
|
||||||
|
|
||||||
|
if (progress > 0)
|
||||||
|
reportPlaybackStopped({
|
||||||
|
api,
|
||||||
|
itemId: item?.Id,
|
||||||
|
positionTicks: progress,
|
||||||
|
sessionId: sessionData?.PlaySessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["nextUp", item?.SeriesId],
|
||||||
|
refetchType: "all",
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["episodes"],
|
||||||
|
refetchType: "all",
|
||||||
|
});
|
||||||
|
}, [api, item, progress, sessionData, queryClient]);
|
||||||
|
|
||||||
|
const startPosition = useMemo(
|
||||||
|
() =>
|
||||||
|
item?.UserData?.PlaybackPositionTicks
|
||||||
|
? Math.round(item.UserData.PlaybackPositionTicks / 10000)
|
||||||
|
: 0,
|
||||||
|
[item],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cp?.playbackUrl) {
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
}, [cp?.playbackUrl]);
|
||||||
|
|
||||||
|
if (!cp) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={[animatedOuterStyle]}
|
||||||
|
className="absolute left-0 w-screen"
|
||||||
|
>
|
||||||
|
<BlurView
|
||||||
|
intensity={Platform.OS === "android" ? 60 : 100}
|
||||||
|
experimentalBlurMethod={Platform.OS === "android" ? "none" : undefined}
|
||||||
|
className={`h-full w-full rounded-xl overflow-hidden ${Platform.OS === "android" && "bg-black"}`}
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{ padding: 8, borderTopLeftRadius: 12, borderTopEndRadius: 12 },
|
||||||
|
animatedInnerStyle,
|
||||||
|
]}
|
||||||
|
className="h-full w-full flex flex-row items-center justify-between overflow-hidden"
|
||||||
|
>
|
||||||
|
<View className="flex flex-row items-center space-x-4 shrink">
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
videoRef.current?.presentFullscreenPlayer();
|
||||||
|
}}
|
||||||
|
className="aspect-video h-full bg-neutral-800 rounded-md overflow-hidden"
|
||||||
|
>
|
||||||
|
{cp.playbackUrl && (
|
||||||
|
<Video
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
source={{
|
||||||
|
uri: cp.playbackUrl,
|
||||||
|
isNetwork: true,
|
||||||
|
startPosition,
|
||||||
|
}}
|
||||||
|
controls={false}
|
||||||
|
ref={videoRef}
|
||||||
|
onBuffer={(e) =>
|
||||||
|
e.isBuffering ? console.log("Buffering...") : null
|
||||||
|
}
|
||||||
|
onProgress={(e) => onProgress(e)}
|
||||||
|
paused={paused}
|
||||||
|
onFullscreenPlayerDidDismiss={() => {
|
||||||
|
play();
|
||||||
|
}}
|
||||||
|
ignoreSilentSwitch="ignore"
|
||||||
|
renderLoader={
|
||||||
|
<View className="flex flex-col items-center justify-center h-full">
|
||||||
|
<ActivityIndicator size={"small"} color={"white"} />
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View className="shrink text-xs">
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/(auth)/items/${item?.Id}/page`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>{item?.Name}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{item?.SeriesName ? (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/(auth)/series/${item.SeriesId}/page`);
|
||||||
|
}}
|
||||||
|
className="text-xs opacity-50"
|
||||||
|
>
|
||||||
|
<Text>{item.SeriesName}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : (
|
||||||
|
<View>
|
||||||
|
<Text className="text-xs opacity-50">
|
||||||
|
{item?.ProductionYear}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className="flex flex-row items-center space-x-2">
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
if (paused) play();
|
||||||
|
else pause();
|
||||||
|
}}
|
||||||
|
className="aspect-square rounded flex flex-col items-center justify-center p-2"
|
||||||
|
>
|
||||||
|
{paused ? (
|
||||||
|
<Ionicons name="play" size={24} color="white" />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="pause" size={24} color="white" />
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
setCp(null);
|
||||||
|
}}
|
||||||
|
className="aspect-square rounded flex flex-col items-center justify-center p-2"
|
||||||
|
>
|
||||||
|
<Ionicons name="close" size={24} color="white" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
</BlurView>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -16,12 +16,12 @@ import { getPlaybackInfo } from "@/utils/jellyfin/media/getPlaybackInfo";
|
|||||||
|
|
||||||
type DownloadProps = {
|
type DownloadProps = {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
playbackURL: string;
|
playbackUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DownloadItem: React.FC<DownloadProps> = ({
|
export const DownloadItem: React.FC<DownloadProps> = ({
|
||||||
item,
|
item,
|
||||||
playbackURL,
|
playbackUrl,
|
||||||
}) => {
|
}) => {
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
@@ -30,7 +30,7 @@ export const DownloadItem: React.FC<DownloadProps> = ({
|
|||||||
const { downloadMedia, isDownloading, error, cancelDownload } =
|
const { downloadMedia, isDownloading, error, cancelDownload } =
|
||||||
useDownloadMedia(api, user?.Id);
|
useDownloadMedia(api, user?.Id);
|
||||||
|
|
||||||
const { startRemuxing, cancelRemuxing } = useRemuxHlsToMp4(playbackURL, item);
|
const { startRemuxing, cancelRemuxing } = useRemuxHlsToMp4(playbackUrl, item);
|
||||||
|
|
||||||
const { data: playbackInfo, isLoading } = useQuery({
|
const { data: playbackInfo, isLoading } = useQuery({
|
||||||
queryKey: ["playbackInfo", item.Id],
|
queryKey: ["playbackInfo", item.Id],
|
||||||
|
|||||||
@@ -29,15 +29,9 @@ export const OfflineVideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
|
|||||||
uri: url,
|
uri: url,
|
||||||
isNetwork: false,
|
isNetwork: false,
|
||||||
}}
|
}}
|
||||||
controls
|
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
resizeMode="contain"
|
ignoreSilentSwitch="ignore"
|
||||||
reportBandwidth
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
aspectRatio: 16 / 9,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ export const ParallaxScrollView: React.FC<Props> = ({
|
|||||||
translateY: interpolate(
|
translateY: interpolate(
|
||||||
scrollOffset.value,
|
scrollOffset.value,
|
||||||
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
|
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
|
||||||
[-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
|
[-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scale: interpolate(
|
scale: interpolate(
|
||||||
scrollOffset.value,
|
scrollOffset.value,
|
||||||
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
|
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
|
||||||
[2, 1, 1]
|
[2, 1, 1],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -61,7 +61,7 @@ export const ParallaxScrollView: React.FC<Props> = ({
|
|||||||
onPress={() => router.back()}
|
onPress={() => router.back()}
|
||||||
className="absolute left-4 z-50 bg-black rounded-full p-2 border border-neutral-900"
|
className="absolute left-4 z-50 bg-black rounded-full p-2 border border-neutral-900"
|
||||||
style={{
|
style={{
|
||||||
top: inset.top,
|
top: inset.top + 17,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
|
|||||||
34
components/PlayButton.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "./Button";
|
||||||
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
|
import { currentlyPlayingItemAtom } from "./CurrentlyPlayingBar";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||||
|
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: BaseItemDto;
|
||||||
|
onPress: () => void;
|
||||||
|
chromecastReady: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PlayButton: React.FC<Props> = ({
|
||||||
|
item,
|
||||||
|
onPress,
|
||||||
|
chromecastReady,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onPress={onPress}
|
||||||
|
iconRight={
|
||||||
|
chromecastReady ? (
|
||||||
|
<Feather name="cast" size={20} color="white" />
|
||||||
|
) : (
|
||||||
|
<Ionicons name="play-circle" size={24} color="white" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{runtimeTicksToMinutes(item?.RunTimeTicks)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -23,31 +23,13 @@ import { chromecastProfile } from "@/utils/profiles/chromecast";
|
|||||||
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
import { reportPlaybackStopped } from "@/utils/jellyfin/playstate/reportPlaybackStopped";
|
||||||
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
|
||||||
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
||||||
|
import { currentlyPlayingItemAtom } from "./CurrentlyPlayingBar";
|
||||||
|
|
||||||
type VideoPlayerProps = {
|
type VideoPlayerProps = {
|
||||||
itemId: string;
|
itemId: string;
|
||||||
onChangePlaybackURL: (url: string | null) => void;
|
onChangePlaybackURL: (url: string | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BITRATES = [
|
|
||||||
{
|
|
||||||
key: "Max",
|
|
||||||
value: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "4 Mb/s",
|
|
||||||
value: 4000000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "2 Mb/s",
|
|
||||||
value: 2000000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "500 Kb/s",
|
|
||||||
value: 500000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
itemId,
|
itemId,
|
||||||
onChangePlaybackURL,
|
onChangePlaybackURL,
|
||||||
@@ -194,6 +176,8 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
});
|
});
|
||||||
}, [item, client, playbackURL]);
|
}, [item, client, playbackURL]);
|
||||||
|
|
||||||
|
const [cp, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
videoRef.current?.pause();
|
videoRef.current?.pause();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -263,14 +247,15 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
disabled={!enableVideo}
|
disabled={!enableVideo}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (chromecastReady) {
|
// if (chromecastReady) {
|
||||||
cast();
|
// cast();
|
||||||
} else {
|
// } else {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
if (!videoRef.current) return;
|
// if (!videoRef.current) return;
|
||||||
videoRef.current.presentFullscreenPlayer();
|
// videoRef.current.presentFullscreenPlayer();
|
||||||
}, 1000);
|
// }, 1000);
|
||||||
}
|
// }
|
||||||
|
if (item) setCp(item);
|
||||||
}}
|
}}
|
||||||
iconRight={
|
iconRight={
|
||||||
chromecastReady ? (
|
chromecastReady ? (
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { TextInputProps, TextProps } from "react-native";
|
import { TextInputProps, TextProps } from "react-native";
|
||||||
import { TextInput } from "react-native";
|
import { TextInput } from "react-native";
|
||||||
export function Input(props: TextInputProps) {
|
export function Input(props: TextInputProps) {
|
||||||
const { style, ...otherProps } = props;
|
const { style, ...otherProps } = props;
|
||||||
|
const inputRef = React.useRef<TextInput>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
|
ref={inputRef}
|
||||||
className="p-4 border border-neutral-800 rounded-xl bg-neutral-900"
|
className="p-4 border border-neutral-800 rounded-xl bg-neutral-900"
|
||||||
allowFontScaling={false}
|
allowFontScaling={false}
|
||||||
style={[{ color: "white" }, style]}
|
style={[{ color: "white" }, style]}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
placeholderTextColor={"#9CA3AF"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { router } from "expo-router";
|
|
||||||
import { TouchableOpacity } from "react-native";
|
import { TouchableOpacity } from "react-native";
|
||||||
import * as ContextMenu from "zeego/context-menu";
|
import * as ContextMenu from "zeego/context-menu";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
import { useFiles } from "@/hooks/useFiles";
|
import { useFiles } from "@/hooks/useFiles";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "expo-haptics";
|
||||||
import { useRef, useMemo, useState } from "react";
|
import { useCallback } from "react";
|
||||||
import Video, { VideoRef } from "react-native-video";
|
|
||||||
import * as FileSystem from "expo-file-system";
|
import * as FileSystem from "expo-file-system";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { currentlyPlayingItemAtom } from "../CurrentlyPlayingBar";
|
||||||
|
|
||||||
export const EpisodeCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
export const EpisodeCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||||
const { deleteFile } = useFiles();
|
const { deleteFile } = useFiles();
|
||||||
const videoRef = useRef<VideoRef | null>(null);
|
const [_, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
|
||||||
|
|
||||||
const openFile = () => {
|
const openFile = useCallback(() => {
|
||||||
videoRef.current?.presentFullscreenPlayer();
|
setCp({
|
||||||
};
|
item,
|
||||||
|
playbackUrl: `${FileSystem.documentDirectory}/${item.Id}.mp4`,
|
||||||
const fileUrl = useMemo(() => {
|
});
|
||||||
return `${FileSystem.documentDirectory}/${item.Id}.mp4`;
|
|
||||||
}, [item]);
|
}, [item]);
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
@@ -72,26 +70,6 @@ export const EpisodeCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
|||||||
))}
|
))}
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
</ContextMenu.Root>
|
</ContextMenu.Root>
|
||||||
|
|
||||||
<Video
|
|
||||||
style={{ width: 0, height: 0 }}
|
|
||||||
source={{
|
|
||||||
uri: fileUrl,
|
|
||||||
isNetwork: false,
|
|
||||||
}}
|
|
||||||
controls
|
|
||||||
onFullscreenPlayerDidDismiss={() => {
|
|
||||||
setIsPlaying(false);
|
|
||||||
videoRef.current?.pause();
|
|
||||||
}}
|
|
||||||
onFullscreenPlayerDidPresent={() => {
|
|
||||||
setIsPlaying(true);
|
|
||||||
videoRef.current?.resume();
|
|
||||||
}}
|
|
||||||
ref={videoRef}
|
|
||||||
resizeMode="contain"
|
|
||||||
paused={!isPlaying}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,30 +3,22 @@ import { Text } from "../common/Text";
|
|||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { runtimeTicksToMinutes } from "@/utils/time";
|
import { runtimeTicksToMinutes } from "@/utils/time";
|
||||||
import * as ContextMenu from "zeego/context-menu";
|
import * as ContextMenu from "zeego/context-menu";
|
||||||
import { router } from "expo-router";
|
|
||||||
import { useFiles } from "@/hooks/useFiles";
|
import { useFiles } from "@/hooks/useFiles";
|
||||||
import Video, {
|
|
||||||
OnBufferData,
|
|
||||||
OnPlaybackStateChangedData,
|
|
||||||
OnProgressData,
|
|
||||||
OnVideoErrorData,
|
|
||||||
VideoRef,
|
|
||||||
} from "react-native-video";
|
|
||||||
import * as FileSystem from "expo-file-system";
|
import * as FileSystem from "expo-file-system";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { useCallback } from "react";
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "expo-haptics";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { currentlyPlayingItemAtom } from "../CurrentlyPlayingBar";
|
||||||
|
|
||||||
export const MovieCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
export const MovieCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
||||||
const { deleteFile } = useFiles();
|
const { deleteFile } = useFiles();
|
||||||
const videoRef = useRef<VideoRef | null>(null);
|
const [_, setCp] = useAtom(currentlyPlayingItemAtom);
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
|
||||||
|
|
||||||
const openFile = () => {
|
const openFile = useCallback(() => {
|
||||||
videoRef.current?.presentFullscreenPlayer();
|
setCp({
|
||||||
};
|
item,
|
||||||
|
playbackUrl: `${FileSystem.documentDirectory}/${item.Id}.mp4`,
|
||||||
const fileUrl = useMemo(() => {
|
});
|
||||||
return `${FileSystem.documentDirectory}/${item.Id}.mp4`;
|
|
||||||
}, [item]);
|
}, [item]);
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
@@ -82,26 +74,6 @@ export const MovieCard: React.FC<{ item: BaseItemDto }> = ({ item }) => {
|
|||||||
))}
|
))}
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
</ContextMenu.Root>
|
</ContextMenu.Root>
|
||||||
|
|
||||||
<Video
|
|
||||||
style={{ width: 0, height: 0 }}
|
|
||||||
source={{
|
|
||||||
uri: fileUrl,
|
|
||||||
isNetwork: false,
|
|
||||||
}}
|
|
||||||
controls
|
|
||||||
onFullscreenPlayerDidDismiss={() => {
|
|
||||||
setIsPlaying(false);
|
|
||||||
videoRef.current?.pause();
|
|
||||||
}}
|
|
||||||
onFullscreenPlayerDidPresent={() => {
|
|
||||||
setIsPlaying(true);
|
|
||||||
videoRef.current?.resume();
|
|
||||||
}}
|
|
||||||
ref={videoRef}
|
|
||||||
resizeMode="contain"
|
|
||||||
paused={!isPlaying}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { router } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { useEffect, useState } from "react";
|
import { useMemo } from "react";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||||
import { HorizontalScroll } from "../common/HorrizontalScroll";
|
|
||||||
import { Text } from "../common/Text";
|
|
||||||
import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
import ContinueWatchingPoster from "../ContinueWatchingPoster";
|
||||||
import { ItemCardText } from "../ItemCardText";
|
import { ItemCardText } from "../ItemCardText";
|
||||||
|
import { HorizontalScroll } from "../common/HorrizontalScroll";
|
||||||
|
import { Text } from "../common/Text";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const seasonIndexAtom = atom<number>(1);
|
||||||
|
|
||||||
export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
|
const [seasonIndex, setSeasonIndex] = useAtom(seasonIndexAtom);
|
||||||
|
|
||||||
const [selectedSeason, setSelectedSeason] = useState<number | null>(null);
|
const router = useRouter();
|
||||||
const [selectedSeasonId, setSelectedSeasonId] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const { data: seasons } = useQuery({
|
const { data: seasons } = useQuery({
|
||||||
queryKey: ["seasons", item.Id],
|
queryKey: ["seasons", item.Id],
|
||||||
@@ -38,7 +40,7 @@ export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `MediaBrowser DeviceId="${api.deviceInfo.id}", Token="${api.accessToken}"`,
|
Authorization: `MediaBrowser DeviceId="${api.deviceInfo.id}", Token="${api.accessToken}"`,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data.Items;
|
return response.data.Items;
|
||||||
@@ -46,6 +48,12 @@ export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
|||||||
enabled: !!api && !!user?.Id && !!item.Id,
|
enabled: !!api && !!user?.Id && !!item.Id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedSeasonId: string | null = useMemo(
|
||||||
|
() =>
|
||||||
|
seasons?.find((season: any) => season.IndexNumber === seasonIndex)?.Id,
|
||||||
|
[seasons, seasonIndex],
|
||||||
|
);
|
||||||
|
|
||||||
const { data: episodes } = useQuery({
|
const { data: episodes } = useQuery({
|
||||||
queryKey: ["episodes", item.Id, selectedSeasonId],
|
queryKey: ["episodes", item.Id, selectedSeasonId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -62,7 +70,7 @@ export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `MediaBrowser DeviceId="${api.deviceInfo.id}", Token="${api.accessToken}"`,
|
Authorization: `MediaBrowser DeviceId="${api.deviceInfo.id}", Token="${api.accessToken}"`,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data.Items as BaseItemDto[];
|
return response.data.Items as BaseItemDto[];
|
||||||
@@ -70,22 +78,13 @@ export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
|||||||
enabled: !!api && !!user?.Id && !!item.Id && !!selectedSeasonId,
|
enabled: !!api && !!user?.Id && !!item.Id && !!selectedSeasonId,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!seasons || seasons.length === 0) return;
|
|
||||||
|
|
||||||
setSelectedSeasonId(
|
|
||||||
seasons.find((season: any) => season.IndexNumber === 1)?.Id
|
|
||||||
);
|
|
||||||
setSelectedSeason(1);
|
|
||||||
}, [seasons]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="mb-2">
|
<View className="mb-2">
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
<View className="flex flex-row px-4">
|
<View className="flex flex-row px-4">
|
||||||
<TouchableOpacity className="bg-neutral-900 rounded-2xl border-neutral-900 border px-3 py-2 flex flex-row items-center justify-between">
|
<TouchableOpacity className="bg-neutral-900 rounded-2xl border-neutral-900 border px-3 py-2 flex flex-row items-center justify-between">
|
||||||
<Text>Season {selectedSeason}</Text>
|
<Text>Season {seasonIndex}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
@@ -103,8 +102,7 @@ export const SeasonPicker: React.FC<Props> = ({ item }) => {
|
|||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={season.Name}
|
key={season.Name}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setSelectedSeason(season.IndexNumber);
|
setSeasonIndex(season.IndexNumber);
|
||||||
setSelectedSeasonId(season.Id);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemTitle>{season.Name}</DropdownMenu.ItemTitle>
|
<DropdownMenu.ItemTitle>{season.Name}</DropdownMenu.ItemTitle>
|
||||||
|
|||||||
7
eas.json
@@ -20,7 +20,12 @@
|
|||||||
"simulator": true
|
"simulator": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {}
|
"production": {
|
||||||
|
"channel": "0.0.6",
|
||||||
|
"android": {
|
||||||
|
"image": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"submit": {
|
"submit": {
|
||||||
"production": {}
|
"production": {}
|
||||||
|
|||||||
30
ios/.gitignore
vendored
@@ -1,30 +0,0 @@
|
|||||||
# OSX
|
|
||||||
#
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# Xcode
|
|
||||||
#
|
|
||||||
build/
|
|
||||||
*.pbxuser
|
|
||||||
!default.pbxuser
|
|
||||||
*.mode1v3
|
|
||||||
!default.mode1v3
|
|
||||||
*.mode2v3
|
|
||||||
!default.mode2v3
|
|
||||||
*.perspectivev3
|
|
||||||
!default.perspectivev3
|
|
||||||
xcuserdata
|
|
||||||
*.xccheckout
|
|
||||||
*.moved-aside
|
|
||||||
DerivedData
|
|
||||||
*.hmap
|
|
||||||
*.ipa
|
|
||||||
*.xcuserstate
|
|
||||||
project.xcworkspace
|
|
||||||
.xcode.env.local
|
|
||||||
|
|
||||||
# Bundle artifacts
|
|
||||||
*.jsbundle
|
|
||||||
|
|
||||||
# CocoaPods
|
|
||||||
/Pods/
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# This `.xcode.env` file is versioned and is used to source the environment
|
|
||||||
# used when running script phases inside Xcode.
|
|
||||||
# To customize your local environment, you can create an `.xcode.env.local`
|
|
||||||
# file that is not versioned.
|
|
||||||
|
|
||||||
# NODE_BINARY variable contains the PATH to the node executable.
|
|
||||||
#
|
|
||||||
# Customize the NODE_BINARY variable here.
|
|
||||||
# For example, to use nvm with brew, add the following line
|
|
||||||
# . "$(brew --prefix nvm)/nvm.sh" --no-use
|
|
||||||
export NODE_BINARY=$(command -v node)
|
|
||||||
58
ios/Podfile
@@ -1,58 +0,0 @@
|
|||||||
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
|
||||||
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
|
|
||||||
|
|
||||||
require 'json'
|
|
||||||
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
|
|
||||||
|
|
||||||
ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
|
|
||||||
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
|
|
||||||
|
|
||||||
platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4'
|
|
||||||
install! 'cocoapods',
|
|
||||||
:deterministic_uuids => false
|
|
||||||
|
|
||||||
prepare_react_native_project!
|
|
||||||
|
|
||||||
target 'Streamyfin' do
|
|
||||||
use_expo_modules!
|
|
||||||
config = use_native_modules!
|
|
||||||
|
|
||||||
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
|
|
||||||
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
|
|
||||||
|
|
||||||
use_react_native!(
|
|
||||||
:path => config[:reactNativePath],
|
|
||||||
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
|
|
||||||
# An absolute path to your application root.
|
|
||||||
:app_path => "#{Pod::Config.instance.installation_root}/..",
|
|
||||||
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
|
|
||||||
)
|
|
||||||
|
|
||||||
post_install do |installer|
|
|
||||||
react_native_post_install(
|
|
||||||
installer,
|
|
||||||
config[:reactNativePath],
|
|
||||||
:mac_catalyst_enabled => false,
|
|
||||||
:ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
|
|
||||||
)
|
|
||||||
|
|
||||||
# This is necessary for Xcode 14, because it signs resource bundles by default
|
|
||||||
# when building for devices.
|
|
||||||
installer.target_installation_results.pod_target_installation_results
|
|
||||||
.each do |pod_name, target_installation_result|
|
|
||||||
target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
|
|
||||||
resource_bundle_target.build_configurations.each do |config|
|
|
||||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
post_integrate do |installer|
|
|
||||||
begin
|
|
||||||
expo_patch_react_imports!(installer)
|
|
||||||
rescue => e
|
|
||||||
Pod::UI.warn e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
2088
ios/Podfile.lock
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"expo.jsEngine": "hermes",
|
|
||||||
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
|
|
||||||
"ios.deploymentTarget": "14.0",
|
|
||||||
"apple.extraPods": "[]",
|
|
||||||
"apple.ccacheEnabled": "false",
|
|
||||||
"apple.privacyManifestAggregationEnabled": "true"
|
|
||||||
}
|
|
||||||
@@ -1,579 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 46;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
|
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
|
||||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
|
||||||
303D6C1B994E4AB730BB4D25 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 5677A7148111D7C0235E3185 /* PrivacyInfo.xcprivacy */; };
|
|
||||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
|
||||||
7B8C1837932545C6A7001A29 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46971613083A41BAB9656F14 /* noop-file.swift */; };
|
|
||||||
96905EF65AED1B983A6B3ABC /* libPods-Streamyfin.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Streamyfin.a */; };
|
|
||||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
|
|
||||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
13B07F961A680F5B00A75B9A /* Streamyfin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Streamyfin.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Streamyfin/AppDelegate.h; sourceTree = "<group>"; };
|
|
||||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Streamyfin/AppDelegate.mm; sourceTree = "<group>"; };
|
|
||||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Streamyfin/Images.xcassets; sourceTree = "<group>"; };
|
|
||||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Streamyfin/Info.plist; sourceTree = "<group>"; };
|
|
||||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Streamyfin/main.m; sourceTree = "<group>"; };
|
|
||||||
46971613083A41BAB9656F14 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Streamyfin/noop-file.swift"; sourceTree = "<group>"; };
|
|
||||||
5677A7148111D7C0235E3185 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Streamyfin/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
|
||||||
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Streamyfin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Streamyfin.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
6C2E3173556A471DD304B334 /* Pods-Streamyfin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Streamyfin.debug.xcconfig"; path = "Target Support Files/Pods-Streamyfin/Pods-Streamyfin.debug.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
7A4D352CD337FB3A3BF06240 /* Pods-Streamyfin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Streamyfin.release.xcconfig"; path = "Target Support Files/Pods-Streamyfin/Pods-Streamyfin.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Streamyfin/SplashScreen.storyboard; sourceTree = "<group>"; };
|
|
||||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
|
||||||
E9097609D0714599AF91F55B /* Streamyfin-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Streamyfin-Bridging-Header.h"; path = "Streamyfin/Streamyfin-Bridging-Header.h"; sourceTree = "<group>"; };
|
|
||||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
|
||||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Streamyfin/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
96905EF65AED1B983A6B3ABC /* libPods-Streamyfin.a in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
13B07FAE1A68108700A75B9A /* Streamyfin */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
BB2F792B24A3F905000567C9 /* Supporting */,
|
|
||||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
|
||||||
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
|
|
||||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
|
||||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
|
||||||
13B07FB71A68108700A75B9A /* main.m */,
|
|
||||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
|
|
||||||
46971613083A41BAB9656F14 /* noop-file.swift */,
|
|
||||||
E9097609D0714599AF91F55B /* Streamyfin-Bridging-Header.h */,
|
|
||||||
5677A7148111D7C0235E3185 /* PrivacyInfo.xcprivacy */,
|
|
||||||
);
|
|
||||||
name = Streamyfin;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
|
||||||
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Streamyfin.a */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
);
|
|
||||||
name = Libraries;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
83CBB9F61A601CBA00E9B192 = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
13B07FAE1A68108700A75B9A /* Streamyfin */,
|
|
||||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
|
||||||
83CBBA001A601CBA00E9B192 /* Products */,
|
|
||||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
|
||||||
D65327D7A22EEC0BE12398D9 /* Pods */,
|
|
||||||
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */,
|
|
||||||
);
|
|
||||||
indentWidth = 2;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
tabWidth = 2;
|
|
||||||
usesTabs = 0;
|
|
||||||
};
|
|
||||||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
13B07F961A680F5B00A75B9A /* Streamyfin.app */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
92DBD88DE9BF7D494EA9DA96 /* Streamyfin */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */,
|
|
||||||
);
|
|
||||||
name = Streamyfin;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
BB2F792B24A3F905000567C9 /* Supporting */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
BB2F792C24A3F905000567C9 /* Expo.plist */,
|
|
||||||
);
|
|
||||||
name = Supporting;
|
|
||||||
path = Streamyfin/Supporting;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D65327D7A22EEC0BE12398D9 /* Pods */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
6C2E3173556A471DD304B334 /* Pods-Streamyfin.debug.xcconfig */,
|
|
||||||
7A4D352CD337FB3A3BF06240 /* Pods-Streamyfin.release.xcconfig */,
|
|
||||||
);
|
|
||||||
path = Pods;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
92DBD88DE9BF7D494EA9DA96 /* Streamyfin */,
|
|
||||||
);
|
|
||||||
name = ExpoModulesProviders;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
13B07F861A680F5B00A75B9A /* Streamyfin */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Streamyfin" */;
|
|
||||||
buildPhases = (
|
|
||||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
|
||||||
5AE4F51AB7DD565AD6F401F0 /* [Expo] Configure project */,
|
|
||||||
13B07F871A680F5B00A75B9A /* Sources */,
|
|
||||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
|
||||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
|
||||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
|
||||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
|
||||||
E8388EA82119C85BBE90EA67 /* [CP] Embed Pods Frameworks */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
name = Streamyfin;
|
|
||||||
productName = Streamyfin;
|
|
||||||
productReference = 13B07F961A680F5B00A75B9A /* Streamyfin.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
LastUpgradeCheck = 1130;
|
|
||||||
TargetAttributes = {
|
|
||||||
13B07F861A680F5B00A75B9A = {
|
|
||||||
LastSwiftMigration = 1250;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Streamyfin" */;
|
|
||||||
compatibilityVersion = "Xcode 3.2";
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
Base,
|
|
||||||
);
|
|
||||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
|
||||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
13B07F861A680F5B00A75B9A /* Streamyfin */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */,
|
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
|
||||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
|
|
||||||
303D6C1B994E4AB730BB4D25 /* PrivacyInfo.xcprivacy in Resources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
|
||||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
alwaysOutOfDate = 1;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "Bundle React Native code and images";
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
|
|
||||||
};
|
|
||||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
"$(DERIVED_FILE_DIR)/Pods-Streamyfin-checkManifestLockResult.txt",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
5AE4F51AB7DD565AD6F401F0 /* [Expo] Configure project */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
alwaysOutOfDate = 1;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[Expo] Configure project";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Streamyfin/expo-configure-project.sh\"\n";
|
|
||||||
};
|
|
||||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Streamyfin/Pods-Streamyfin-resources.sh",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/Protobuf/Protobuf_Privacy.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle",
|
|
||||||
"${PODS_ROOT}/google-cast-sdk/Resources/GoogleCastCoreResources.bundle",
|
|
||||||
"${PODS_ROOT}/google-cast-sdk/Resources/GoogleCastUIResources.bundle",
|
|
||||||
"${PODS_ROOT}/google-cast-sdk/Resources/MaterialDialogs.bundle",
|
|
||||||
"${PODS_CONFIGURATION_BUILD_DIR}/google-cast-sdk/GoogleCast.bundle",
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
|
||||||
outputPaths = (
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Protobuf_Privacy.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SDWebImage.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastCoreResources.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastUIResources.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialDialogs.bundle",
|
|
||||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCast.bundle",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Streamyfin/Pods-Streamyfin-resources.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
E8388EA82119C85BBE90EA67 /* [CP] Embed Pods Frameworks */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Streamyfin/Pods-Streamyfin-frameworks.sh",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileVLCKit/MobileVLCKit.framework/MobileVLCKit",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/ffmpegkit.framework/ffmpegkit",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavcodec.framework/libavcodec",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavdevice.framework/libavdevice",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavfilter.framework/libavfilter",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavformat.framework/libavformat",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libavutil.framework/libavutil",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libswresample.framework/libswresample",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg-kit-ios-https/libswscale.framework/libswscale",
|
|
||||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
|
||||||
outputPaths = (
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileVLCKit.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ffmpegkit.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavcodec.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavdevice.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavfilter.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavformat.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavutil.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libswresample.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libswscale.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Streamyfin/Pods-Streamyfin-frameworks.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
13B07F871A680F5B00A75B9A /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
|
|
||||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
|
||||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
|
|
||||||
7B8C1837932545C6A7001A29 /* noop-file.swift in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-Streamyfin.debug.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = Streamyfin/Streamyfin.entitlements;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
ENABLE_BITCODE = NO;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"FB_SONARKIT_ENABLED=1",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = Streamyfin/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
OTHER_LDFLAGS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"-ObjC",
|
|
||||||
"-lc++",
|
|
||||||
);
|
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.fredrikburmester.streamyfin;
|
|
||||||
PRODUCT_NAME = Streamyfin;
|
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Streamyfin/Streamyfin-Bridging-Header.h";
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-Streamyfin.release.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = Streamyfin/Streamyfin.entitlements;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
INFOPLIST_FILE = Streamyfin/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
||||||
MARKETING_VERSION = 1.0;
|
|
||||||
OTHER_LDFLAGS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"-ObjC",
|
|
||||||
"-lc++",
|
|
||||||
);
|
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.fredrikburmester.streamyfin;
|
|
||||||
PRODUCT_NAME = Streamyfin;
|
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Streamyfin/Streamyfin-Bridging-Header.h";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CC = "";
|
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
CXX = "";
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_TESTABILITY = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
|
||||||
LD = "";
|
|
||||||
LDPLUSPLUS = "";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
|
||||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
|
||||||
OTHER_LDFLAGS = (
|
|
||||||
"$(inherited)",
|
|
||||||
" ",
|
|
||||||
);
|
|
||||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
USE_HERMES = true;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
83CBBA211A601CBA00E9B192 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CC = "";
|
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
|
||||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
|
||||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_COMMA = YES;
|
|
||||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
|
||||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
|
||||||
CLANG_WARN_EMPTY_BODY = YES;
|
|
||||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
|
||||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
|
||||||
CLANG_WARN_INT_CONVERSION = YES;
|
|
||||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
|
||||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
|
||||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
|
||||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
|
||||||
COPY_PHASE_STRIP = YES;
|
|
||||||
CXX = "";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
|
|
||||||
LD = "";
|
|
||||||
LDPLUSPLUS = "";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
|
||||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
OTHER_LDFLAGS = (
|
|
||||||
"$(inherited)",
|
|
||||||
" ",
|
|
||||||
);
|
|
||||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
USE_HERMES = true;
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Streamyfin" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
13B07F941A680F5B00A75B9A /* Debug */,
|
|
||||||
13B07F951A680F5B00A75B9A /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Streamyfin" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
83CBBA201A601CBA00E9B192 /* Debug */,
|
|
||||||
83CBBA211A601CBA00E9B192 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
};
|
|
||||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1130"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
|
||||||
BuildableName = "Streamyfin.app"
|
|
||||||
BlueprintName = "Streamyfin"
|
|
||||||
ReferencedContainer = "container:Streamyfin.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
<TestableReference
|
|
||||||
skipped = "NO">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
|
||||||
BuildableName = "StreamyfinTests.xctest"
|
|
||||||
BlueprintName = "StreamyfinTests"
|
|
||||||
ReferencedContainer = "container:Streamyfin.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
</Testables>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
|
||||||
BuildableName = "Streamyfin.app"
|
|
||||||
BlueprintName = "Streamyfin"
|
|
||||||
ReferencedContainer = "container:Streamyfin.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
|
||||||
BuildableName = "Streamyfin.app"
|
|
||||||
BlueprintName = "Streamyfin"
|
|
||||||
ReferencedContainer = "container:Streamyfin.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
||||||
10
ios/Streamyfin.xcworkspace/contents.xcworkspacedata
generated
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "group:Streamyfin.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
|
||||||
location = "group:Pods/Pods.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#import <RCTAppDelegate.h>
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import <Expo/Expo.h>
|
|
||||||
|
|
||||||
@interface AppDelegate : EXAppDelegateWrapper
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#import "AppDelegate.h"
|
|
||||||
// @generated begin react-native-google-cast-import - expo prebuild (DO NOT MODIFY) sync-da0acf16745f87cea5bffba9c0cc3a4f5e4387ea
|
|
||||||
#if __has_include(<GoogleCast/GoogleCast.h>)
|
|
||||||
#import <GoogleCast/GoogleCast.h>
|
|
||||||
#endif
|
|
||||||
// @generated end react-native-google-cast-import
|
|
||||||
|
|
||||||
#import <React/RCTBundleURLProvider.h>
|
|
||||||
#import <React/RCTLinkingManager.h>
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
||||||
{
|
|
||||||
// @generated begin react-native-google-cast-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-8901be60b982d2ae9c658b1e8c50634d61bb5091
|
|
||||||
#if __has_include(<GoogleCast/GoogleCast.h>)
|
|
||||||
NSString *receiverAppID = kGCKDefaultMediaReceiverApplicationID;
|
|
||||||
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:receiverAppID];
|
|
||||||
GCKCastOptions* options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
|
|
||||||
options.disableDiscoveryAutostart = false;
|
|
||||||
options.startDiscoveryAfterFirstTapOnCastButton = true;
|
|
||||||
options.suspendSessionsWhenBackgrounded = true;
|
|
||||||
[GCKCastContext setSharedInstanceWithOptions:options];
|
|
||||||
[GCKCastContext sharedInstance].useDefaultExpandedMediaControls = true;
|
|
||||||
#endif
|
|
||||||
// @generated end react-native-google-cast-didFinishLaunchingWithOptions
|
|
||||||
self.moduleName = @"main";
|
|
||||||
|
|
||||||
// You can add your custom initial props in the dictionary below.
|
|
||||||
// They will be passed down to the ViewController used by React Native.
|
|
||||||
self.initialProps = @{};
|
|
||||||
|
|
||||||
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
|
||||||
{
|
|
||||||
return [self bundleURL];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSURL *)bundleURL
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
|
|
||||||
#else
|
|
||||||
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linking API
|
|
||||||
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
|
||||||
return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Universal Links
|
|
||||||
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
|
|
||||||
BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
|
|
||||||
return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
|
|
||||||
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
|
||||||
{
|
|
||||||
return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
|
|
||||||
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
|
|
||||||
{
|
|
||||||
return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
|
|
||||||
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
|
|
||||||
{
|
|
||||||
return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
Before Width: | Height: | Size: 812 KiB |
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"filename": "App-Icon-1024x1024@1x.png",
|
|
||||||
"idiom": "universal",
|
|
||||||
"platform": "ios",
|
|
||||||
"size": "1024x1024"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info": {
|
|
||||||
"version": 1,
|
|
||||||
"author": "expo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "expo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"idiom": "universal",
|
|
||||||
"filename": "image.png",
|
|
||||||
"scale": "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom": "universal",
|
|
||||||
"scale": "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom": "universal",
|
|
||||||
"scale": "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info": {
|
|
||||||
"version": 1,
|
|
||||||
"author": "expo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 170 KiB |
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"idiom": "universal",
|
|
||||||
"filename": "image.png",
|
|
||||||
"scale": "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom": "universal",
|
|
||||||
"scale": "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom": "universal",
|
|
||||||
"scale": "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info": {
|
|
||||||
"version": 1,
|
|
||||||
"author": "expo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 68 B |
@@ -1,101 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Streamyfin</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>0.0.6</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleURLTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>streamyfin</string>
|
|
||||||
<string>com.fredrikburmester.streamyfin</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>exp+streamyfin</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSAllowsLocalNetworking</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>NSBonjourServices</key>
|
|
||||||
<array>
|
|
||||||
<string>_googlecast._tcp</string>
|
|
||||||
<string>_CC1AD845._googlecast._tcp</string>
|
|
||||||
</array>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>The app needs access to your camera to scan barcodes.</string>
|
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
|
||||||
<string>${PRODUCT_NAME} uses the local network to discover Cast-enabled devices on your WiFi network</string>
|
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
|
||||||
<string>The app needs access to your microphone.</string>
|
|
||||||
<key>NSUserActivityTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
|
||||||
</array>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>SplashScreen</string>
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array>
|
|
||||||
<string>arm64</string>
|
|
||||||
</array>
|
|
||||||
<key>UIRequiresFullScreen</key>
|
|
||||||
<false/>
|
|
||||||
<key>UIStatusBarStyle</key>
|
|
||||||
<string>UIStatusBarStyleDefault</string>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UIUserInterfaceStyle</key>
|
|
||||||
<string>Dark</string>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
|
||||||
<key>NSBonjourServices</key>
|
|
||||||
<array>
|
|
||||||
<string>_googlecast._tcp</string>
|
|
||||||
<string>_CC1AD845._googlecast._tcp</string>
|
|
||||||
</array>
|
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
|
||||||
<string>${PRODUCT_NAME} uses the local network to discover Cast-enabled devices on your WiFi network.</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>CA92.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>35F9.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>0A2A.1</string>
|
|
||||||
<string>3B52.1</string>
|
|
||||||
<string>C617.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>E174.1</string>
|
|
||||||
<string>85F4.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>NSPrivacyCollectedDataTypes</key>
|
|
||||||
<array/>
|
|
||||||
<key>NSPrivacyTracking</key>
|
|
||||||
<false/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="EXPO-VIEWCONTROLLER-1">
|
|
||||||
<device id="retina5_5" orientation="portrait" appearance="light"/>
|
|
||||||
<dependencies>
|
|
||||||
<deployment identifier="iOS"/>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
|
||||||
<scenes>
|
|
||||||
<scene sceneID="EXPO-SCENE-1">
|
|
||||||
<objects>
|
|
||||||
<viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
|
|
||||||
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" insetsLayoutMarginsFromSafeArea="NO" image="SplashScreenBackground" translatesAutoresizingMaskIntoConstraints="NO" id="EXPO-SplashScreenBackground" userLabel="SplashScreenBackground">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
|
|
||||||
</imageView>
|
|
||||||
<imageView id="EXPO-SplashScreen" userLabel="SplashScreen" image="SplashScreen" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
|
|
||||||
<rect key="frame" x="0" y="0" width="414" height="736"/>
|
|
||||||
</imageView>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="1gX-mQ-vu6"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="6tX-OG-Sck"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="ABX-8g-7v4"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreenBackground" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="jkI-2V-eW5"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="2VS-Uz-0LU"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="LhH-Ei-DKo"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="I6l-TP-6fn"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="nbp-HC-eaG"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="top" secondItem="EXPO-ContainerView" secondAttribute="top" id="83fcb9b545b870ba44c24f0feeb116490c499c52"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="leading" secondItem="EXPO-ContainerView" secondAttribute="leading" id="61d16215e44b98e39d0a2c74fdbfaaa22601b12c"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="trailing" secondItem="EXPO-ContainerView" secondAttribute="trailing" id="f934da460e9ab5acae3ad9987d5b676a108796c1"/>
|
|
||||||
<constraint firstItem="EXPO-SplashScreen" firstAttribute="bottom" secondItem="EXPO-ContainerView" secondAttribute="bottom" id="d6a0be88096b36fb132659aa90203d39139deda9"/>
|
|
||||||
</constraints>
|
|
||||||
<viewLayoutGuide key="safeArea" id="Rmq-lb-GrQ"/>
|
|
||||||
</view>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="EXPO-PLACEHOLDER-1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="140.625" y="129.4921875"/>
|
|
||||||
</scene>
|
|
||||||
</scenes>
|
|
||||||
<resources>
|
|
||||||
<image name="SplashScreenBackground" width="1" height="1"/>
|
|
||||||
<image name="SplashScreen" width="414" height="736"/>
|
|
||||||
</resources>
|
|
||||||
</document>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
//
|
|
||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
|
||||||
//
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict/>
|
|
||||||
</plist>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>EXUpdatesCheckOnLaunch</key>
|
|
||||||
<string>ALWAYS</string>
|
|
||||||
<key>EXUpdatesEnabled</key>
|
|
||||||
<false/>
|
|
||||||
<key>EXUpdatesLaunchWaitMs</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
|
||||||
@autoreleasepool {
|
|
||||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
//
|
|
||||||
// @generated
|
|
||||||
// A blank Swift file must be created for native modules with Swift files to work correctly.
|
|
||||||
//
|
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"@tanstack/react-query": "^5.51.16",
|
"@tanstack/react-query": "^5.51.16",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"expo": "~51.0.26",
|
"expo": "~51.0.26",
|
||||||
|
"expo-blur": "~13.0.2",
|
||||||
"expo-build-properties": "~0.12.5",
|
"expo-build-properties": "~0.12.5",
|
||||||
"expo-constants": "~16.0.2",
|
"expo-constants": "~16.0.2",
|
||||||
"expo-dev-client": "~4.0.22",
|
"expo-dev-client": "~4.0.22",
|
||||||
@@ -35,10 +36,12 @@
|
|||||||
"expo-image": "~1.12.13",
|
"expo-image": "~1.12.13",
|
||||||
"expo-keep-awake": "~13.0.2",
|
"expo-keep-awake": "~13.0.2",
|
||||||
"expo-linking": "~6.3.1",
|
"expo-linking": "~6.3.1",
|
||||||
|
"expo-navigation-bar": "~3.0.7",
|
||||||
"expo-router": "~3.5.21",
|
"expo-router": "~3.5.21",
|
||||||
"expo-splash-screen": "~0.27.5",
|
"expo-splash-screen": "~0.27.5",
|
||||||
"expo-status-bar": "~1.12.1",
|
"expo-status-bar": "~1.12.1",
|
||||||
"expo-system-ui": "~3.0.7",
|
"expo-system-ui": "~3.0.7",
|
||||||
|
"expo-updates": "~0.25.22",
|
||||||
"expo-web-browser": "~13.0.3",
|
"expo-web-browser": "~13.0.3",
|
||||||
"ffmpeg-kit-react-native": "^6.0.2",
|
"ffmpeg-kit-react-native": "^6.0.2",
|
||||||
"jotai": "^2.9.1",
|
"jotai": "^2.9.1",
|
||||||
@@ -60,7 +63,6 @@
|
|||||||
"react-native-url-polyfill": "^2.0.0",
|
"react-native-url-polyfill": "^2.0.0",
|
||||||
"react-native-uuid": "^2.0.2",
|
"react-native-uuid": "^2.0.2",
|
||||||
"react-native-video": "^6.4.3",
|
"react-native-video": "^6.4.3",
|
||||||
"react-native-vlc-media-player": "^1.0.67",
|
|
||||||
"react-native-web": "~0.19.10",
|
"react-native-web": "~0.19.10",
|
||||||
"tailwindcss": "3.3.2",
|
"tailwindcss": "3.3.2",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
|
|||||||