chore(bottom-sheet): Migrate to expo-ui bottom sheet

This commit is contained in:
Alex Kim
2026-05-31 21:49:32 +10:00
parent ea5a999f21
commit 8cf9a8d584
29 changed files with 477 additions and 637 deletions

View File

@@ -1,4 +1,3 @@
import { BottomSheetModal } from "@gorhom/bottom-sheet";
import { useNavigation } from "expo-router";
import { useAtom } from "jotai";
import { useEffect, useMemo, useRef, useState } from "react";
@@ -18,6 +17,7 @@ import { useDownload } from "@/providers/DownloadProvider";
import { type DownloadedItem } from "@/providers/Downloads/types";
import { OfflineModeProvider } from "@/providers/OfflineModeProvider";
import { queueAtom } from "@/utils/atoms/queue";
import type { BottomSheetMethods } from "@/utils/expoUiBottomSheet";
import { writeToLog } from "@/utils/log";
export default function DownloadsPage() {
@@ -26,7 +26,7 @@ export default function DownloadsPage() {
const [_queue, _setQueue] = useAtom(queueAtom);
const { downloadedItems, deleteFileByType, deleteAllFiles } = useDownload();
const router = useRouter();
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
const [showMigration, setShowMigration] = useState(false);

View File

@@ -1,11 +1,4 @@
import { Ionicons } from "@expo/vector-icons";
import {
BottomSheetBackdrop,
type BottomSheetBackdropProps,
BottomSheetModal,
BottomSheetTextInput,
BottomSheetView,
} from "@gorhom/bottom-sheet";
import { useQuery } from "@tanstack/react-query";
import { Image } from "expo-image";
import { useLocalSearchParams, useNavigation } from "expo-router";
@@ -31,6 +24,12 @@ import { ItemActions } from "@/components/series/SeriesActions";
import useRouter from "@/hooks/useAppRouter";
import { useJellyseerr } from "@/hooks/useJellyseerr";
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 {
type IssueType,
@@ -76,8 +75,8 @@ const MobilePage: React.FC = () => {
const [issueMessage, setIssueMessage] = useState<string>();
const [requestBody, _setRequestBody] = useState<MediaRequestBody>();
const [issueTypeDropdownOpen, setIssueTypeDropdownOpen] = useState(false);
const advancedReqModalRef = useRef<BottomSheetModal>(null);
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
const advancedReqModalRef = useRef<BottomSheetMethods>(null);
const bottomSheetModalRef = useRef<BottomSheetMethods>(null);
const {
data: details,
@@ -143,17 +142,6 @@ const MobilePage: React.FC = () => {
}
}, [jellyseerrApi, pendingRequest, refetch, t]);
const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => (
<BottomSheetBackdrop
{...props}
disappearsOnIndex={-1}
appearsOnIndex={0}
/>
),
[],
);
const submitIssue = useCallback(() => {
if (result.id && issueType && issueMessage && details) {
jellyseerrApi
@@ -478,6 +466,7 @@ const MobilePage: React.FC = () => {
// This is till it's fixed because the menu isn't selectable on TV
<BottomSheetModal
ref={bottomSheetModalRef}
enablePanDownToClose
enableDynamicSizing
handleIndicatorStyle={{
backgroundColor: "white",
@@ -485,8 +474,6 @@ const MobilePage: React.FC = () => {
backgroundStyle={{
backgroundColor: "#171717",
}}
backdropComponent={renderBackdrop}
stackBehavior='push'
onDismiss={handleIssueModalDismiss}
>
<BottomSheetView>

View File

@@ -1,6 +1,5 @@
import { ExpoAvRoutePickerView } from "@douglowder/expo-av-route-picker-view";
import { Ionicons } from "@expo/vector-icons";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import type {
BaseItemDto,
MediaSourceInfo,
@@ -222,133 +221,129 @@ export default function NowPlayingScreen() {
if (!currentTrack) {
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
className='flex-1 bg-[#121212]'
className='flex-1 bg-[#121212] items-center justify-center'
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}
/>
<Text className='text-neutral-500'>No track playing</Text>
</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>
);
}