feat: major refactor of navigation

This commit is contained in:
Fredrik Burmester
2024-08-23 16:41:59 +02:00
parent d330dd8db4
commit 725ba1ccaf
27 changed files with 225 additions and 329 deletions

View File

@@ -1,8 +1,9 @@
import { Chromecast } from "@/components/Chromecast";
import { HeaderBackButton } from "@/components/common/HeaderBackButton";
import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack";
import { Feather } from "@expo/vector-icons";
import { Stack, useRouter } from "expo-router";
import { Platform, View } from "react-native";
import { TouchableOpacity } from "react-native";
import { Platform, TouchableOpacity, View } from "react-native";
export default function IndexLayout() {
const router = useRouter();
@@ -45,6 +46,9 @@ export default function IndexLayout() {
),
}}
/>
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
<Stack.Screen key={name} name={name} options={options} />
))}
</Stack>
);
}

View File

@@ -1,34 +1,23 @@
import { Bitrate } from "@/components/BitrateSelector";
import { ItemCardText } from "@/components/ItemCardText";
import { Loader } from "@/components/Loader";
import { OverviewText } from "@/components/OverviewText";
import { Ratings } from "@/components/Ratings";
import { ParallaxScrollView } from "@/components/ParallaxPage";
import { InfiniteHorizontalScroll } from "@/components/common/InfiniteHorrizontalScroll";
import { Text } from "@/components/common/Text";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import { MoviesTitleHeader } from "@/components/movies/MoviesTitleHeader";
import { SeriesTitleHeader } from "@/components/series/SeriesTitleHeader";
import MoviePoster from "@/components/posters/MoviePoster";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById";
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData";
import { chromecastProfile } from "@/utils/profiles/chromecast";
import ios from "@/utils/profiles/ios";
import native from "@/utils/profiles/native";
import old from "@/utils/profiles/old";
import { getItemsApi, getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
import { BaseItemDtoQueryResult } from "@jellyfin/sdk/lib/generated-client/models";
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { Image } from "expo-image";
import { useLocalSearchParams } from "expo-router";
import { useAtom } from "jotai";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useMemo } from "react";
import { View } from "react-native";
import { useCastDevice } from "react-native-google-cast";
import { ParallaxScrollView } from "../../../components/ParallaxPage";
import { InfiniteHorizontalScroll } from "@/components/common/InfiniteHorrizontalScroll";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import MoviePoster from "@/components/posters/MoviePoster";
import { ItemCardText } from "@/components/ItemCardText";
import { BaseItemDtoQueryResult } from "@jellyfin/sdk/lib/generated-client/models";
const page: React.FC = () => {
const local = useLocalSearchParams();

View File

@@ -1,5 +1,6 @@
import { Chromecast } from "@/components/Chromecast";
import { Text } from "@/components/common/Text";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import { SongsList } from "@/components/music/SongsList";
import ArtistPoster from "@/components/posters/ArtistPoster";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
@@ -102,16 +103,11 @@ export default function page() {
<View className="flex flex-row space-x-2 mt-1">
{album.AlbumArtists?.map((a) => (
<TouchableOpacity
key={a.Id}
onPress={() => {
router.push(`/artists/${a.Id}/page`);
}}
>
<TouchableItemRouter key={a.Id} item={album}>
<Text className="font-bold text-purple-600">
{album?.AlbumArtist}
</Text>
</TouchableOpacity>
</TouchableItemRouter>
))}
</View>
</View>

View File

@@ -1,4 +1,5 @@
import { Text } from "@/components/common/Text";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import ArtistPoster from "@/components/posters/ArtistPoster";
import MoviePoster from "@/components/posters/MoviePoster";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
@@ -90,15 +91,13 @@ export default function page() {
justifyContent: "space-between",
}}
renderItem={({ item, index }) => (
<TouchableOpacity
<TouchableItemRouter
style={{
maxWidth: "30%",
width: "100%",
}}
key={index}
onPress={() => {
router.push(`/artists/${item.Id}/page`);
}}
item={item}
>
<View className="flex flex-col gap-y-2">
{collection?.CollectionType === "movies" && (
@@ -110,7 +109,7 @@ export default function page() {
<Text>{item.Name}</Text>
<Text className="opacity-50 text-xs">{item.ProductionYear}</Text>
</View>
</TouchableOpacity>
</TouchableItemRouter>
)}
keyExtractor={(item) => item.Id || ""}
/>

View File

@@ -3,7 +3,6 @@ import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import { FilterButton } from "@/components/filters/FilterButton";
import { ResetFiltersButton } from "@/components/filters/ResetFiltersButton";
import { ItemCardText } from "@/components/ItemCardText";
import { Loader } from "@/components/Loader";
import MoviePoster from "@/components/posters/MoviePoster";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import {
@@ -18,7 +17,6 @@ import {
import {
BaseItemDto,
BaseItemDtoQueryResult,
BaseItemKind,
} from "@jellyfin/sdk/lib/generated-client/models";
import {
getFilterApi,
@@ -27,7 +25,8 @@ import {
} from "@jellyfin/sdk/lib/utils/api";
import { FlashList } from "@shopify/flash-list";
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import { Stack, useLocalSearchParams, useNavigation } from "expo-router";
import { useLocalSearchParams, useNavigation } from "expo-router";
import * as ScreenOrientation from "expo-screen-orientation";
import { useAtom } from "jotai";
import React, {
useCallback,
@@ -36,8 +35,7 @@ import React, {
useMemo,
useState,
} from "react";
import { FlatList, NativeScrollEvent, ScrollView, View } from "react-native";
import * as ScreenOrientation from "expo-screen-orientation";
import { FlatList, View } from "react-native";
const MemoizedTouchableItemRouter = React.memo(TouchableItemRouter);

View File

@@ -3,6 +3,7 @@ import { Bitrate, BitrateSelector } from "@/components/BitrateSelector";
import { DownloadItem } from "@/components/DownloadItem";
import { Loader } from "@/components/Loader";
import { OverviewText } from "@/components/OverviewText";
import { ParallaxScrollView } from "@/components/ParallaxPage";
import { PlayButton } from "@/components/PlayButton";
import { PlayedStatus } from "@/components/PlayedStatus";
import { Ratings } from "@/components/Ratings";
@@ -32,7 +33,6 @@ import { useAtom } from "jotai";
import { useMemo, useState } from "react";
import { View } from "react-native";
import { useCastDevice } from "react-native-google-cast";
import { ParallaxScrollView } from "../../../components/ParallaxPage";
const page: React.FC = () => {
const local = useLocalSearchParams();

View File

@@ -1,15 +1,14 @@
import { Button } from "@/components/Button";
import { Text } from "@/components/common/Text";
import { ListItem } from "@/components/ListItem";
import { SettingToggles } from "@/components/settings/SettingToggles";
import { useFiles } from "@/hooks/useFiles";
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
import { clearLogs, readFromLog } from "@/utils/log";
import { useQuery } from "@tanstack/react-query";
import * as Haptics from "expo-haptics";
import { useAtom } from "jotai";
import { ScrollView, View } from "react-native";
import * as Haptics from "expo-haptics";
import { useFiles } from "@/hooks/useFiles";
import { SettingToggles } from "@/components/settings/SettingToggles";
import { WebSocketsTest } from "@/components/settings/WebsocketsText";
export default function settings() {
const { logout } = useJellyfin();

View File

@@ -1,4 +1,5 @@
import { Stack, useRouter } from "expo-router";
import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack";
import { Stack } from "expo-router";
import { Platform } from "react-native";
export default function IndexLayout() {
@@ -25,6 +26,9 @@ export default function IndexLayout() {
headerShadowVisible: false,
}}
/>
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
<Stack.Screen key={name} name={name} options={options} />
))}
</Stack>
);
}

View File

@@ -1,4 +1,5 @@
import { Text } from "@/components/common/Text";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import { Loader } from "@/components/Loader";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
@@ -62,8 +63,6 @@ interface Props {
}
const LibraryItemCard: React.FC<Props> = ({ library }) => {
const router = useRouter();
const [api] = useAtom(apiAtom);
const url = useMemo(
@@ -78,11 +77,7 @@ const LibraryItemCard: React.FC<Props> = ({ library }) => {
if (!url) return null;
return (
<TouchableOpacity
onPress={() => {
router.push(`/libraries/${library.Id}`);
}}
>
<TouchableItemRouter item={library}>
<View className="flex justify-center rounded-xl w-full relative border border-neutral-900 h-20 ">
<Image
source={{ uri: url }}
@@ -99,6 +94,6 @@ const LibraryItemCard: React.FC<Props> = ({ library }) => {
{library.Name}
</Text>
</View>
</TouchableOpacity>
</TouchableItemRouter>
);
};

View File

@@ -1,3 +1,4 @@
import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack";
import { Stack } from "expo-router";
import { Platform } from "react-native";
@@ -15,6 +16,9 @@ export default function SearchLayout() {
headerShadowVisible: false,
}}
/>
{Object.entries(nestedTabPageScreenOptions).map(([name, options]) => (
<Stack.Screen key={name} name={name} options={options} />
))}
</Stack>
);
}

View File

@@ -257,10 +257,10 @@ export default function search() {
<HorizontalScroll<BaseItemDto>
data={data}
renderItem={(item) => (
<TouchableOpacity
<TouchableItemRouter
key={item.Id}
className="flex flex-col w-28"
onPress={() => router.push(`/items/${item.Id}`)}
item={item}
>
<MoviePoster item={item} key={item.Id} />
<Text numberOfLines={2} className="mt-2">
@@ -269,7 +269,7 @@ export default function search() {
<Text className="opacity-50 text-xs">
{item.ProductionYear}
</Text>
</TouchableOpacity>
</TouchableItemRouter>
)}
/>
)}

View File

@@ -1,13 +1,10 @@
import { router, Tabs } from "expo-router";
import React, { useEffect } from "react";
import * as NavigationBar from "expo-navigation-bar";
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
import { Colors } from "@/constants/Colors";
import { Platform, TouchableOpacity, View } from "react-native";
import { Feather } from "@expo/vector-icons";
import { Chromecast } from "@/components/Chromecast";
import { BlurView } from "expo-blur";
import { StyleSheet } from "react-native";
import * as NavigationBar from "expo-navigation-bar";
import { Tabs } from "expo-router";
import React, { useEffect } from "react";
import { Platform, StyleSheet } from "react-native";
export default function TabLayout() {
useEffect(() => {
@@ -50,7 +47,7 @@ export default function TabLayout() {
>
<Tabs.Screen redirect name="index" />
<Tabs.Screen
name="home"
name="(home)"
options={{
headerShown: false,
title: "Home",
@@ -63,7 +60,7 @@ export default function TabLayout() {
}}
/>
<Tabs.Screen
name="search"
name="(search)"
options={{
headerShown: false,
title: "Search",
@@ -73,7 +70,7 @@ export default function TabLayout() {
}}
/>
<Tabs.Screen
name="libraries"
name="(libraries)"
options={{
headerShown: false,
title: "Library",

View File

@@ -18,11 +18,10 @@ import { useEffect, useRef } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import "react-native-reanimated";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export const unstable_settings = {
initialRouteName: "/index",
initialRouteName: "/(auth)/(tabs)/(home)/",
};
export default function RootLayout() {
@@ -93,88 +92,6 @@ function Layout() {
title: "",
}}
/>
<Stack.Screen
name="(auth)/settings"
options={{
headerShown: true,
title: "Settings",
headerStyle: { backgroundColor: "black" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="(auth)/downloads"
options={{
headerShown: true,
title: "Downloads",
headerStyle: { backgroundColor: "black" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="(auth)/items/[id]"
options={{
title: "",
headerShown: false,
}}
/>
<Stack.Screen
name="(auth)/actors/[actorId]"
options={{
title: "",
headerShown: false,
}}
/>
<Stack.Screen
name="(auth)/collections/[collectionId]"
options={{
title: "",
headerShown: true,
headerStyle: { backgroundColor: "black" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="(auth)/artists/page"
options={{
title: "",
headerShown: true,
headerStyle: { backgroundColor: "black" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="(auth)/artists/[artistId]/page"
options={{
title: "",
headerShown: true,
headerStyle: { backgroundColor: "black" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="(auth)/albums/[albumId]"
options={{
title: "",
headerShown: true,
headerStyle: { backgroundColor: "black" },
headerShadowVisible: false,
}}
/>
<Stack.Screen
name="(auth)/songs/[songId]"
options={{
title: "",
headerShown: false,
}}
/>
<Stack.Screen
name="(auth)/series/[id]"
options={{
title: "",
headerShown: false,
}}
/>
<Stack.Screen
name="login"
options={{ headerShown: false, title: "Login" }}

View File

@@ -1,20 +1,24 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { BlurView } from "expo-blur";
import React, { useEffect } from "react";
import { View } from "react-native";
import {
import GoogleCast, {
CastButton,
useCastDevice,
useDevices,
useRemoteMediaClient,
} from "react-native-google-cast";
import GoogleCast from "react-native-google-cast";
type Props = {
width?: number;
height?: number;
background?: "blur" | "transparent";
};
export const Chromecast: React.FC<Props> = ({ width = 48, height = 48 }) => {
export const Chromecast: React.FC<Props> = ({
width = 48,
height = 48,
background = "transparent",
}) => {
const client = useRemoteMediaClient();
const castDevice = useCastDevice();
const devices = useDevices();
@@ -31,9 +35,19 @@ export const Chromecast: React.FC<Props> = ({ width = 48, height = 48 }) => {
})();
}, [client, devices, castDevice, sessionManager, discoveryManager]);
if (background === "transparent")
return (
<View className=" rounded h-10 aspect-square flex items-center justify-center">
<CastButton style={{ tintColor: "white", height, width }} />
</View>
);
return (
<View className="rounded h-10 aspect-square flex items-center justify-center">
<BlurView
intensity={100}
className="rounded-full overflow-hidden h-10 aspect-square flex items-center justify-center"
>
<CastButton style={{ tintColor: "white", height, width }} />
</View>
</BlurView>
);
};

View File

@@ -33,14 +33,14 @@ export const ParallaxScrollView: React.FC<Props> = ({
translateY: interpolate(
scrollOffset.value,
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
[-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75],
[-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75]
),
},
{
scale: interpolate(
scrollOffset.value,
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
[2, 1, 1],
[2, 1, 1]
),
},
],
@@ -58,30 +58,6 @@ export const ParallaxScrollView: React.FC<Props> = ({
ref={scrollRef}
scrollEventThrottle={16}
>
<TouchableOpacity
onPress={() => router.back()}
className="absolute left-4 z-50 bg-black rounded-full p-2 border border-neutral-900"
style={{
top: inset.top + 17,
}}
>
<Ionicons
className="drop-shadow-2xl"
name="arrow-back"
size={24}
color="#077DF2"
/>
</TouchableOpacity>
<View
className="absolute right-4 z-50 bg-black rounded-full p-0.5"
style={{
top: inset.top + 17,
}}
>
<Chromecast width={22} height={22} />
</View>
{logo && (
<View className="absolute top-[250px] h-[130px] left-0 w-full z-40 px-4 flex justify-center items-center">
{logo}

View File

@@ -0,0 +1,59 @@
import {
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
} from "react-native";
import { Text } from "@/components/common/Text";
import { useRouter } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
import { BlurView, BlurViewProps } from "expo-blur";
interface Props extends BlurViewProps {
background?: "blur" | "transparent";
touchableOpacityProps?: TouchableOpacityProps;
}
export const HeaderBackButton: React.FC<Props> = ({
background = "transparent",
touchableOpacityProps,
...props
}) => {
const router = useRouter();
if (background === "transparent")
return (
<BlurView
{...props}
intensity={100}
className="overflow-hidden rounded-full p-2"
>
<TouchableOpacity
onPress={() => router.back()}
{...touchableOpacityProps}
>
<Ionicons
className="drop-shadow-2xl"
name="arrow-back"
size={24}
color="#077DF2"
/>
</TouchableOpacity>
</BlurView>
);
return (
<TouchableOpacity
onPress={() => router.back()}
className=" bg-black rounded-full p-2 border border-neutral-900"
{...touchableOpacityProps}
>
<Ionicons
className="drop-shadow-2xl"
name="arrow-back"
size={24}
color="#077DF2"
/>
</TouchableOpacity>
);
};

View File

@@ -1,14 +1,8 @@
import {
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
} from "react-native";
import { Text } from "@/components/common/Text";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { PropsWithChildren } from "react";
import { useRouter } from "expo-router";
import * as Haptics from "expo-haptics";
import { useRouter, useSegments } from "expo-router";
import { PropsWithChildren } from "react";
import { Alert, TouchableOpacity, TouchableOpacityProps } from "react-native";
interface Props extends TouchableOpacityProps {
item: BaseItemDto;
@@ -20,46 +14,69 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
...props
}) => {
const router = useRouter();
return (
<TouchableOpacity
onPress={() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
const segments = useSegments();
if (item.Type === "Series") {
router.push(`/series/${item.Id}`);
return;
}
if (item.Type === "Episode") {
router.push(`/items/${item.Id}`);
return;
}
if (item.Type === "MusicAlbum") {
router.push(`/albums/${item.Id}`);
return;
}
if (item.Type === "Audio") {
router.push(`/albums/${item.AlbumId}`);
return;
}
if (item.Type === "MusicArtist") {
router.push(`/artists/${item.Id}/page`);
return;
}
if (item.Type === "Person") {
router.push(`/actors/${item.Id}`);
return;
}
const from = segments[2];
if (item.Type === "BoxSet") {
router.push(`/collections/${item.Id}`);
return;
}
if (from === "(home)" || from === "(search)" || from === "(libraries)")
return (
<TouchableOpacity
onPress={() => {
console.log("[0]", item.Type);
router.push(`/items/${item.Id}`);
}}
{...props}
>
{children}
</TouchableOpacity>
);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (item.Type === "Series") {
router.push(`/(auth)/(tabs)/${from}/series/${item.Id}`);
return;
}
if (item.Type === "MusicAlbum") {
router.push(`/(auth)/(tabs)/${from}/albums/${item.Id}`);
return;
}
if (item.Type === "Audio") {
router.push(`/(auth)/(tabs)/${from}/albums/${item.AlbumId}`);
return;
}
if (item.Type === "MusicArtist") {
router.push(`/(auth)/(tabs)/${from}/artists/${item.Id}`);
return;
}
if (item.Type === "Person") {
router.push(`/(auth)/(tabs)/${from}/actors/${item.Id}`);
return;
}
if (item.Type === "BoxSet") {
router.push(`/(auth)/(tabs)/${from}/collections/${item.Id}`);
return;
}
if (item.Type === "UserView") {
Alert.alert("Not implemented");
return;
}
if (item.Type === "CollectionFolder") {
router.push(`/(auth)/(tabs)/(libraries)/${item.Id}`);
return;
}
// Same as default
// if (item.Type === "Episode") {
// router.push(`/items/${item.Id}`);
// return;
// }
router.push(`/(auth)/(tabs)/${from}/items/${item.Id}`);
}}
{...props}
>
{children}
</TouchableOpacity>
);
};

View File

@@ -1,93 +0,0 @@
import * as DropdownMenu from "zeego/dropdown-menu";
import { Text } from "@/components/common/Text";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { Ionicons } from "@expo/vector-icons";
import { useQuery } from "@tanstack/react-query";
import { useAtom } from "jotai";
import { TouchableOpacity, View, ViewProps } from "react-native";
import {
sortByAtom,
sortOptions,
sortOrderAtom,
sortOrderOptions,
} from "@/utils/atoms/filters";
interface Props extends ViewProps {
title: string;
}
export const SortButton: React.FC<Props> = ({ title, ...props }) => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const [sortBy, setSortBy] = useAtom(sortByAtom);
const [sortOrder, setSortOrder] = useAtom(sortOrderAtom);
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<TouchableOpacity>
<View
className={`
px-3 py-2 rounded-full flex flex-row items-center space-x-2 bg-neutral-900
`}
{...props}
>
<Text>Sort by</Text>
<Ionicons
name="filter"
size={16}
color="white"
style={{ opacity: 0.5 }}
/>
</View>
</TouchableOpacity>
</DropdownMenu.Trigger>
<DropdownMenu.Content
loop={true}
side="bottom"
align="start"
alignOffset={0}
avoidCollisions={true}
collisionPadding={8}
sideOffset={8}
>
{sortOptions?.map((g) => (
<DropdownMenu.CheckboxItem
value={sortBy.key === g.key ? "on" : "off"}
onValueChange={(next, previous) => {
if (next === "on") {
setSortBy(g);
} else {
setSortBy(sortOptions[0]);
}
}}
key={g.key}
textValue={g.value}
>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
))}
<DropdownMenu.Separator />
<DropdownMenu.Group>
{sortOrderOptions.map((g) => (
<DropdownMenu.CheckboxItem
value={sortOrder.key === g.key ? "on" : "off"}
onValueChange={(next, previous) => {
if (next === "on") {
setSortOrder(g);
} else {
setSortOrder(sortOrderOptions[0]);
}
}}
key={g.key}
textValue={g.value}
>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
};

View File

@@ -90,7 +90,7 @@ export const NextEpisodeButton: React.FC<Props> = ({
return (
<Button
onPress={() => router.replace(`/items/${nextEpisode?.Id}`)}
onPress={() => router.push(`/items/${nextEpisode?.Id}`)}
className={`h-12 aspect-square`}
disabled={disabled}
{...props}

View File

@@ -0,0 +1,26 @@
import { Stack } from "expo-router";
import { Chromecast } from "../Chromecast";
import { HeaderBackButton } from "../common/HeaderBackButton";
const commonScreenOptions = {
title: "",
headerShown: true,
headerTransparent: true,
headerShadowVisible: false,
headerLeft: () => <HeaderBackButton />,
headerRight: () => <Chromecast background="blur" width={22} height={22} />,
};
const routes = [
"actors/[actorId]",
"albums/[albumId]",
"artists/index",
"artists/[artistId]",
"collections/[collectionId]",
"items/[id]",
"songs/[songId]",
"series/[id]",
];
export const nestedTabPageScreenOptions: { [key: string]: any } =
Object.fromEntries(routes.map((route) => [route, commonScreenOptions]));

View File

@@ -1,8 +1,3 @@
import {
currentlyPlayingItemAtom,
playingAtom,
showCurrentlyPlayingBarAtom,
} from "@/utils/atoms/playState";
import { Api, Jellyfin } from "@jellyfin/sdk";
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
import AsyncStorage from "@react-native-async-storage/async-storage";
@@ -233,7 +228,7 @@ function useProtectedRoute(user: UserDto | null, loading = false) {
if (!user?.Id && inAuthGroup) {
router.replace("/login");
} else if (user?.Id && !inAuthGroup) {
router.replace("/home");
router.replace("/(auth)/(tabs)/(home)/");
}
}, [user, segments, loading]);
}