mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 03:28:27 +01:00
Compare commits
4 Commits
develop
...
chore/migr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6055503a5 | ||
|
|
ebc86473ff | ||
|
|
27f6f6b056 | ||
|
|
8cf9a8d584 |
@@ -1,9 +1,3 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import { useNavigation } from "expo-router";
|
import { useNavigation } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
@@ -24,6 +18,11 @@ import { useDownload } from "@/providers/DownloadProvider";
|
|||||||
import { type DownloadedItem } from "@/providers/Downloads/types";
|
import { type DownloadedItem } from "@/providers/Downloads/types";
|
||||||
import { OfflineModeProvider } from "@/providers/OfflineModeProvider";
|
import { OfflineModeProvider } from "@/providers/OfflineModeProvider";
|
||||||
import { queueAtom } from "@/utils/atoms/queue";
|
import { queueAtom } from "@/utils/atoms/queue";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
|
|
||||||
export default function DownloadsPage() {
|
export default function DownloadsPage() {
|
||||||
@@ -32,7 +31,7 @@ export default function DownloadsPage() {
|
|||||||
const [_queue, _setQueue] = useAtom(queueAtom);
|
const [_queue, _setQueue] = useAtom(queueAtom);
|
||||||
const { downloadedItems, deleteFileByType, deleteAllFiles } = useDownload();
|
const { downloadedItems, deleteFileByType, deleteAllFiles } = useDownload();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
|
|
||||||
const [showMigration, setShowMigration] = useState(false);
|
const [showMigration, setShowMigration] = useState(false);
|
||||||
|
|
||||||
@@ -268,19 +267,13 @@ export default function DownloadsPage() {
|
|||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
|
enablePanDownToClose
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
backgroundStyle={{
|
backgroundStyle={{
|
||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
backdropComponent={(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<BottomSheetView>
|
<BottomSheetView>
|
||||||
<View className='p-4 space-y-4 mb-4'>
|
<View className='p-4 space-y-4 mb-4'>
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetTextInput,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type { BottomSheetModalMethods } from "@gorhom/bottom-sheet/lib/typescript/types";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { useLocalSearchParams, useNavigation } from "expo-router";
|
import { useLocalSearchParams, useNavigation } from "expo-router";
|
||||||
@@ -32,6 +24,12 @@ import { ItemActions } from "@/components/series/SeriesActions";
|
|||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||||
import { useJellyseerrCanRequest } from "@/utils/_jellyseerr/useJellyseerrCanRequest";
|
import { useJellyseerrCanRequest } from "@/utils/_jellyseerr/useJellyseerrCanRequest";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetTextInput,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { ANIME_KEYWORD_ID } from "@/utils/jellyseerr/server/api/themoviedb/constants";
|
import { ANIME_KEYWORD_ID } from "@/utils/jellyseerr/server/api/themoviedb/constants";
|
||||||
import {
|
import {
|
||||||
type IssueType,
|
type IssueType,
|
||||||
@@ -77,8 +75,8 @@ const MobilePage: React.FC = () => {
|
|||||||
const [issueMessage, setIssueMessage] = useState<string>();
|
const [issueMessage, setIssueMessage] = useState<string>();
|
||||||
const [requestBody, _setRequestBody] = useState<MediaRequestBody>();
|
const [requestBody, _setRequestBody] = useState<MediaRequestBody>();
|
||||||
const [issueTypeDropdownOpen, setIssueTypeDropdownOpen] = useState(false);
|
const [issueTypeDropdownOpen, setIssueTypeDropdownOpen] = useState(false);
|
||||||
const advancedReqModalRef = useRef<BottomSheetModalMethods>(null);
|
const advancedReqModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: details,
|
data: details,
|
||||||
@@ -144,17 +142,6 @@ const MobilePage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [jellyseerrApi, pendingRequest, refetch, t]);
|
}, [jellyseerrApi, pendingRequest, refetch, t]);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const submitIssue = useCallback(() => {
|
const submitIssue = useCallback(() => {
|
||||||
if (result.id && issueType && issueMessage && details) {
|
if (result.id && issueType && issueMessage && details) {
|
||||||
jellyseerrApi
|
jellyseerrApi
|
||||||
@@ -479,6 +466,7 @@ const MobilePage: React.FC = () => {
|
|||||||
// This is till it's fixed because the menu isn't selectable on TV
|
// This is till it's fixed because the menu isn't selectable on TV
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
@@ -486,8 +474,6 @@ const MobilePage: React.FC = () => {
|
|||||||
backgroundStyle={{
|
backgroundStyle={{
|
||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
stackBehavior='push'
|
|
||||||
onDismiss={handleIssueModalDismiss}
|
onDismiss={handleIssueModalDismiss}
|
||||||
>
|
>
|
||||||
<BottomSheetView>
|
<BottomSheetView>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ExpoAvRoutePickerView } from "@douglowder/expo-av-route-picker-view";
|
import { ExpoAvRoutePickerView } from "@douglowder/expo-av-route-picker-view";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
||||||
import type {
|
import type {
|
||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
@@ -222,133 +221,129 @@ export default function NowPlayingScreen() {
|
|||||||
|
|
||||||
if (!currentTrack) {
|
if (!currentTrack) {
|
||||||
return (
|
return (
|
||||||
<BottomSheetModalProvider>
|
|
||||||
<View
|
|
||||||
className='flex-1 bg-[#121212] items-center justify-center'
|
|
||||||
style={{
|
|
||||||
paddingTop: Platform.OS === "android" ? insets.top : 0,
|
|
||||||
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text className='text-neutral-500'>No track playing</Text>
|
|
||||||
</View>
|
|
||||||
</BottomSheetModalProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BottomSheetModalProvider>
|
|
||||||
<View
|
<View
|
||||||
className='flex-1 bg-[#121212]'
|
className='flex-1 bg-[#121212] items-center justify-center'
|
||||||
style={{
|
style={{
|
||||||
paddingTop: Platform.OS === "android" ? insets.top : 0,
|
paddingTop: Platform.OS === "android" ? insets.top : 0,
|
||||||
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
|
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
<Text className='text-neutral-500'>No track playing</Text>
|
||||||
<View className='flex-row items-center justify-between px-4 pt-3 pb-2'>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleClose}
|
|
||||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
||||||
className='p-2'
|
|
||||||
>
|
|
||||||
<Ionicons name='chevron-down' size={28} color='white' />
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<View className='flex-row'>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => setViewMode("player")}
|
|
||||||
className='px-3 py-1'
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
className={
|
|
||||||
viewMode === "player"
|
|
||||||
? "text-white font-semibold"
|
|
||||||
: "text-neutral-500"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Now Playing
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => setViewMode("queue")}
|
|
||||||
className='px-3 py-1'
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
className={
|
|
||||||
viewMode === "queue"
|
|
||||||
? "text-white font-semibold"
|
|
||||||
: "text-neutral-500"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Queue ({queue.length})
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
{/* Empty placeholder to balance header layout */}
|
|
||||||
<View className='p-2' style={{ width: 44 }} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{viewMode === "player" ? (
|
|
||||||
<PlayerView
|
|
||||||
api={api}
|
|
||||||
currentTrack={currentTrack}
|
|
||||||
imageUrl={imageUrl}
|
|
||||||
sliderProgress={sliderProgress}
|
|
||||||
sliderMin={sliderMin}
|
|
||||||
sliderMax={sliderMax}
|
|
||||||
progressText={progressText}
|
|
||||||
remainingText={remainingText}
|
|
||||||
isPlaying={isPlaying}
|
|
||||||
isLoading={isLoading}
|
|
||||||
repeatMode={repeatMode}
|
|
||||||
shuffleEnabled={shuffleEnabled}
|
|
||||||
canGoNext={canGoNext}
|
|
||||||
canGoPrevious={canGoPrevious}
|
|
||||||
onSliderComplete={handleSliderComplete}
|
|
||||||
onTogglePlayPause={togglePlayPause}
|
|
||||||
onNext={next}
|
|
||||||
onPrevious={previous}
|
|
||||||
onCycleRepeat={cycleRepeatMode}
|
|
||||||
onToggleShuffle={toggleShuffle}
|
|
||||||
getRepeatIcon={getRepeatIcon}
|
|
||||||
mediaSource={mediaSource}
|
|
||||||
isTranscoding={isTranscoding}
|
|
||||||
isFavorite={isFavorite}
|
|
||||||
onToggleFavorite={toggleFavorite}
|
|
||||||
onOptionsPress={handleOptionsPress}
|
|
||||||
isCastConnected={isCastConnected}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<QueueView
|
|
||||||
api={api}
|
|
||||||
queue={queue}
|
|
||||||
queueIndex={queueIndex}
|
|
||||||
onJumpToIndex={jumpToIndex}
|
|
||||||
onRemoveFromQueue={removeFromQueue}
|
|
||||||
onReorderQueue={reorderQueue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TrackOptionsSheet
|
|
||||||
open={trackOptionsOpen}
|
|
||||||
setOpen={setTrackOptionsOpen}
|
|
||||||
track={currentTrack}
|
|
||||||
onAddToPlaylist={handleAddToPlaylist}
|
|
||||||
/>
|
|
||||||
<PlaylistPickerSheet
|
|
||||||
open={playlistPickerOpen}
|
|
||||||
setOpen={setPlaylistPickerOpen}
|
|
||||||
trackToAdd={currentTrack}
|
|
||||||
onCreateNew={handleCreateNewPlaylist}
|
|
||||||
/>
|
|
||||||
<CreatePlaylistModal
|
|
||||||
open={createPlaylistOpen}
|
|
||||||
setOpen={setCreatePlaylistOpen}
|
|
||||||
initialTrackId={currentTrack?.Id}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
</BottomSheetModalProvider>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
className='flex-1 bg-[#121212]'
|
||||||
|
style={{
|
||||||
|
paddingTop: Platform.OS === "android" ? insets.top : 0,
|
||||||
|
paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<View className='flex-row items-center justify-between px-4 pt-3 pb-2'>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={handleClose}
|
||||||
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||||
|
className='p-2'
|
||||||
|
>
|
||||||
|
<Ionicons name='chevron-down' size={28} color='white' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View className='flex-row'>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setViewMode("player")}
|
||||||
|
className='px-3 py-1'
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
className={
|
||||||
|
viewMode === "player"
|
||||||
|
? "text-white font-semibold"
|
||||||
|
: "text-neutral-500"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Now Playing
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => setViewMode("queue")}
|
||||||
|
className='px-3 py-1'
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
className={
|
||||||
|
viewMode === "queue"
|
||||||
|
? "text-white font-semibold"
|
||||||
|
: "text-neutral-500"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Queue ({queue.length})
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
{/* Empty placeholder to balance header layout */}
|
||||||
|
<View className='p-2' style={{ width: 44 }} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{viewMode === "player" ? (
|
||||||
|
<PlayerView
|
||||||
|
api={api}
|
||||||
|
currentTrack={currentTrack}
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
sliderProgress={sliderProgress}
|
||||||
|
sliderMin={sliderMin}
|
||||||
|
sliderMax={sliderMax}
|
||||||
|
progressText={progressText}
|
||||||
|
remainingText={remainingText}
|
||||||
|
isPlaying={isPlaying}
|
||||||
|
isLoading={isLoading}
|
||||||
|
repeatMode={repeatMode}
|
||||||
|
shuffleEnabled={shuffleEnabled}
|
||||||
|
canGoNext={canGoNext}
|
||||||
|
canGoPrevious={canGoPrevious}
|
||||||
|
onSliderComplete={handleSliderComplete}
|
||||||
|
onTogglePlayPause={togglePlayPause}
|
||||||
|
onNext={next}
|
||||||
|
onPrevious={previous}
|
||||||
|
onCycleRepeat={cycleRepeatMode}
|
||||||
|
onToggleShuffle={toggleShuffle}
|
||||||
|
getRepeatIcon={getRepeatIcon}
|
||||||
|
mediaSource={mediaSource}
|
||||||
|
isTranscoding={isTranscoding}
|
||||||
|
isFavorite={isFavorite}
|
||||||
|
onToggleFavorite={toggleFavorite}
|
||||||
|
onOptionsPress={handleOptionsPress}
|
||||||
|
isCastConnected={isCastConnected}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<QueueView
|
||||||
|
api={api}
|
||||||
|
queue={queue}
|
||||||
|
queueIndex={queueIndex}
|
||||||
|
onJumpToIndex={jumpToIndex}
|
||||||
|
onRemoveFromQueue={removeFromQueue}
|
||||||
|
onReorderQueue={reorderQueue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TrackOptionsSheet
|
||||||
|
open={trackOptionsOpen}
|
||||||
|
setOpen={setTrackOptionsOpen}
|
||||||
|
track={currentTrack}
|
||||||
|
onAddToPlaylist={handleAddToPlaylist}
|
||||||
|
/>
|
||||||
|
<PlaylistPickerSheet
|
||||||
|
open={playlistPickerOpen}
|
||||||
|
setOpen={setPlaylistPickerOpen}
|
||||||
|
trackToAdd={currentTrack}
|
||||||
|
onCreateNew={handleCreateNewPlaylist}
|
||||||
|
/>
|
||||||
|
<CreatePlaylistModal
|
||||||
|
open={createPlaylistOpen}
|
||||||
|
setOpen={setCreatePlaylistOpen}
|
||||||
|
initialTrackId={currentTrack?.Id}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
237
app/_layout.tsx
237
app/_layout.tsx
@@ -1,6 +1,5 @@
|
|||||||
import "@/augmentations";
|
import "@/augmentations";
|
||||||
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
import { ActionSheetProvider } from "@expo/react-native-action-sheet";
|
||||||
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
|
|
||||||
import NetInfo from "@react-native-community/netinfo";
|
import NetInfo from "@react-native-community/netinfo";
|
||||||
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
||||||
import { onlineManager, QueryClient } from "@tanstack/react-query";
|
import { onlineManager, QueryClient } from "@tanstack/react-query";
|
||||||
@@ -412,127 +411,125 @@ function Layout() {
|
|||||||
<DownloadProvider>
|
<DownloadProvider>
|
||||||
<MusicPlayerProvider>
|
<MusicPlayerProvider>
|
||||||
<GlobalModalProvider>
|
<GlobalModalProvider>
|
||||||
<BottomSheetModalProvider>
|
<IntroSheetProvider>
|
||||||
<IntroSheetProvider>
|
<ThemeProvider value={DarkTheme}>
|
||||||
<ThemeProvider value={DarkTheme}>
|
<SystemBars style='light' hidden={false} />
|
||||||
<SystemBars style='light' hidden={false} />
|
<Stack initialRouteName='(auth)/(tabs)'>
|
||||||
<Stack initialRouteName='(auth)/(tabs)'>
|
<Stack.Screen
|
||||||
<Stack.Screen
|
name='(auth)/(tabs)'
|
||||||
name='(auth)/(tabs)'
|
options={{
|
||||||
options={{
|
headerShown: false,
|
||||||
headerShown: false,
|
title: "",
|
||||||
title: "",
|
header: () => null,
|
||||||
header: () => null,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/player'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
title: "",
|
|
||||||
header: () => null,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/now-playing'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "modal",
|
|
||||||
gestureEnabled: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='login'
|
|
||||||
options={{
|
|
||||||
headerShown: true,
|
|
||||||
title: "",
|
|
||||||
headerTransparent: Platform.OS === "ios",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen name='+not-found' />
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-option-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-subtitle-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-request-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-season-select-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-series-season-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='tv-account-action-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='tv-account-select-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name='(auth)/tv-user-switch-modal'
|
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
presentation: "transparentModal",
|
|
||||||
animation: "fade",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Toaster
|
|
||||||
duration={4000}
|
|
||||||
toastOptions={{
|
|
||||||
style: {
|
|
||||||
backgroundColor: "#262626",
|
|
||||||
borderColor: "#363639",
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
titleStyle: {
|
|
||||||
color: "white",
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
closeButton
|
|
||||||
/>
|
/>
|
||||||
{!Platform.isTV && <GlobalModal />}
|
<Stack.Screen
|
||||||
</ThemeProvider>
|
name='(auth)/player'
|
||||||
</IntroSheetProvider>
|
options={{
|
||||||
</BottomSheetModalProvider>
|
headerShown: false,
|
||||||
|
title: "",
|
||||||
|
header: () => null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/now-playing'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "modal",
|
||||||
|
gestureEnabled: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='login'
|
||||||
|
options={{
|
||||||
|
headerShown: true,
|
||||||
|
title: "",
|
||||||
|
headerTransparent: Platform.OS === "ios",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen name='+not-found' />
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-option-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-subtitle-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-request-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-season-select-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-series-season-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='tv-account-action-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='tv-account-select-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='(auth)/tv-user-switch-modal'
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
presentation: "transparentModal",
|
||||||
|
animation: "fade",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Toaster
|
||||||
|
duration={4000}
|
||||||
|
toastOptions={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: "#262626",
|
||||||
|
borderColor: "#363639",
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
titleStyle: {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
closeButton
|
||||||
|
/>
|
||||||
|
{!Platform.isTV && <GlobalModal />}
|
||||||
|
</ThemeProvider>
|
||||||
|
</IntroSheetProvider>
|
||||||
</GlobalModalProvider>
|
</GlobalModalProvider>
|
||||||
</MusicPlayerProvider>
|
</MusicPlayerProvider>
|
||||||
</DownloadProvider>
|
</DownloadProvider>
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -12,6 +6,11 @@ import { Alert, Platform, TouchableOpacity, View } from "react-native";
|
|||||||
import { Swipeable } from "react-native-gesture-handler";
|
import { Swipeable } from "react-native-gesture-handler";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import {
|
import {
|
||||||
deleteAccountCredential,
|
deleteAccountCredential,
|
||||||
type SavedServer,
|
type SavedServer,
|
||||||
@@ -39,7 +38,7 @@ export const AccountsSheet: React.FC<AccountsSheetProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
|
|
||||||
const isAndroid = Platform.OS === "android";
|
const isAndroid = Platform.OS === "android";
|
||||||
const snapPoints = useMemo(
|
const snapPoints = useMemo(
|
||||||
@@ -64,17 +63,6 @@ export const AccountsSheet: React.FC<AccountsSheetProps> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteAccount = async (account: SavedServerAccount) => {
|
const handleDeleteAccount = async (account: SavedServerAccount) => {
|
||||||
if (!server) return;
|
if (!server) return;
|
||||||
|
|
||||||
@@ -118,15 +106,16 @@ export const AccountsSheet: React.FC<AccountsSheetProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!server) return null;
|
if (!server) return null;
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
handleIndicatorStyle={{ backgroundColor: "white" }}
|
handleIndicatorStyle={{ backgroundColor: "white" }}
|
||||||
backgroundStyle={{ backgroundColor: "#171717" }}
|
backgroundStyle={{ backgroundColor: "#171717" }}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
>
|
>
|
||||||
<BottomSheetView
|
<BottomSheetView
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type {
|
import type {
|
||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
@@ -23,6 +17,11 @@ import { useDownload } from "@/providers/DownloadProvider";
|
|||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { queueAtom } from "@/utils/atoms/queue";
|
import { queueAtom } from "@/utils/atoms/queue";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
|
||||||
import { getDownloadUrl } from "@/utils/jellyfin/media/getDownloadUrl";
|
import { getDownloadUrl } from "@/utils/jellyfin/media/getDownloadUrl";
|
||||||
import { AudioTrackSelector } from "./AudioTrackSelector";
|
import { AudioTrackSelector } from "./AudioTrackSelector";
|
||||||
@@ -90,7 +89,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
|||||||
[user],
|
[user],
|
||||||
);
|
);
|
||||||
|
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
|
|
||||||
const handlePresentModalPress = useCallback(() => {
|
const handlePresentModalPress = useCallback(() => {
|
||||||
bottomSheetModalRef.current?.present();
|
bottomSheetModalRef.current?.present();
|
||||||
@@ -317,17 +316,6 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
|||||||
}
|
}
|
||||||
}, [closeModal, initiateDownload, itemsToDownload, userCanDownload]);
|
}, [closeModal, initiateDownload, itemsToDownload, userCanDownload]);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderButtonContent = () => {
|
const renderButtonContent = () => {
|
||||||
// For single item downloads, show progress if item is being processed
|
// For single item downloads, show progress if item is being processed
|
||||||
// For multi-item downloads (season/series), show progress only if 2+ items are in progress or queued
|
// For multi-item downloads (season/series), show progress only if 2+ items are in progress or queued
|
||||||
@@ -375,6 +363,8 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View {...props}>
|
<View {...props}>
|
||||||
<RoundButton size={size} onPress={onButtonPress}>
|
<RoundButton size={size} onPress={onButtonPress}>
|
||||||
@@ -390,10 +380,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
|
|||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
enablePanDownToClose
|
enablePanDownToClose
|
||||||
enableDismissOnClose
|
|
||||||
android_keyboardInputMode='adjustResize'
|
|
||||||
keyboardBehavior='interactive'
|
keyboardBehavior='interactive'
|
||||||
keyboardBlurBehavior='restore'
|
keyboardBlurBehavior='restore'
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
import { Platform } from "react-native";
|
||||||
import { useGlobalModal } from "@/providers/GlobalModalProvider";
|
import { useGlobalModal } from "@/providers/GlobalModalProvider";
|
||||||
|
import { BottomSheetModal } from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GlobalModal Component
|
* GlobalModal Component
|
||||||
@@ -12,8 +9,7 @@ import { useGlobalModal } from "@/providers/GlobalModalProvider";
|
|||||||
* This component renders a global bottom sheet modal that can be controlled
|
* This component renders a global bottom sheet modal that can be controlled
|
||||||
* from anywhere in the app using the useGlobalModal hook.
|
* from anywhere in the app using the useGlobalModal hook.
|
||||||
*
|
*
|
||||||
* Place this component at the root level of your app (in _layout.tsx)
|
* Place this component at the root level of your app (in _layout.tsx).
|
||||||
* after BottomSheetModalProvider.
|
|
||||||
*/
|
*/
|
||||||
export const GlobalModal = () => {
|
export const GlobalModal = () => {
|
||||||
const { hideModal, modalState, modalRef, isVisible } = useGlobalModal();
|
const { hideModal, modalState, modalRef, isVisible } = useGlobalModal();
|
||||||
@@ -33,17 +29,6 @@ export const GlobalModal = () => {
|
|||||||
[hideModal],
|
[hideModal],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
enableDynamicSizing: true,
|
enableDynamicSizing: true,
|
||||||
enablePanDownToClose: true,
|
enablePanDownToClose: true,
|
||||||
@@ -58,6 +43,8 @@ export const GlobalModal = () => {
|
|||||||
// Merge default options with provided options
|
// Merge default options with provided options
|
||||||
const modalOptions = { ...defaultOptions, ...modalState.options };
|
const modalOptions = { ...defaultOptions, ...modalState.options };
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={modalRef}
|
ref={modalRef}
|
||||||
@@ -65,12 +52,9 @@ export const GlobalModal = () => {
|
|||||||
? { snapPoints: modalOptions.snapPoints }
|
? { snapPoints: modalOptions.snapPoints }
|
||||||
: { enableDynamicSizing: modalOptions.enableDynamicSizing })}
|
: { enableDynamicSizing: modalOptions.enableDynamicSizing })}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={modalOptions.handleIndicatorStyle}
|
handleIndicatorStyle={modalOptions.handleIndicatorStyle}
|
||||||
backgroundStyle={modalOptions.backgroundStyle}
|
backgroundStyle={modalOptions.backgroundStyle}
|
||||||
enablePanDownToClose={modalOptions.enablePanDownToClose}
|
enablePanDownToClose={modalOptions.enablePanDownToClose}
|
||||||
enableDismissOnClose
|
|
||||||
stackBehavior='push'
|
|
||||||
style={{ zIndex: 1000 }}
|
style={{ zIndex: 1000 }}
|
||||||
>
|
>
|
||||||
{modalState.content}
|
{modalState.content}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { Feather, Ionicons } from "@expo/vector-icons";
|
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetScrollView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
|
import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -13,6 +7,11 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { storage } from "@/utils/mmkv";
|
import { storage } from "@/utils/mmkv";
|
||||||
|
|
||||||
export interface IntroSheetRef {
|
export interface IntroSheetRef {
|
||||||
@@ -21,7 +20,7 @@ export interface IntroSheetRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const IntroSheet = forwardRef<IntroSheetRef>((_, ref) => {
|
export const IntroSheet = forwardRef<IntroSheetRef>((_, ref) => {
|
||||||
const bottomSheetRef = useRef<BottomSheetModal>(null);
|
const bottomSheetRef = useRef<BottomSheetMethods>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -36,17 +35,6 @@ export const IntroSheet = forwardRef<IntroSheetRef>((_, ref) => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
bottomSheetRef.current?.dismiss();
|
bottomSheetRef.current?.dismiss();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -56,11 +44,13 @@ export const IntroSheet = forwardRef<IntroSheetRef>((_, ref) => {
|
|||||||
router.push("/settings");
|
router.push("/settings");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetRef}
|
ref={bottomSheetRef}
|
||||||
|
enablePanDownToClose
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
backgroundStyle={{ backgroundColor: "#171717" }}
|
backgroundStyle={{ backgroundColor: "#171717" }}
|
||||||
handleIndicatorStyle={{ backgroundColor: "#737373" }}
|
handleIndicatorStyle={{ backgroundColor: "#737373" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetScrollView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type {
|
import type {
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
MediaStream,
|
MediaStream,
|
||||||
@@ -12,8 +6,13 @@ import type {
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useMemo, useRef } from "react";
|
import { useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TouchableOpacity, View } from "react-native";
|
import { Platform, TouchableOpacity, View } from "react-native";
|
||||||
import { formatBitrate } from "@/utils/bitrate";
|
import { formatBitrate } from "@/utils/bitrate";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { Badge } from "./Badge";
|
import { Badge } from "./Badge";
|
||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
|
|
||||||
@@ -22,9 +21,20 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ItemTechnicalDetails: React.FC<Props> = ({ source }) => {
|
export const ItemTechnicalDetails: React.FC<Props> = ({ source }) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (Platform.isTV) {
|
||||||
|
return (
|
||||||
|
<View className='px-4 mt-2 mb-4'>
|
||||||
|
<Text className='text-lg font-bold mb-4'>{t("item_card.video")}</Text>
|
||||||
|
<View className='flex flex-row space-x-2'>
|
||||||
|
<VideoStreamInfo source={source} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='px-4 mt-2 mb-4'>
|
<View className='px-4 mt-2 mb-4'>
|
||||||
<Text className='text-lg font-bold mb-4'>{t("item_card.video")}</Text>
|
<Text className='text-lg font-bold mb-4'>{t("item_card.video")}</Text>
|
||||||
@@ -36,27 +46,27 @@ export const ItemTechnicalDetails: React.FC<Props> = ({ source }) => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
snapPoints={["80%"]}
|
enableDynamicSizing
|
||||||
|
enablePanDownToClose
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
backgroundStyle={{
|
backgroundStyle={{
|
||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
backdropComponent={(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<BottomSheetScrollView>
|
<BottomSheetScrollView>
|
||||||
<View className='flex flex-col space-y-2 p-4 mb-4'>
|
<View className='flex flex-col space-y-2 p-4'>
|
||||||
|
<View className='flex-row items-center justify-between mb-2'>
|
||||||
|
<Text className='text-lg font-bold'>{t("item_card.video")}</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => bottomSheetModalRef.current?.dismiss()}
|
||||||
|
hitSlop={12}
|
||||||
|
>
|
||||||
|
<Ionicons name='close' size={24} color='white' />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<Text className='text-lg font-bold mb-4'>
|
|
||||||
{t("item_card.video")}
|
|
||||||
</Text>
|
|
||||||
<View className='flex flex-row space-x-2'>
|
<View className='flex flex-row space-x-2'>
|
||||||
<VideoStreamInfo source={source} />
|
<VideoStreamInfo source={source} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -17,6 +11,11 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { verifyAccountPIN } from "@/utils/secureCredentials";
|
import { verifyAccountPIN } from "@/utils/secureCredentials";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
@@ -43,7 +42,7 @@ export const PINEntryModal: React.FC<PINEntryModalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const [pinCode, setPinCode] = useState("");
|
const [pinCode, setPinCode] = useState("");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isVerifying, setIsVerifying] = useState(false);
|
const [isVerifying, setIsVerifying] = useState(false);
|
||||||
@@ -78,17 +77,6 @@ export const PINEntryModal: React.FC<PINEntryModalProps> = ({
|
|||||||
[onClose],
|
[onClose],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const shake = () => {
|
const shake = () => {
|
||||||
Animated.sequence([
|
Animated.sequence([
|
||||||
Animated.timing(shakeAnimation, {
|
Animated.timing(shakeAnimation, {
|
||||||
@@ -159,18 +147,18 @@ export const PINEntryModal: React.FC<PINEntryModalProps> = ({
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
handleIndicatorStyle={{ backgroundColor: "white" }}
|
handleIndicatorStyle={{ backgroundColor: "white" }}
|
||||||
backgroundStyle={{ backgroundColor: "#171717" }}
|
backgroundStyle={{ backgroundColor: "#171717" }}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
||||||
keyboardBlurBehavior='restore'
|
keyboardBlurBehavior='restore'
|
||||||
android_keyboardInputMode='adjustResize'
|
|
||||||
topInset={isAndroid ? 0 : undefined}
|
|
||||||
>
|
>
|
||||||
<BottomSheetView
|
<BottomSheetView
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetTextInput,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ActivityIndicator, Platform, View } from "react-native";
|
import { ActivityIndicator, Platform, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetTextInput,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ export const PasswordEntryModal: React.FC<PasswordEntryModalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@@ -62,17 +61,6 @@ export const PasswordEntryModal: React.FC<PasswordEntryModalProps> = ({
|
|||||||
[onClose],
|
[onClose],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!password) {
|
if (!password) {
|
||||||
setError(t("password.enter_password"));
|
setError(t("password.enter_password"));
|
||||||
@@ -93,18 +81,18 @@ export const PasswordEntryModal: React.FC<PasswordEntryModalProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
handleIndicatorStyle={{ backgroundColor: "white" }}
|
handleIndicatorStyle={{ backgroundColor: "white" }}
|
||||||
backgroundStyle={{ backgroundColor: "#171717" }}
|
backgroundStyle={{ backgroundColor: "#171717" }}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
||||||
keyboardBlurBehavior='restore'
|
keyboardBlurBehavior='restore'
|
||||||
android_keyboardInputMode='adjustResize'
|
|
||||||
topInset={isAndroid ? 0 : undefined}
|
|
||||||
>
|
>
|
||||||
<BottomSheetView
|
<BottomSheetView
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
type LayoutChangeEvent,
|
type LayoutChangeEvent,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { useGlobalModal } from "@/providers/GlobalModalProvider";
|
import { useGlobalModal } from "@/providers/GlobalModalProvider";
|
||||||
|
import { BottomSheetScrollView } from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
// @expo/ui's SwiftUI native module (ExpoUI) does not exist in tvOS builds.
|
// @expo/ui's SwiftUI native module (ExpoUI) does not exist in tvOS builds.
|
||||||
// A static top-level import evaluates requireNativeModule('ExpoUI') at module
|
// A static top-level import evaluates requireNativeModule('ExpoUI') at module
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useActionSheet } from "@expo/react-native-action-sheet";
|
import { useActionSheet } from "@expo/react-native-action-sheet";
|
||||||
import { Feather, Ionicons } from "@expo/vector-icons";
|
import { Feather, Ionicons } from "@expo/vector-icons";
|
||||||
import { BottomSheetView } from "@gorhom/bottom-sheet";
|
|
||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
@@ -32,6 +31,7 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
|||||||
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
import { useOfflineMode } from "@/providers/OfflineModeProvider";
|
||||||
import { itemThemeColorAtom } from "@/utils/atoms/primaryColor";
|
import { itemThemeColorAtom } from "@/utils/atoms/primaryColor";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
import { BottomSheetView } from "@/utils/expoUiBottomSheet";
|
||||||
import { getParentBackdropImageUrl } from "@/utils/jellyfin/image/getParentBackdropImageUrl";
|
import { getParentBackdropImageUrl } from "@/utils/jellyfin/image/getParentBackdropImageUrl";
|
||||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||||
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
|
||||||
|
|||||||
@@ -39,19 +39,21 @@ export const RoundButton: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
|
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable onPress={handlePress} {...(viewProps as any)}>
|
||||||
onPress={handlePress}
|
<BlurView
|
||||||
className={`rounded-full ${buttonSize} flex items-center justify-center ${fillColorClass}`}
|
intensity={50}
|
||||||
{...(viewProps as any)}
|
tint='systemChromeMaterial'
|
||||||
>
|
className={`rounded-full overflow-hidden ${buttonSize} flex items-center justify-center ${fillColorClass}`}
|
||||||
{icon ? (
|
>
|
||||||
<Ionicons
|
{icon ? (
|
||||||
name={icon}
|
<Ionicons
|
||||||
size={size === "large" ? 22 : 18}
|
name={icon}
|
||||||
color={color === "white" ? "white" : "#9334E9"}
|
size={size === "large" ? 22 : 18}
|
||||||
/>
|
color={color === "white" ? "white" : "#9334E9"}
|
||||||
) : null}
|
/>
|
||||||
{children ? children : null}
|
) : null}
|
||||||
|
{children ? children : null}
|
||||||
|
</BlurView>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, TouchableOpacity, View } from "react-native";
|
import { Platform, TouchableOpacity, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import type { AccountSecurityType } from "@/utils/secureCredentials";
|
import type { AccountSecurityType } from "@/utils/secureCredentials";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
@@ -58,7 +57,7 @@ export const SaveAccountModal: React.FC<SaveAccountModalProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const [selectedType, setSelectedType] = useState<AccountSecurityType>("none");
|
const [selectedType, setSelectedType] = useState<AccountSecurityType>("none");
|
||||||
const [pinCode, setPinCode] = useState("");
|
const [pinCode, setPinCode] = useState("");
|
||||||
const [pinError, setPinError] = useState<string | null>(null);
|
const [pinError, setPinError] = useState<string | null>(null);
|
||||||
@@ -93,17 +92,6 @@ export const SaveAccountModal: React.FC<SaveAccountModalProps> = ({
|
|||||||
setPinError(null);
|
setPinError(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOptionSelect = (type: AccountSecurityType) => {
|
const handleOptionSelect = (type: AccountSecurityType) => {
|
||||||
setSelectedType(type);
|
setSelectedType(type);
|
||||||
setPinCode("");
|
setPinCode("");
|
||||||
@@ -135,18 +123,18 @@ export const SaveAccountModal: React.FC<SaveAccountModalProps> = ({
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
handleIndicatorStyle={{ backgroundColor: "white" }}
|
handleIndicatorStyle={{ backgroundColor: "white" }}
|
||||||
backgroundStyle={{ backgroundColor: "#171717" }}
|
backgroundStyle={{ backgroundColor: "#171717" }}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
||||||
keyboardBlurBehavior='restore'
|
keyboardBlurBehavior='restore'
|
||||||
android_keyboardInputMode='adjustResize'
|
|
||||||
topInset={isAndroid ? 0 : undefined}
|
|
||||||
>
|
>
|
||||||
<BottomSheetView
|
<BottomSheetView
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useActionSheet } from "@expo/react-native-action-sheet";
|
|||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useSegments } from "expo-router";
|
import { useSegments } from "expo-router";
|
||||||
import { type PropsWithChildren, useCallback } from "react";
|
import { type PropsWithChildren, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import {
|
import {
|
||||||
Platform,
|
Platform,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
@@ -150,7 +149,6 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
const { showActionSheetWithOptions } = useActionSheet();
|
const { showActionSheetWithOptions } = useActionSheet();
|
||||||
const markAsPlayedStatus = useMarkAsPlayed([item]);
|
const markAsPlayedStatus = useMarkAsPlayed([item]);
|
||||||
@@ -184,13 +182,11 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const options: string[] = [
|
const options: string[] = [
|
||||||
t("common.mark_as_played"),
|
"Mark as Played",
|
||||||
t("common.mark_as_not_played"),
|
"Mark as Not Played",
|
||||||
isFavorite
|
isFavorite ? "Unmark as Favorite" : "Mark as Favorite",
|
||||||
? t("music.track_options.remove_from_favorites")
|
...(isOffline ? ["Delete Download"] : []),
|
||||||
: t("music.track_options.add_to_favorites"),
|
"Cancel",
|
||||||
...(isOffline ? [t("home.downloads.delete_download")] : []),
|
|
||||||
t("common.cancel"),
|
|
||||||
];
|
];
|
||||||
const cancelButtonIndex = options.length - 1;
|
const cancelButtonIndex = options.length - 1;
|
||||||
const destructiveButtonIndex = isOffline
|
const destructiveButtonIndex = isOffline
|
||||||
@@ -223,7 +219,6 @@ export const TouchableItemRouter: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
isOffline,
|
isOffline,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
item.Id,
|
item.Id,
|
||||||
t,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetScrollView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
@@ -17,6 +12,11 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
import { Input } from "../common/Input";
|
import { Input } from "../common/Input";
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export const FilterSheet = <T,>({
|
|||||||
disableSearch = false,
|
disableSearch = false,
|
||||||
multiple = false,
|
multiple = false,
|
||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const snapPoints = useMemo(() => ["85%"], []);
|
const snapPoints = useMemo(() => ["85%"], []);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@@ -143,24 +143,15 @@ export const FilterSheet = <T,>({
|
|||||||
return data;
|
return data;
|
||||||
}, [search, filteredData, data]);
|
}, [search, filteredData, data]);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
if (Platform.isTV) return null;
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
index={0}
|
index={0}
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { BottomSheetTextInput } from "@gorhom/bottom-sheet";
|
|
||||||
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
import React, { useCallback, useImperativeHandle, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
type StyleProp,
|
type StyleProp,
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
import { BottomSheetTextInput } from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
interface PinInputProps
|
interface PinInputProps
|
||||||
extends Omit<TextInputProps, "value" | "onChangeText" | "style"> {
|
extends Omit<TextInputProps, "value" | "onChangeText" | "style"> {
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type { BottomSheetModalMethods } from "@gorhom/bottom-sheet/lib/typescript/types";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { forwardRef, useCallback, useMemo, useState } from "react";
|
import { forwardRef, useCallback, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View, type ViewProps } from "react-native";
|
import { Platform, View, type ViewProps } from "react-native";
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
||||||
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
import { useJellyseerr } from "@/hooks/useJellyseerr";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import type {
|
import type {
|
||||||
QualityProfile,
|
QualityProfile,
|
||||||
RootFolder,
|
RootFolder,
|
||||||
Tag,
|
Tag,
|
||||||
} from "@/utils/jellyseerr/server/api/servarr/base";
|
} from "@/utils/jellyseerr/server/api/servarr/base";
|
||||||
|
|
||||||
import type { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
import type { MediaType } from "@/utils/jellyseerr/server/constants/media";
|
||||||
import type { MediaRequestBody } from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces";
|
import type { MediaRequestBody } from "@/utils/jellyseerr/server/interfaces/api/requestInterfaces";
|
||||||
import { writeDebugLog } from "@/utils/log";
|
import { writeDebugLog } from "@/utils/log";
|
||||||
@@ -34,7 +33,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RequestModal = forwardRef<
|
const RequestModal = forwardRef<
|
||||||
BottomSheetModalMethods,
|
BottomSheetMethods,
|
||||||
Props & Omit<ViewProps, "id">
|
Props & Omit<ViewProps, "id">
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
@@ -283,11 +282,13 @@ const RequestModal = forwardRef<
|
|||||||
defaultTags,
|
defaultTags,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
enablePanDownToClose
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
enableDismissOnClose
|
|
||||||
onDismiss={handleDismiss}
|
onDismiss={handleDismiss}
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
@@ -295,14 +296,6 @@ const RequestModal = forwardRef<
|
|||||||
backgroundStyle={{
|
backgroundStyle={{
|
||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
backdropComponent={(sheetProps: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...sheetProps}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
stackBehavior='push'
|
|
||||||
>
|
>
|
||||||
<BottomSheetView>
|
<BottomSheetView>
|
||||||
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
|
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetTextInput,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -13,11 +6,17 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ActivityIndicator, Keyboard } from "react-native";
|
import { ActivityIndicator, Keyboard, Platform } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { useCreatePlaylist } from "@/hooks/usePlaylistMutations";
|
import { useCreatePlaylist } from "@/hooks/usePlaylistMutations";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetTextInput,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -32,7 +31,7 @@ export const CreatePlaylistModal: React.FC<Props> = ({
|
|||||||
onPlaylistCreated,
|
onPlaylistCreated,
|
||||||
initialTrackId,
|
initialTrackId,
|
||||||
}) => {
|
}) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const createPlaylist = useCreatePlaylist();
|
const createPlaylist = useCreatePlaylist();
|
||||||
@@ -59,17 +58,6 @@ export const CreatePlaylistModal: React.FC<Props> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCreate = useCallback(async () => {
|
const handleCreate = useCallback(async () => {
|
||||||
if (!name.trim()) return;
|
if (!name.trim()) return;
|
||||||
|
|
||||||
@@ -86,13 +74,15 @@ export const CreatePlaylistModal: React.FC<Props> = ({
|
|||||||
|
|
||||||
const isValid = name.trim().length > 0;
|
const isValid = name.trim().length > 0;
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
index={0}
|
index={0}
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Alert, StyleSheet, TouchableOpacity, View } from "react-native";
|
import {
|
||||||
|
Alert,
|
||||||
|
Platform,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import { useDeletePlaylist } from "@/hooks/usePlaylistMutations";
|
import { useDeletePlaylist } from "@/hooks/usePlaylistMutations";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -25,7 +30,7 @@ export const PlaylistOptionsSheet: React.FC<Props> = ({
|
|||||||
setOpen,
|
setOpen,
|
||||||
playlist,
|
playlist,
|
||||||
}) => {
|
}) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -47,17 +52,6 @@ export const PlaylistOptionsSheet: React.FC<Props> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeletePlaylist = useCallback(() => {
|
const handleDeletePlaylist = useCallback(() => {
|
||||||
if (!playlist?.Id) return;
|
if (!playlist?.Id) return;
|
||||||
|
|
||||||
@@ -89,14 +83,15 @@ export const PlaylistOptionsSheet: React.FC<Props> = ({
|
|||||||
}, [playlist, deletePlaylist, setOpen, router, t]);
|
}, [playlist, deletePlaylist, setOpen, router, t]);
|
||||||
|
|
||||||
if (!playlist) return null;
|
if (!playlist) return null;
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
index={0}
|
index={0}
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetScrollView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
@@ -20,6 +14,7 @@ import React, {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
@@ -29,6 +24,11 @@ import { Input } from "@/components/common/Input";
|
|||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { useAddToPlaylist } from "@/hooks/usePlaylistMutations";
|
import { useAddToPlaylist } from "@/hooks/usePlaylistMutations";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -43,7 +43,7 @@ export const PlaylistPickerSheet: React.FC<Props> = ({
|
|||||||
trackToAdd,
|
trackToAdd,
|
||||||
onCreateNew,
|
onCreateNew,
|
||||||
}) => {
|
}) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@@ -101,17 +101,6 @@ export const PlaylistPickerSheet: React.FC<Props> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectPlaylist = useCallback(
|
const handleSelectPlaylist = useCallback(
|
||||||
async (playlist: BaseItemDto) => {
|
async (playlist: BaseItemDto) => {
|
||||||
if (!trackToAdd?.Id || !playlist.Id) return;
|
if (!trackToAdd?.Id || !playlist.Id) return;
|
||||||
@@ -142,13 +131,15 @@ export const PlaylistPickerSheet: React.FC<Props> = ({
|
|||||||
[api],
|
[api],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
index={0}
|
index={0}
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { StyleSheet, TouchableOpacity, View } from "react-native";
|
import { Platform, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
export type PlaylistSortOption = "SortName" | "DateCreated";
|
export type PlaylistSortOption = "SortName" | "DateCreated";
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ export const PlaylistSortSheet: React.FC<Props> = ({
|
|||||||
sortOrder,
|
sortOrder,
|
||||||
onSortChange,
|
onSortChange,
|
||||||
}) => {
|
}) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -63,17 +62,6 @@ export const PlaylistSortSheet: React.FC<Props> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSortSelect = useCallback(
|
const handleSortSelect = useCallback(
|
||||||
(option: PlaylistSortOption) => {
|
(option: PlaylistSortOption) => {
|
||||||
// If selecting same option, toggle order; otherwise use sensible default
|
// If selecting same option, toggle order; otherwise use sensible default
|
||||||
@@ -93,13 +81,15 @@ export const PlaylistSortSheet: React.FC<Props> = ({
|
|||||||
[sortBy, sortOrder, onSortChange, setOpen],
|
[sortBy, sortOrder, onSortChange, setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
index={0}
|
index={0}
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { Image } from "expo-image";
|
import { Image } from "expo-image";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
@@ -18,6 +12,7 @@ import React, {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
@@ -36,6 +31,11 @@ import {
|
|||||||
} from "@/providers/AudioStorage";
|
} from "@/providers/AudioStorage";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { useMusicPlayer } from "@/providers/MusicPlayerProvider";
|
import { useMusicPlayer } from "@/providers/MusicPlayerProvider";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { getAudioStreamUrl } from "@/utils/jellyfin/audio/getAudioStreamUrl";
|
import { getAudioStreamUrl } from "@/utils/jellyfin/audio/getAudioStreamUrl";
|
||||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export const TrackOptionsSheet: React.FC<Props> = ({
|
|||||||
playlistId,
|
playlistId,
|
||||||
onRemoveFromPlaylist,
|
onRemoveFromPlaylist,
|
||||||
}) => {
|
}) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -128,17 +128,6 @@ export const TrackOptionsSheet: React.FC<Props> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePlayNext = useCallback(() => {
|
const handlePlayNext = useCallback(() => {
|
||||||
if (track) {
|
if (track) {
|
||||||
playNext(track);
|
playNext(track);
|
||||||
@@ -227,13 +216,14 @@ export const TrackOptionsSheet: React.FC<Props> = ({
|
|||||||
const hasAlbum = !!(track?.AlbumId || track?.ParentId);
|
const hasAlbum = !!(track?.AlbumId || track?.ParentId);
|
||||||
|
|
||||||
if (!track) return null;
|
if (!track) return null;
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
@@ -16,6 +11,11 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
interface LibraryOptions {
|
interface LibraryOptions {
|
||||||
display: "row" | "list";
|
display: "row" | "list";
|
||||||
@@ -132,7 +132,7 @@ export const LibraryOptionsSheet: React.FC<Props> = ({
|
|||||||
updateSettings,
|
updateSettings,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
@@ -161,25 +161,14 @@ export const LibraryOptionsSheet: React.FC<Props> = ({
|
|||||||
[setOpen],
|
[setOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (disabled) return null;
|
if (disabled) return null;
|
||||||
|
if (Platform.isTV) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
onChange={handleSheetChanges}
|
onChange={handleSheetChanges}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
@@ -187,7 +176,6 @@ export const LibraryOptionsSheet: React.FC<Props> = ({
|
|||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
enablePanDownToClose
|
enablePanDownToClose
|
||||||
enableDismissOnClose
|
|
||||||
>
|
>
|
||||||
<BottomSheetView>
|
<BottomSheetView>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
import { getQuickConnectApi } from "@jellyfin/sdk/lib/utils/api";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
@@ -12,6 +6,11 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Alert, Platform, View, type ViewProps } from "react-native";
|
import { Alert, Platform, View, type ViewProps } from "react-native";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
import { PinInput } from "../inputs/PinInput";
|
import { PinInput } from "../inputs/PinInput";
|
||||||
@@ -25,7 +24,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const [quickConnectCode, setQuickConnectCode] = useState<string>();
|
const [quickConnectCode, setQuickConnectCode] = useState<string>();
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const successHapticFeedback = useHaptic("success");
|
const successHapticFeedback = useHaptic("success");
|
||||||
const errorHapticFeedback = useHaptic("error");
|
const errorHapticFeedback = useHaptic("error");
|
||||||
const snapPoints = useMemo(
|
const snapPoints = useMemo(
|
||||||
@@ -36,17 +35,6 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const authorizeQuickConnect = useCallback(async () => {
|
const authorizeQuickConnect = useCallback(async () => {
|
||||||
if (quickConnectCode) {
|
if (quickConnectCode) {
|
||||||
try {
|
try {
|
||||||
@@ -97,6 +85,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
|
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
snapPoints={snapPoints}
|
snapPoints={snapPoints}
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
@@ -104,11 +93,8 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
backgroundStyle={{
|
backgroundStyle={{
|
||||||
backgroundColor: "#171717",
|
backgroundColor: "#171717",
|
||||||
}}
|
}}
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
keyboardBehavior={isAndroid ? "fillParent" : "interactive"}
|
||||||
keyboardBlurBehavior='restore'
|
keyboardBlurBehavior='restore'
|
||||||
android_keyboardInputMode='adjustResize'
|
|
||||||
topInset={isAndroid ? 0 : undefined}
|
|
||||||
>
|
>
|
||||||
<BottomSheetView>
|
<BottomSheetView>
|
||||||
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
|
<View className='flex flex-col space-y-4 px-4 pb-8 pt-2'>
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
BottomSheetBackdrop,
|
|
||||||
type BottomSheetBackdropProps,
|
|
||||||
BottomSheetModal,
|
|
||||||
BottomSheetView,
|
|
||||||
} from "@gorhom/bottom-sheet";
|
|
||||||
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
@@ -16,6 +10,7 @@ import React, {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
@@ -31,6 +26,11 @@ import {
|
|||||||
useItemInWatchlists,
|
useItemInWatchlists,
|
||||||
useMyWatchlistsQuery,
|
useMyWatchlistsQuery,
|
||||||
} from "@/hooks/useWatchlists";
|
} from "@/hooks/useWatchlists";
|
||||||
|
import {
|
||||||
|
type BottomSheetMethods,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetView,
|
||||||
|
} from "@/utils/expoUiBottomSheet";
|
||||||
import type { StreamystatsWatchlist } from "@/utils/streamystats/types";
|
import type { StreamystatsWatchlist } from "@/utils/streamystats/types";
|
||||||
|
|
||||||
export interface WatchlistSheetRef {
|
export interface WatchlistSheetRef {
|
||||||
@@ -263,7 +263,7 @@ const WatchlistSheetContent: React.FC<WatchlistSheetContentProps> = ({
|
|||||||
|
|
||||||
export const WatchlistSheet = forwardRef<WatchlistSheetRef, object>(
|
export const WatchlistSheet = forwardRef<WatchlistSheetRef, object>(
|
||||||
(_props, ref) => {
|
(_props, ref) => {
|
||||||
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
|
||||||
const [currentItem, setCurrentItem] = React.useState<BaseItemDto | null>(
|
const [currentItem, setCurrentItem] = React.useState<BaseItemDto | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -283,23 +283,13 @@ export const WatchlistSheet = forwardRef<WatchlistSheetRef, object>(
|
|||||||
bottomSheetModalRef.current?.dismiss();
|
bottomSheetModalRef.current?.dismiss();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderBackdrop = useCallback(
|
if (Platform.isTV) return null;
|
||||||
(props: BottomSheetBackdropProps) => (
|
|
||||||
<BottomSheetBackdrop
|
|
||||||
{...props}
|
|
||||||
disappearsOnIndex={-1}
|
|
||||||
appearsOnIndex={0}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheetModal
|
<BottomSheetModal
|
||||||
ref={bottomSheetModalRef}
|
ref={bottomSheetModalRef}
|
||||||
|
enablePanDownToClose
|
||||||
enableDynamicSizing
|
enableDynamicSizing
|
||||||
maxDynamicContentSize={600}
|
|
||||||
backdropComponent={renderBackdrop}
|
|
||||||
handleIndicatorStyle={{
|
handleIndicatorStyle={{
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
"@expo/react-native-action-sheet": "^4.1.1",
|
"@expo/react-native-action-sheet": "^4.1.1",
|
||||||
"@expo/ui": "~56.0.14",
|
"@expo/ui": "~56.0.14",
|
||||||
"@expo/vector-icons": "^15.0.3",
|
"@expo/vector-icons": "^15.0.3",
|
||||||
"@gorhom/bottom-sheet": "5.2.14",
|
|
||||||
"@jellyfin/sdk": "^0.13.0",
|
"@jellyfin/sdk": "^0.13.0",
|
||||||
"@react-native-community/netinfo": "^12.0.0",
|
"@react-native-community/netinfo": "^12.0.0",
|
||||||
"@react-navigation/material-top-tabs": "7.4.28",
|
"@react-navigation/material-top-tabs": "7.4.28",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { BottomSheetModal } from "@gorhom/bottom-sheet";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { BackHandler, Platform } from "react-native";
|
import { BackHandler, Platform } from "react-native";
|
||||||
|
import type { BottomSheetMethods } from "@/utils/expoUiBottomSheet";
|
||||||
|
|
||||||
interface ModalOptions {
|
interface ModalOptions {
|
||||||
enableDynamicSizing?: boolean;
|
enableDynamicSizing?: boolean;
|
||||||
@@ -30,7 +30,7 @@ interface GlobalModalContextType {
|
|||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
modalState: GlobalModalState;
|
modalState: GlobalModalState;
|
||||||
modalRef: React.RefObject<BottomSheetModal | null>;
|
modalRef: React.RefObject<BottomSheetMethods | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalModalContext = createContext<GlobalModalContextType | undefined>(
|
const GlobalModalContext = createContext<GlobalModalContextType | undefined>(
|
||||||
@@ -57,7 +57,7 @@ export const GlobalModalProvider: React.FC<GlobalModalProviderProps> = ({
|
|||||||
options: undefined,
|
options: undefined,
|
||||||
});
|
});
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
const modalRef = useRef<BottomSheetModal>(null);
|
const modalRef = useRef<BottomSheetMethods>(null);
|
||||||
|
|
||||||
const showModal = useCallback(
|
const showModal = useCallback(
|
||||||
(content: ReactNode, options?: ModalOptions) => {
|
(content: ReactNode, options?: ModalOptions) => {
|
||||||
|
|||||||
@@ -69,13 +69,6 @@ const initialApi = (() => {
|
|||||||
|
|
||||||
const initialUser = (() => {
|
const initialUser = (() => {
|
||||||
try {
|
try {
|
||||||
// Only return a stored user if we also have a token. Otherwise the
|
|
||||||
// user atom would be populated while the api atom is null (e.g. after
|
|
||||||
// a logout that left stale user JSON in storage), which causes
|
|
||||||
// useProtectedRoute to keep us inside the (auth) group instead of
|
|
||||||
// redirecting to /login.
|
|
||||||
const token = storage.getString("token");
|
|
||||||
if (!token) return null;
|
|
||||||
const userStr = storage.getString("user");
|
const userStr = storage.getString("user");
|
||||||
if (userStr) {
|
if (userStr) {
|
||||||
return JSON.parse(userStr) as UserDto;
|
return JSON.parse(userStr) as UserDto;
|
||||||
@@ -409,7 +402,6 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
storage.remove("token");
|
storage.remove("token");
|
||||||
storage.remove("user");
|
|
||||||
clearTVDiscoverySafely();
|
clearTVDiscoverySafely();
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setApi(null);
|
setApi(null);
|
||||||
|
|||||||
@@ -456,7 +456,6 @@
|
|||||||
"new_app_version_requires_re_download_description": "Die neue App-Version erfordert das erneute Herunterladen von Filmen und Serien. Bitte lösche alle heruntergeladenen Elemente und starte den Download erneut.",
|
"new_app_version_requires_re_download_description": "Die neue App-Version erfordert das erneute Herunterladen von Filmen und Serien. Bitte lösche alle heruntergeladenen Elemente und starte den Download erneut.",
|
||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"delete_download": "Download löschen",
|
|
||||||
"something_went_wrong": "Etwas ist schiefgelaufen",
|
"something_went_wrong": "Etwas ist schiefgelaufen",
|
||||||
"could_not_get_stream_url_from_jellyfin": "Konnte keine Stream-URL von Jellyfin erhalten",
|
"could_not_get_stream_url_from_jellyfin": "Konnte keine Stream-URL von Jellyfin erhalten",
|
||||||
"eta": "ETA {{eta}}",
|
"eta": "ETA {{eta}}",
|
||||||
@@ -499,8 +498,6 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"subtitle": "Untertitel",
|
"subtitle": "Untertitel",
|
||||||
"play": "Abspielen",
|
"play": "Abspielen",
|
||||||
"mark_as_played": "Als gesehen markieren",
|
|
||||||
"mark_as_not_played": "Als ungesehen markieren",
|
|
||||||
"none": "Keine",
|
"none": "Keine",
|
||||||
"track": "Spur",
|
"track": "Spur",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
|
|||||||
@@ -534,7 +534,6 @@
|
|||||||
"new_app_version_requires_re_download_description": "The new update requires content to be downloaded again. Please remove all downloaded content and try again.",
|
"new_app_version_requires_re_download_description": "The new update requires content to be downloaded again. Please remove all downloaded content and try again.",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"delete_download": "Delete Download",
|
|
||||||
"something_went_wrong": "Something Went Wrong",
|
"something_went_wrong": "Something Went Wrong",
|
||||||
"could_not_get_stream_url_from_jellyfin": "Could not get the stream URL from Jellyfin",
|
"could_not_get_stream_url_from_jellyfin": "Could not get the stream URL from Jellyfin",
|
||||||
"eta": "ETA {{eta}}",
|
"eta": "ETA {{eta}}",
|
||||||
@@ -578,8 +577,6 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"subtitle": "Subtitle",
|
"subtitle": "Subtitle",
|
||||||
"play": "Play",
|
"play": "Play",
|
||||||
"mark_as_played": "Mark as Played",
|
|
||||||
"mark_as_not_played": "Mark as not Played",
|
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"track": "Track",
|
"track": "Track",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|||||||
40
utils/expoUiBottomSheet.ts
Normal file
40
utils/expoUiBottomSheet.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TV-safe re-exports of `@expo/ui/community/bottom-sheet`.
|
||||||
|
*
|
||||||
|
* `@expo/ui` resolves its SwiftUI bridge at module load via
|
||||||
|
* `requireNativeModule('ExpoUI')`. That native module does not exist on tvOS,
|
||||||
|
* so a static top-level import from any route file crashes the whole route
|
||||||
|
* tree (expo-router eagerly loads every route).
|
||||||
|
*
|
||||||
|
* We `require()` the module lazily and only when *not* on tvOS. On TV the
|
||||||
|
* exported components are `undefined`, which is fine because every call site
|
||||||
|
* must early-return (`if (Platform.isTV) return null;`) before rendering a
|
||||||
|
* bottom sheet.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* import {
|
||||||
|
* BottomSheetModal,
|
||||||
|
* BottomSheetView,
|
||||||
|
* type BottomSheetMethods,
|
||||||
|
* } from "@/utils/expoUiBottomSheet";
|
||||||
|
*
|
||||||
|
* const ref = useRef<BottomSheetMethods>(null);
|
||||||
|
*
|
||||||
|
* if (Platform.isTV) return null;
|
||||||
|
* return <BottomSheetModal ref={ref}>...</BottomSheetModal>;
|
||||||
|
*/
|
||||||
|
type BottomSheetMod = typeof import("@expo/ui/community/bottom-sheet");
|
||||||
|
|
||||||
|
const mod: BottomSheetMod = Platform.isTV
|
||||||
|
? ({} as BottomSheetMod)
|
||||||
|
: (require("@expo/ui/community/bottom-sheet") as BottomSheetMod);
|
||||||
|
|
||||||
|
export const BottomSheetModal = mod.BottomSheetModal;
|
||||||
|
export const BottomSheetView = mod.BottomSheetView;
|
||||||
|
export const BottomSheetScrollView = mod.BottomSheetScrollView;
|
||||||
|
export const BottomSheetTextInput = mod.BottomSheetTextInput;
|
||||||
|
|
||||||
|
export type { BottomSheetMethods } from "@expo/ui/community/bottom-sheet";
|
||||||
Reference in New Issue
Block a user