mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2025-12-29 18:01:18 -06:00
navigation stack refactor
This commit is contained in:
11
App.tsx
11
App.tsx
@@ -3,7 +3,7 @@ import React, { useState } from 'react'
|
||||
import 'react-native-url-polyfill/auto'
|
||||
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
||||
import Jellify from './src/components/jellify'
|
||||
import { TamaguiProvider, Theme } from 'tamagui'
|
||||
import { TamaguiProvider } from 'tamagui'
|
||||
import { Platform, useColorScheme } from 'react-native'
|
||||
import jellifyConfig from './tamagui.config'
|
||||
import { clientPersister } from './src/constants/storage'
|
||||
@@ -15,7 +15,6 @@ import TrackPlayer, {
|
||||
IOSCategoryOptions,
|
||||
} from 'react-native-track-player'
|
||||
import { CAPABILITIES } from './src/player/constants'
|
||||
import { createWorkletRuntime } from 'react-native-reanimated'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import { NavigationContainer } from '@react-navigation/native'
|
||||
import { JellifyDarkTheme, JellifyLightTheme } from './src/components/theme'
|
||||
@@ -23,9 +22,8 @@ import { requestStoragePermission } from './src/utils/permisson-helpers'
|
||||
import ErrorBoundary from './src/components/ErrorBoundary'
|
||||
import OTAUpdateScreen from './src/components/OtaUpdates'
|
||||
import { usePerformanceMonitor } from './src/hooks/use-performance-monitor'
|
||||
import { SettingsProvider, useSettingsContext } from './src/providers/Settings'
|
||||
|
||||
export const backgroundRuntime = createWorkletRuntime('background')
|
||||
import { SettingsProvider, useThemeSettingContext } from './src/providers/Settings'
|
||||
import { navigationRef } from './navigation'
|
||||
|
||||
export default function App(): React.JSX.Element {
|
||||
// Add performance monitoring to track app-level re-renders
|
||||
@@ -88,12 +86,13 @@ export default function App(): React.JSX.Element {
|
||||
}
|
||||
|
||||
function Container({ playerIsReady }: { playerIsReady: boolean }): React.JSX.Element {
|
||||
const { theme } = useSettingsContext()
|
||||
const theme = useThemeSettingContext()
|
||||
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
theme={
|
||||
theme === 'system'
|
||||
? isDarkMode
|
||||
|
||||
@@ -217,6 +217,7 @@ GEM
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-24
|
||||
x64-mingw-ucrt
|
||||
x86_64-linux
|
||||
|
||||
|
||||
3
index.js
3
index.js
@@ -4,6 +4,9 @@ import App from './App'
|
||||
import { name as appName } from './app.json'
|
||||
import { PlaybackService } from './src/player/service'
|
||||
import TrackPlayer from 'react-native-track-player'
|
||||
import { enableFreeze } from 'react-native-screens'
|
||||
|
||||
enableFreeze(true)
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App)
|
||||
AppRegistry.registerComponent('RNCarPlayScene', () => App)
|
||||
|
||||
@@ -61,30 +61,11 @@
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>${PRODUCT_NAME} uses the local network to connect to one's Jellyfin server for streaming music</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<string/>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>AntDesign.ttf</string>
|
||||
<string>Entypo.ttf</string>
|
||||
<string>EvilIcons.ttf</string>
|
||||
<string>Feather.ttf</string>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>FontAwesome5_Brands.ttf</string>
|
||||
<string>FontAwesome5_Regular.ttf</string>
|
||||
<string>FontAwesome5_Solid.ttf</string>
|
||||
<string>FontAwesome6_Brands.ttf</string>
|
||||
<string>FontAwesome6_Regular.ttf</string>
|
||||
<string>FontAwesome6_Solid.ttf</string>
|
||||
<string>Foundation.ttf</string>
|
||||
<string>Ionicons.ttf</string>
|
||||
<string>MaterialIcons.ttf</string>
|
||||
<string>MaterialCommunityIcons.ttf</string>
|
||||
<string>SimpleLineIcons.ttf</string>
|
||||
<string>Octicons.ttf</string>
|
||||
<string>Zocial.ttf</string>
|
||||
<string>Fontisto.ttf</string>
|
||||
<string>Figtree-Black.otf</string>
|
||||
<string>Figtree-BlackItalic.otf</string>
|
||||
<string>Figtree-Bold.otf</string>
|
||||
@@ -99,6 +80,7 @@
|
||||
<string>Figtree-Regular.otf</string>
|
||||
<string>Figtree-SemiBold.otf</string>
|
||||
<string>Figtree-SemiBoldItalic.otf</string>
|
||||
<string>MaterialDesignIcons.ttf</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
@@ -154,4 +136,4 @@
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
@@ -2000,6 +2000,7 @@ PODS:
|
||||
- SocketRocket
|
||||
- SwiftAudioEx (= 1.1.0)
|
||||
- Yoga
|
||||
- react-native-vector-icons-material-design-icons (12.3.0)
|
||||
- React-NativeModulesApple (0.81.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -2718,7 +2719,7 @@ PODS:
|
||||
- RNWorklets
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens (4.13.1):
|
||||
- RNScreens (4.14.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -2745,10 +2746,10 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNScreens/common (= 4.13.1)
|
||||
- RNScreens/common (= 4.14.0)
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens/common (4.13.1):
|
||||
- RNScreens/common (4.14.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -2807,34 +2808,6 @@ PODS:
|
||||
- Sentry/HybridSDK (= 8.53.1)
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNVectorIcons (10.2.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNWorklets (0.4.1):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -2987,6 +2960,7 @@ DEPENDENCIES:
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-track-player (from `../node_modules/react-native-track-player`)
|
||||
- "react-native-vector-icons-material-design-icons (from `../node_modules/@react-native-vector-icons/material-design-icons`)"
|
||||
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
|
||||
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
|
||||
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||
@@ -3028,7 +3002,6 @@ DEPENDENCIES:
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- "RNSentry (from `../node_modules/@sentry/react-native`)"
|
||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||
- RNWorklets (from `../node_modules/react-native-worklets`)
|
||||
- SDWebImage
|
||||
- SocketRocket (~> 0.7.1)
|
||||
@@ -3150,6 +3123,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-track-player:
|
||||
:path: "../node_modules/react-native-track-player"
|
||||
react-native-vector-icons-material-design-icons:
|
||||
:path: "../node_modules/@react-native-vector-icons/material-design-icons"
|
||||
React-NativeModulesApple:
|
||||
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
|
||||
React-oscompat:
|
||||
@@ -3232,8 +3207,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-screens"
|
||||
RNSentry:
|
||||
:path: "../node_modules/@sentry/react-native"
|
||||
RNVectorIcons:
|
||||
:path: "../node_modules/react-native-vector-icons"
|
||||
RNWorklets:
|
||||
:path: "../node_modules/react-native-worklets"
|
||||
Yoga:
|
||||
@@ -3293,6 +3266,7 @@ SPEC CHECKSUMS:
|
||||
react-native-pager-view: 0b0b445d3cb9f8e9972842edf6ddf892b46bdc55
|
||||
react-native-safe-area-context: a72764e0eb5d6b79b7450e5d0ae919eb1a4567b4
|
||||
react-native-track-player: 89d8e641c83a89bea5dee43c381be743282553e9
|
||||
react-native-vector-icons-material-design-icons: c502df5b988ce85d6c7d2b7ee909818315760b82
|
||||
React-NativeModulesApple: b3766e1f87b08064ebc459b9e1538da2447ca874
|
||||
React-oscompat: 34f3d3c06cadcbc470bc4509c717fb9b919eaa8b
|
||||
React-perflogger: a1edb025fd5d44f61bf09307e248f7608d7b2dcf
|
||||
@@ -3332,9 +3306,8 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: 3a73f098d74712952870e948b3d9cf7b6cae9961
|
||||
RNReactNativeHapticFeedback: be4f1b4bf0398c30b59b76ed92ecb0a2ff3a69c6
|
||||
RNReanimated: ee96d03fe3713993a30cc205522792b4cb08e4f9
|
||||
RNScreens: 40264381e0eca3f0c368c78f192f547ef40caa39
|
||||
RNScreens: 67ef075c463869c82f26744eee5f927edeea3190
|
||||
RNSentry: 95e1ed0ede28a4af58aaafedeac9fcfaba0e89ce
|
||||
RNVectorIcons: c13cc1db346e960ecd0aafcdd5d0bb458133b9c1
|
||||
RNWorklets: e8335dff9d27004709f58316985769040cd1e8f2
|
||||
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
||||
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
|
||||
|
||||
@@ -41,7 +41,7 @@ jest.mock('../../src/providers/Network', () => ({
|
||||
|
||||
// Mock the SettingsProvider to avoid dependency issues
|
||||
jest.mock('../../src/providers/Settings', () => ({
|
||||
useSettingsContext: () => ({
|
||||
useAutoDownloadContext: () => ({
|
||||
autoDownload: false,
|
||||
}),
|
||||
}))
|
||||
|
||||
11
navigation.ts
Normal file
11
navigation.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createNavigationContainerRef } from '@react-navigation/native'
|
||||
import { RootStackParamList } from './src/screens/types'
|
||||
|
||||
export const navigationRef = createNavigationContainerRef<RootStackParamList>()
|
||||
|
||||
export default function navigate(
|
||||
name: keyof RootStackParamList,
|
||||
params?: RootStackParamList[keyof RootStackParamList],
|
||||
) {
|
||||
if (navigationRef.isReady()) navigationRef.navigate(name, params)
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"tsc": "tsc",
|
||||
"codegen": "env DEBUG=metro:* react-native codegen",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "echo 'Please run `yarn pod:install:new-arch` to enable the new architecture'",
|
||||
@@ -39,6 +40,7 @@
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-native-picker/picker": "^2.11.1",
|
||||
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
||||
"@react-navigation/bottom-tabs": "^7.4.6",
|
||||
"@react-navigation/material-top-tabs": "^7.3.6",
|
||||
"@react-navigation/native": "^7.1.17",
|
||||
@@ -59,6 +61,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"openai": "^5.12.2",
|
||||
"react": "19.1.0",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.81.0",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-blob-util": "^0.22.2",
|
||||
@@ -79,15 +82,14 @@
|
||||
"react-native-pager-view": "^7.0.0",
|
||||
"react-native-reanimated": "4.0.2",
|
||||
"react-native-safe-area-context": "^5.6.0",
|
||||
"react-native-screens": "^4.13.1",
|
||||
"react-native-screens": "^4.14.0",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.15.0",
|
||||
"react-native-toast-message": "^2.3.3",
|
||||
"react-native-track-player": "5.0.0-alpha0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"react-native-worklets": "0.4.1",
|
||||
"react-native-worklets": "^0.4.1",
|
||||
"ruby": "^0.6.1",
|
||||
"scheduler": "^0.26.0",
|
||||
"tamagui": "^1.132.18",
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp
|
||||
index 94822c5..1499e11 100644
|
||||
--- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp
|
||||
+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.cpp
|
||||
@@ -14,9 +14,9 @@ Point RNSScreenShadowNode::getContentOriginOffset(
|
||||
return stateData.contentOffset;
|
||||
}
|
||||
|
||||
-std::optional<std::reference_wrapper<const ShadowNode::Shared>>
|
||||
+std::optional<std::reference_wrapper<const std::shared_ptr<const ShadowNode>>>
|
||||
findHeaderConfigChild(const YogaLayoutableShadowNode &screenShadowNode) {
|
||||
- for (const ShadowNode::Shared &child : screenShadowNode.getChildren()) {
|
||||
+ for (const std::shared_ptr<const ShadowNode> &child : screenShadowNode.getChildren()) {
|
||||
if (std::strcmp(child->getComponentName(), "RNSScreenStackHeaderConfig") ==
|
||||
0) {
|
||||
return {std::cref(child)};
|
||||
@@ -81,7 +81,7 @@ std::optional<float> findHeaderHeight(
|
||||
}
|
||||
#endif // ANDROID
|
||||
|
||||
-void RNSScreenShadowNode::appendChild(const ShadowNode::Shared &child) {
|
||||
+void RNSScreenShadowNode::appendChild(const std::shared_ptr<const ShadowNode> &child) {
|
||||
YogaLayoutableShadowNode::appendChild(child);
|
||||
#ifdef ANDROID
|
||||
const auto &stateData = getStateData();
|
||||
diff --git a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h
|
||||
index 6b4b72e..3379f89 100644
|
||||
--- a/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h
|
||||
+++ b/node_modules/react-native-screens/common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h
|
||||
@@ -28,7 +28,7 @@ class JSI_EXPORT RNSScreenShadowNode final : public ConcreteViewShadowNode<
|
||||
|
||||
Point getContentOriginOffset(bool includeTransform) const override;
|
||||
|
||||
- void appendChild(const ShadowNode::Shared &child) override;
|
||||
+ void appendChild(const std::shared_ptr<const ShadowNode> &child) override;
|
||||
|
||||
void layout(LayoutContext layoutContext) override;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JellifyUser } from '@/src/types/JellifyUser'
|
||||
import { JellifyUser } from '../../types/JellifyUser'
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import { BaseItemDto, MediaType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getLibraryApi, getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JellifyLibrary } from '@/src/types/JellifyLibrary'
|
||||
import { JellifyLibrary } from '../../types/JellifyLibrary'
|
||||
import { Api } from '@jellyfin/sdk/lib/api'
|
||||
import {
|
||||
BaseItemDto,
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function fetchItems(
|
||||
page: string | number = 0,
|
||||
sortBy: ItemSortBy[] = [ItemSortBy.SortName],
|
||||
sortOrder: SortOrder[] = [SortOrder.Ascending],
|
||||
isFavorite: boolean | undefined,
|
||||
isFavorite?: boolean | undefined,
|
||||
parentId?: string | undefined,
|
||||
ids?: string[] | undefined,
|
||||
): Promise<{ title: string | number; data: BaseItemDto[] }> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JellifyLibrary } from '@/src/types/JellifyLibrary'
|
||||
import { JellifyLibrary } from '../../types/JellifyLibrary'
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import {
|
||||
BaseItemDto,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AlbumProps, StackParamList } from '../types'
|
||||
import { YStack, XStack, Separator, getToken, Spacer } from 'tamagui'
|
||||
import { BaseStackParamList } from '../../screens/types'
|
||||
import { YStack, XStack, Separator, getToken, Spacer, Spinner } from 'tamagui'
|
||||
import { H5, Text } from '../Global/helpers/text'
|
||||
import { ActivityIndicator, FlatList, SectionList } from 'react-native'
|
||||
import { RunTimeTicks } from '../Global/helpers/time-codes'
|
||||
@@ -16,10 +16,12 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
import { useNetworkContext } from '../../providers/Network'
|
||||
import { useSettingsContext } from '../../providers/Settings'
|
||||
import { useDownloadQualityContext, useStreamingQualityContext } from '../../providers/Settings'
|
||||
import { useLoadQueueContext } from '../../providers/Player/queue'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import { useAlbumContext } from '../../providers/Album'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
/**
|
||||
* The screen for an Album's track list
|
||||
@@ -29,18 +31,13 @@ import { useAlbumContext } from '../../providers/Album'
|
||||
*
|
||||
* @returns A React component
|
||||
*/
|
||||
export function Album({ navigation }: AlbumProps): React.JSX.Element {
|
||||
export function Album(): React.JSX.Element {
|
||||
const { album, discs, isPending } = useAlbumContext()
|
||||
|
||||
const { api, sessionId } = useJellifyContext()
|
||||
const {
|
||||
useDownloadMultiple,
|
||||
pendingDownloads,
|
||||
downloadingDownloads,
|
||||
downloadedTracks,
|
||||
failedDownloads,
|
||||
} = useNetworkContext()
|
||||
const { downloadQuality, streamingQuality } = useSettingsContext()
|
||||
const { useDownloadMultiple, pendingDownloads } = useNetworkContext()
|
||||
const downloadQuality = useDownloadQualityContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
|
||||
const downloadAlbum = (item: BaseItemDto[]) => {
|
||||
@@ -54,7 +51,7 @@ export function Album({ navigation }: AlbumProps): React.JSX.Element {
|
||||
const playAlbum = (shuffled: boolean = false) => {
|
||||
if (!discs || discs.length === 0) return
|
||||
|
||||
const allTracks = discs.flatMap((disc) => disc.data)
|
||||
const allTracks = discs?.flatMap((disc) => disc.data) ?? []
|
||||
if (allTracks.length === 0) return
|
||||
|
||||
useLoadNewQueue({
|
||||
@@ -71,14 +68,14 @@ export function Album({ navigation }: AlbumProps): React.JSX.Element {
|
||||
return (
|
||||
<SectionList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
sections={discs ? discs : [{ title: '1', data: [] }]}
|
||||
sections={!isUndefined(discs) ? discs : []}
|
||||
keyExtractor={(item, index) => item.Id! + index}
|
||||
ItemSeparatorComponent={() => <Separator />}
|
||||
renderSectionHeader={({ section }) => {
|
||||
return (
|
||||
<XStack
|
||||
width='100%'
|
||||
justifyContent={discs && discs.length >= 2 ? 'space-between' : 'flex-end'}
|
||||
justifyContent={discs && discs?.length >= 2 ? 'space-between' : 'flex-end'}
|
||||
alignItems='center'
|
||||
backgroundColor={'$background'}
|
||||
paddingHorizontal={'$4.5'}
|
||||
@@ -103,21 +100,20 @@ export function Album({ navigation }: AlbumProps): React.JSX.Element {
|
||||
</XStack>
|
||||
)
|
||||
}}
|
||||
ListHeaderComponent={() => AlbumTrackListHeader(album, navigation, playAlbum)}
|
||||
ListHeaderComponent={() => AlbumTrackListHeader(album, playAlbum)}
|
||||
renderItem={({ item: track, index }) => (
|
||||
<Track
|
||||
track={track}
|
||||
tracklist={discs?.flatMap((disc) => disc.data)}
|
||||
index={discs?.flatMap((disc) => disc.data).indexOf(track) ?? index}
|
||||
navigation={navigation}
|
||||
queue={album}
|
||||
/>
|
||||
)}
|
||||
ListFooterComponent={() => AlbumTrackListFooter(album, navigation)}
|
||||
ListFooterComponent={() => AlbumTrackListFooter(album)}
|
||||
ListEmptyComponent={() => (
|
||||
<YStack>
|
||||
{isPending ? (
|
||||
<ActivityIndicator size='large' color={'$background'} />
|
||||
<Spinner size='large' color={'$background'} />
|
||||
) : (
|
||||
<Text>No tracks found</Text>
|
||||
)}
|
||||
@@ -136,11 +132,12 @@ export function Album({ navigation }: AlbumProps): React.JSX.Element {
|
||||
*/
|
||||
function AlbumTrackListHeader(
|
||||
album: BaseItemDto,
|
||||
navigation: NativeStackNavigationProp<StackParamList>,
|
||||
playAlbum: (shuffled?: boolean) => void,
|
||||
): React.JSX.Element {
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
|
||||
return (
|
||||
<YStack marginTop={'$4'} alignItems='center'>
|
||||
<XStack justifyContent='center'>
|
||||
@@ -183,7 +180,7 @@ function AlbumTrackListHeader(
|
||||
>
|
||||
<FavoriteButton item={album} />
|
||||
|
||||
<InstantMixButton item={album} navigation={navigation} />
|
||||
<InstantMixButton item={album} />
|
||||
|
||||
<Icon name='play' onPress={() => playAlbum(false)} small />
|
||||
|
||||
@@ -219,10 +216,9 @@ function AlbumTrackListHeader(
|
||||
)
|
||||
}
|
||||
|
||||
function AlbumTrackListFooter(
|
||||
album: BaseItemDto,
|
||||
navigation: NativeStackNavigationProp<StackParamList>,
|
||||
): React.JSX.Element {
|
||||
function AlbumTrackListFooter(album: BaseItemDto): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
|
||||
return (
|
||||
<YStack marginLeft={'$2'}>
|
||||
{album.ArtistItems && album.ArtistItems.length > 1 && (
|
||||
|
||||
@@ -1,27 +1,33 @@
|
||||
import { ActivityIndicator, RefreshControl } from 'react-native'
|
||||
import { AlbumsProps } from '../types'
|
||||
import { useDisplayContext } from '../../providers/Display/display-provider'
|
||||
import { getToken, Separator, XStack, YStack } from 'tamagui'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import React from 'react'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import { FetchNextPageOptions } from '@tanstack/react-query'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
interface AlbumsProps {
|
||||
albums: (string | number | BaseItemDto)[] | undefined
|
||||
fetchNextPage: (options?: FetchNextPageOptions | undefined) => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
showAlphabeticalSelector: boolean
|
||||
}
|
||||
|
||||
export default function Albums({
|
||||
albums,
|
||||
navigation,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isPending,
|
||||
isFetchingNextPage,
|
||||
showAlphabeticalSelector,
|
||||
}: AlbumsProps): React.JSX.Element {
|
||||
const { numberOfColumns } = useDisplayContext()
|
||||
useDisplayContext()
|
||||
|
||||
const MemoizedItem = React.memo(ItemRow)
|
||||
|
||||
const itemHeight = getToken('$6')
|
||||
|
||||
return (
|
||||
<XStack flex={1}>
|
||||
<FlashList
|
||||
@@ -30,7 +36,7 @@ export default function Albums({
|
||||
}}
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
data={albums ?? []}
|
||||
renderItem={({ index, item: album }) =>
|
||||
renderItem={({ item: album }) =>
|
||||
typeof album === 'string' ? (
|
||||
<XStack
|
||||
padding={'$2'}
|
||||
@@ -43,11 +49,7 @@ export default function Albums({
|
||||
<Text>{album.toUpperCase()}</Text>
|
||||
</XStack>
|
||||
) : typeof album === 'number' ? null : typeof album === 'object' ? (
|
||||
<MemoizedItem
|
||||
item={album}
|
||||
queueName={album.Name ?? 'Unknown Album'}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<MemoizedItem item={album} queueName={album.Name ?? 'Unknown Album'} />
|
||||
) : null
|
||||
}
|
||||
ListEmptyComponent={
|
||||
@@ -68,9 +70,7 @@ export default function Albums({
|
||||
stickyHeaderIndices={
|
||||
showAlphabeticalSelector
|
||||
? albums
|
||||
?.map((album, index, albums) =>
|
||||
typeof album === 'string' ? index : 0,
|
||||
)
|
||||
?.map((album, index) => (typeof album === 'string' ? index : 0))
|
||||
.filter((value, index, indices) => indices.indexOf(value) === index)
|
||||
: []
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { ArtistAlbumsProps, ArtistEpsProps, ArtistFeaturedOnProps } from '../types'
|
||||
import { ArtistAlbumsProps, ArtistEpsProps, ArtistFeaturedOnProps } from './types'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { useArtistContext } from '../../providers/Artist'
|
||||
import { convertRunTimeTicksToSeconds } from '../../utils/runtimeticks'
|
||||
|
||||
@@ -2,23 +2,18 @@ import React from 'react'
|
||||
import Albums from './albums'
|
||||
import SimilarArtists from './similar'
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'
|
||||
import { StackParamList } from '../types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import ArtistTabBar from './tab-bar'
|
||||
import { useArtistContext } from '../../providers/Artist'
|
||||
import ArtistTabList from './types'
|
||||
|
||||
const ArtistTabs = createMaterialTopTabNavigator<StackParamList>()
|
||||
const ArtistTabs = createMaterialTopTabNavigator<ArtistTabList>()
|
||||
|
||||
export default function ArtistNavigation({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
export default function ArtistNavigation(): React.JSX.Element {
|
||||
const { featuredOn, artist } = useArtistContext()
|
||||
|
||||
return (
|
||||
<ArtistTabs.Navigator
|
||||
tabBar={(props) => ArtistTabBar(props, navigation)}
|
||||
tabBar={(props) => ArtistTabBar(props)}
|
||||
screenOptions={{
|
||||
tabBarLabelStyle: {
|
||||
fontFamily: 'Figtree-Bold',
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../types'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import { BaseStackParamList } from '../../screens/types'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { useArtistContext } from '../../providers/Artist'
|
||||
import Animated, { useAnimatedScrollHandler } from 'react-native-reanimated'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
|
||||
export default function SimilarArtists({
|
||||
route,
|
||||
navigation,
|
||||
}: {
|
||||
route: RouteProp<StackParamList, 'SimilarArtists'>
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
export default function SimilarArtists(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
const { similarArtists, fetchingSimilarArtists, scroll } = useArtistContext()
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onScroll: (event) => {
|
||||
|
||||
@@ -10,18 +10,13 @@ import { ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useArtistContext } from '../../providers/Artist'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../types'
|
||||
import React from 'react'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { useLoadQueueContext } from '../../providers/Player/queue'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import { fetchAlbumDiscs } from '../../api/queries/item'
|
||||
|
||||
export default function ArtistTabBar(
|
||||
props: MaterialTopTabBarProps,
|
||||
stackNavigator: NativeStackNavigationProp<StackParamList>,
|
||||
) {
|
||||
export default function ArtistTabBar(props: MaterialTopTabBarProps) {
|
||||
const { api } = useJellifyContext()
|
||||
const { artist, scroll, albums } = useArtistContext()
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
@@ -102,7 +97,7 @@ export default function ArtistTabBar(
|
||||
<XStack alignItems='center' justifyContent='center' flex={1} gap={'$6'}>
|
||||
<FavoriteButton item={artist} />
|
||||
|
||||
<InstantMixButton item={artist} navigation={stackNavigator} />
|
||||
<InstantMixButton item={artist} />
|
||||
|
||||
<Icon name='play' onPress={() => playArtist(false)} />
|
||||
|
||||
|
||||
14
src/components/Artist/types.d.ts
vendored
Normal file
14
src/components/Artist/types.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MaterialTopTabBarProps } from '@react-navigation/material-top-tabs'
|
||||
|
||||
type ArtistTabList = {
|
||||
ArtistAlbums: undefined
|
||||
ArtistEps: undefined
|
||||
ArtistFeaturedOn: undefined
|
||||
SimilarArtists: undefined
|
||||
}
|
||||
|
||||
export default ArtistTabList
|
||||
|
||||
export type ArtistAlbumsProps = MaterialTopTabBarProps<ArtistTabList>
|
||||
export type ArtistEpsProps = MaterialTopTabBarProps<ArtistTabList>
|
||||
export type ArtistFeaturedOnProps = MaterialTopTabBarProps<ArtistTabList>
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { getToken, Separator, useTheme, XStack, YStack, Spinner } from 'tamagui'
|
||||
import { getToken, Separator, useTheme, XStack } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { ActivityIndicator, RefreshControl } from 'react-native'
|
||||
import { ArtistsProps } from '../types'
|
||||
import { RefreshControl } from 'react-native'
|
||||
import { ArtistsProps } from '../../screens/types'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { useLibrarySortAndFilterContext } from '../../providers/Library'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto'
|
||||
@@ -20,7 +20,6 @@ import { isString } from 'lodash'
|
||||
*/
|
||||
export default function Artists({
|
||||
artistsInfiniteQuery,
|
||||
navigation,
|
||||
showAlphabeticalSelector,
|
||||
artistPageParams,
|
||||
}: ArtistsProps): React.JSX.Element {
|
||||
@@ -145,7 +144,6 @@ export default function Artists({
|
||||
circular
|
||||
item={artist}
|
||||
queueName={artist.Name ?? 'Unknown Artist'}
|
||||
navigation={navigation}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from 'react'
|
||||
import Artists from './component'
|
||||
import { ArtistsProps } from '../types'
|
||||
import { ArtistsProps } from '../../screens/types'
|
||||
|
||||
export default function ArtistsScreen({
|
||||
navigation,
|
||||
artistsInfiniteQuery: artistInfiniteQuery,
|
||||
showAlphabeticalSelector,
|
||||
}: ArtistsProps): React.JSX.Element {
|
||||
return (
|
||||
<Artists
|
||||
navigation={navigation}
|
||||
artistsInfiniteQuery={artistInfiniteQuery}
|
||||
showAlphabeticalSelector={showAlphabeticalSelector}
|
||||
/>
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
import { ScrollView, View } from 'tamagui'
|
||||
import { MultipleArtistsProps } from '../../types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import ItemRow from '../../Global/components/item-row'
|
||||
import { useEffect } from 'react'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import { PlayerParamList } from '../../../screens/Player/types'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import navigate from '../../../../navigation'
|
||||
|
||||
interface MultipleArtistsProps {
|
||||
navigation: NativeStackNavigationProp<PlayerParamList, 'MultipleArtists'>
|
||||
route: RouteProp<PlayerParamList, 'MultipleArtists'>
|
||||
}
|
||||
export default function MultipleArtists({
|
||||
navigation,
|
||||
route,
|
||||
}: MultipleArtistsProps): React.JSX.Element {
|
||||
return (
|
||||
<ScrollView>
|
||||
{route.params.artists.map((artist) => {
|
||||
return (
|
||||
<ItemRow
|
||||
circular
|
||||
key={artist.Id}
|
||||
item={artist}
|
||||
queueName={''}
|
||||
navigation={navigation}
|
||||
onPress={() => {
|
||||
navigation.goBack()
|
||||
navigation.goBack()
|
||||
navigation.navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
<FlashList
|
||||
data={route.params.artists}
|
||||
renderItem={({ item: artist }) => (
|
||||
<ItemRow
|
||||
circular
|
||||
key={artist.Id}
|
||||
item={artist}
|
||||
queueName={''}
|
||||
onPress={() => {
|
||||
navigation.goBack() // Dismiss multiple artists modal
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
param: {
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
artist: artist,
|
||||
},
|
||||
artist,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,102 +1,235 @@
|
||||
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { Separator, View, XStack, YGroup, YStack, ZStack } from 'tamagui'
|
||||
import { StackParamList } from '../types'
|
||||
import { getToken, ListItem, View, YGroup, ZStack } from 'tamagui'
|
||||
import { BaseStackParamList, RootStackParamList } from '../../screens/types'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import FavoriteContextMenuRow from '../Global/components/favorite-context-menu-row'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import { getPrimaryBlurhashFromDto } from '../../utils/blurhash'
|
||||
import { useColorScheme, useWindowDimensions } from 'react-native'
|
||||
import { useThemeSettingContext } from '../../providers/Settings'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import Icon from '../Global/components/icon'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchItem } from '../../api/queries/item'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useAddToQueueContext } from '../../providers/Player/queue'
|
||||
import { AddToQueueMutation } from '../../providers/Player/interfaces'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import LibraryStackParamList from '../../screens/Library/types'
|
||||
import navigate from '../../../navigation'
|
||||
import DiscoverStackParamList from '@/src/screens/Discover/types'
|
||||
import { screen } from '@testing-library/react-native'
|
||||
import HomeStackParamList from '../../screens/Home/types'
|
||||
|
||||
interface ContextProps {
|
||||
item: BaseItemDto
|
||||
isNested?: boolean | undefined
|
||||
navigation?: NativeStackNavigationProp<
|
||||
HomeStackParamList | LibraryStackParamList | DiscoverStackParamList
|
||||
>
|
||||
}
|
||||
|
||||
export default function ItemContext({ item }: ContextProps): React.JSX.Element {
|
||||
const navigation = useNavigation<StackParamList>()
|
||||
export default function ItemContext({
|
||||
item,
|
||||
isNested,
|
||||
navigation,
|
||||
}: ContextProps): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
|
||||
const isArtist = item.Type === BaseItemKind.MusicArtist
|
||||
const isAlbum = item.Type === BaseItemKind.MusicAlbum
|
||||
const isTrack = item.Type === BaseItemKind.Audio
|
||||
const isPlaylist = item.Type === BaseItemKind.Playlist
|
||||
|
||||
const albumArtists = item.AlbumArtists ?? []
|
||||
|
||||
const { data: album, isSuccess: albumFetchSuccess } = useQuery({
|
||||
queryKey: [QueryKeys.Item, item.AlbumId],
|
||||
queryFn: () => fetchItem(api, item.AlbumId!),
|
||||
enabled: isTrack,
|
||||
})
|
||||
|
||||
const { data: artist, isSuccess: artistFetchSuccess } = useQuery({
|
||||
queryKey: [QueryKeys.ArtistById, albumArtists.length > 0 ? albumArtists[0].Id : item.Id],
|
||||
queryFn: () => fetchItem(api, albumArtists[0].Id!),
|
||||
enabled: (isTrack || isAlbum) && albumArtists.length > 0,
|
||||
})
|
||||
|
||||
const { data: tracks, isSuccess: tracksFetchSuccess } = useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, item.Id],
|
||||
queryFn: () =>
|
||||
getItemsApi(api!)
|
||||
.getItems({ parentId: item.Id! })
|
||||
.then(({ data }) => {
|
||||
if (data.Items) return data.Items
|
||||
else return []
|
||||
}),
|
||||
})
|
||||
|
||||
const renderAddToQueueRow = isTrack || isAlbum || isPlaylist
|
||||
|
||||
return (
|
||||
<ZStack flex={1}>
|
||||
<View flex={1}>{renderBackgroundBlur(item)}</View>
|
||||
<ZStack>
|
||||
<ItemContextBackground item={item} />
|
||||
|
||||
<View flex={1}>
|
||||
{renderContextHeader(item)}
|
||||
<YGroup unstyled flex={1} marginTop={'$8'}>
|
||||
<FavoriteContextMenuRow item={item} />
|
||||
|
||||
<Separator />
|
||||
{renderAddToQueueRow && tracks && (
|
||||
<AddToQueueMenuRow tracks={isTrack ? [item] : tracks} />
|
||||
)}
|
||||
|
||||
<YGroup>
|
||||
<FavoriteContextMenuRow item={item} />
|
||||
</YGroup>
|
||||
</View>
|
||||
{album && (
|
||||
<ViewAlbumMenuRow item={album} isNested={isNested} navigation={navigation} />
|
||||
)}
|
||||
|
||||
{artist && (
|
||||
<ViewArtistMenuRow item={artist} isNested={isNested} navigation={navigation} />
|
||||
)}
|
||||
</YGroup>
|
||||
</ZStack>
|
||||
)
|
||||
}
|
||||
|
||||
function renderBackgroundBlur(item: BaseItemDto): React.JSX.Element {
|
||||
function ItemContextBackground({ item }: { item: BaseItemDto }): React.JSX.Element {
|
||||
return (
|
||||
<ZStack flex={1}>
|
||||
<BackgroundBlur item={item} />
|
||||
|
||||
<BackgroundGradient />
|
||||
</ZStack>
|
||||
)
|
||||
}
|
||||
|
||||
function BackgroundBlur({ item }: { item: BaseItemDto }): React.JSX.Element {
|
||||
const blurhash = getPrimaryBlurhashFromDto(item)
|
||||
|
||||
return (
|
||||
<Blurhash
|
||||
blurhash={blurhash!}
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function renderContextHeader(item: BaseItemDto): React.JSX.Element {
|
||||
const isArtist = item.Type === BaseItemKind.MusicArtist
|
||||
const isAlbum = item.Type === BaseItemKind.MusicAlbum
|
||||
const isTrack = item.Type === BaseItemKind.Audio
|
||||
const isPlaylist = item.Type === BaseItemKind.Playlist
|
||||
function AddToQueueMenuRow({ tracks }: { tracks: BaseItemDto[] }): React.JSX.Element {
|
||||
const useAddToQueue = useAddToQueueContext()
|
||||
|
||||
const itemName = item.Name ?? getNamePlaceholder(item.Type)
|
||||
const mutation: AddToQueueMutation = {
|
||||
tracks,
|
||||
queuingType: QueuingType.DirectlyQueued,
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack alignItems='center' marginBottom={'$2'} margin={'$4'} gap={'$2'} minHeight={'$6'}>
|
||||
<ItemImage item={item} circular={isArtist} />
|
||||
<ListItem
|
||||
animation={'quick'}
|
||||
backgroundColor={'transparent'}
|
||||
flex={1}
|
||||
gap={'$2'}
|
||||
justifyContent='flex-start'
|
||||
onPress={() => {
|
||||
useAddToQueue.mutate(mutation)
|
||||
}}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
<Icon color='$primary' name='playlist-plus' />
|
||||
|
||||
<YStack gap={'$1'}>
|
||||
<Text bold>{itemName}</Text>
|
||||
|
||||
{!isArtist && !isPlaylist ? (
|
||||
isAlbum ? (
|
||||
<Text>
|
||||
{item.AlbumArtists?.map((artist) => artist.Name).join(', ') ||
|
||||
'Unknown Artist'}
|
||||
</Text>
|
||||
) : (
|
||||
<Text>
|
||||
{item.ArtistItems?.map((artist) => artist.Name).join(', ') ||
|
||||
'Unknown Artist'}
|
||||
</Text>
|
||||
)
|
||||
) : (
|
||||
<Text>{`${item.ChildCount?.toString() ?? '0'} ${item.ChildCount === 1 ? 'track' : 'tracks'}`}</Text>
|
||||
)}
|
||||
</YStack>
|
||||
</XStack>
|
||||
<Text bold>Add to Queue</Text>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
function getNamePlaceholder(type: BaseItemKind | undefined): string {
|
||||
switch (type) {
|
||||
case BaseItemKind.MusicArtist:
|
||||
return 'Artist'
|
||||
case BaseItemKind.MusicAlbum:
|
||||
return 'Album'
|
||||
case BaseItemKind.Audio:
|
||||
return 'Track'
|
||||
case BaseItemKind.Playlist:
|
||||
return 'Playlist'
|
||||
default:
|
||||
return 'Item'
|
||||
}
|
||||
function BackgroundGradient(): React.JSX.Element {
|
||||
const themeSetting = useThemeSettingContext()
|
||||
|
||||
const colorScheme = useColorScheme()
|
||||
|
||||
const isDarkMode =
|
||||
(themeSetting === 'system' && colorScheme === 'dark') || themeSetting === 'dark'
|
||||
|
||||
const gradientColors = isDarkMode
|
||||
? [getToken('$black'), getToken('$black75')]
|
||||
: [getToken('$lightTranslucent'), getToken('$lightTranslucent')]
|
||||
|
||||
return <LinearGradient style={{ flex: 1 }} colors={gradientColors} />
|
||||
}
|
||||
|
||||
function ViewAlbumMenuRow({ item: album, isNested, navigation }: ContextProps): React.JSX.Element {
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
animation='quick'
|
||||
backgroundColor={'transparent'}
|
||||
gap={'$2'}
|
||||
justifyContent='flex-start'
|
||||
onPress={() => {
|
||||
rootNavigation.goBack()
|
||||
|
||||
if (isNested) rootNavigation.goBack()
|
||||
|
||||
if (navigation) navigation?.navigate('Album', { album })
|
||||
else
|
||||
navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'Album',
|
||||
params: {
|
||||
album,
|
||||
},
|
||||
},
|
||||
})
|
||||
}}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
<Icon color='$primary' name='disc' />
|
||||
|
||||
<Text bold>Go to Album</Text>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
function ViewArtistMenuRow({
|
||||
item: artist,
|
||||
isNested,
|
||||
navigation,
|
||||
}: ContextProps): React.JSX.Element {
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
animation={'quick'}
|
||||
backgroundColor={'transparent'}
|
||||
gap={'$2'}
|
||||
justifyContent='flex-start'
|
||||
onPress={() => {
|
||||
rootNavigation.goBack()
|
||||
|
||||
if (isNested) rootNavigation.goBack()
|
||||
|
||||
if (navigation) navigation.navigate('Artist', { artist })
|
||||
else
|
||||
navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
artist,
|
||||
},
|
||||
},
|
||||
})
|
||||
}}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
<Icon color='$primary' name='microphone-variant' />
|
||||
|
||||
<Text bold>Go to Artist</Text>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../types'
|
||||
import TrackOptions from './helpers/TrackOptions'
|
||||
import { getToken, ScrollView, Spacer, useTheme, View, XStack, YStack } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import FavoriteButton from '../Global/components/favorite-button'
|
||||
import { useEffect } from 'react'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import TextTicker from 'react-native-text-ticker'
|
||||
import { TextTickerConfig } from '../Player/component.config'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import JellifyToastConfig from '../../constants/toast.config'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
export default function ItemDetail({
|
||||
item,
|
||||
navigation,
|
||||
isNested,
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
isNested?: boolean | undefined
|
||||
}): React.JSX.Element {
|
||||
let options: React.JSX.Element | undefined = undefined
|
||||
|
||||
const { api } = useJellifyContext()
|
||||
|
||||
useEffect(() => {
|
||||
trigger('impactMedium')
|
||||
}, [item])
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
switch (item.Type) {
|
||||
case 'Audio': {
|
||||
options = TrackOptions({ track: item, navigation, isNested })
|
||||
break
|
||||
}
|
||||
|
||||
case 'MusicAlbum': {
|
||||
break
|
||||
}
|
||||
|
||||
case 'MusicArtist': {
|
||||
break
|
||||
}
|
||||
|
||||
case 'Playlist': {
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView contentInsetAdjustmentBehavior='automatic' removeClippedSubviews>
|
||||
<YStack alignItems='center' flex={1} marginTop={'$4'}>
|
||||
<XStack
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
minHeight={getToken('$20') * 1.5}
|
||||
>
|
||||
<FastImage
|
||||
source={{
|
||||
uri:
|
||||
getImageApi(api!).getItemImageUrlById(
|
||||
item.Type === 'Audio' ? item.AlbumId! || item.Id! : item.Id!,
|
||||
ImageType.Primary,
|
||||
{
|
||||
tag: item.ImageTags?.Primary,
|
||||
},
|
||||
) || '',
|
||||
}}
|
||||
style={{
|
||||
width: getToken('$20') * 1.5,
|
||||
height: getToken('$20') * 1.5,
|
||||
borderRadius:
|
||||
item.Type === 'MusicArtist'
|
||||
? getToken('$20') * 1.5
|
||||
: getToken('$5'),
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
/>
|
||||
</XStack>
|
||||
|
||||
{/* Item Name, Artist, Album, and Favorite Button */}
|
||||
<XStack maxWidth={getToken('$20') * 1.5}>
|
||||
<YStack
|
||||
marginLeft={'$0.5'}
|
||||
alignItems='flex-start'
|
||||
alignContent='flex-start'
|
||||
justifyContent='flex-start'
|
||||
flex={3}
|
||||
>
|
||||
<TextTicker {...TextTickerConfig}>
|
||||
<Text bold fontSize={'$6'}>
|
||||
{item.Name ?? 'Untitled Track'}
|
||||
</Text>
|
||||
</TextTicker>
|
||||
|
||||
<TextTicker {...TextTickerConfig}>
|
||||
<Text
|
||||
fontSize={'$6'}
|
||||
onPress={() => {
|
||||
if (item.ArtistItems) {
|
||||
if (isNested) navigation.getParent()!.goBack()
|
||||
|
||||
navigation.goBack()
|
||||
navigation.navigate('Artist', {
|
||||
artist: item.ArtistItems[0],
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.Artists?.join(', ') ?? 'Unknown Artist'}
|
||||
</Text>
|
||||
</TextTicker>
|
||||
</YStack>
|
||||
|
||||
<YStack flex={1} alignItems='flex-end' justifyContent='center'>
|
||||
<FavoriteButton item={item} />
|
||||
</YStack>
|
||||
</XStack>
|
||||
|
||||
<Spacer />
|
||||
|
||||
{options ?? <View />}
|
||||
</YStack>
|
||||
<Toast config={JellifyToastConfig(theme)} />
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
import { StackParamList } from '../../types'
|
||||
import { BaseItemDto, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import {
|
||||
Circle,
|
||||
getToken,
|
||||
getTokens,
|
||||
ListItem,
|
||||
Separator,
|
||||
Spacer,
|
||||
Spinner,
|
||||
XStack,
|
||||
YGroup,
|
||||
YStack,
|
||||
} from 'tamagui'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import IconButton from '../../../components/Global/helpers/icon-button'
|
||||
import { Text } from '../../../components/Global/helpers/text'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { AddToPlaylistMutation } from '../../../components/Detail/types'
|
||||
import { addToPlaylist } from '../../../api/mutations/playlists'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import { queryClient } from '../../../constants/query-client'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchItem } from '../../../api/queries/item'
|
||||
import { fetchUserPlaylists } from '../../../api/queries/playlists'
|
||||
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { getImageApi, getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useNetworkContext } from '../../../providers/Network'
|
||||
import { useAddToQueueContext } from '../../../providers/Player/queue'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import Icon from '../../../components/Global/components/icon'
|
||||
import QueryConfig from '../../../api/queries/query.config'
|
||||
|
||||
interface TrackOptionsProps {
|
||||
track: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
|
||||
/**
|
||||
* Whether this is nested in the player modal
|
||||
*/
|
||||
isNested: boolean | undefined
|
||||
}
|
||||
|
||||
export default function TrackOptions({
|
||||
track,
|
||||
navigation,
|
||||
isNested,
|
||||
}: TrackOptionsProps): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
|
||||
const { data: album, isSuccess: albumFetchSuccess } = useQuery({
|
||||
queryKey: [QueryKeys.Item, track.AlbumId!],
|
||||
queryFn: () => fetchItem(api, track.AlbumId!),
|
||||
})
|
||||
|
||||
const { useDownload, useRemoveDownload, downloadedTracks } = useNetworkContext()
|
||||
|
||||
const isDownloaded = downloadedTracks?.find((t) => t.item.Id === track.Id)?.item?.Id
|
||||
|
||||
const {
|
||||
data: playlists,
|
||||
isPending: playlistsFetchPending,
|
||||
isSuccess: playlistsFetchSuccess,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: [QueryKeys.Playlists],
|
||||
queryFn: () => fetchUserPlaylists(api, user, library),
|
||||
select: (data) => data.pages.flatMap((page) => page),
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
|
||||
return lastPage.length === QueryConfig.limits.library * 2
|
||||
? lastPageParam + 1
|
||||
: undefined
|
||||
},
|
||||
})
|
||||
|
||||
// Fetch all playlist tracks to check if the current track is already in any playlists
|
||||
const playlistsWithTracks = useQuery({
|
||||
queryKey: [QueryKeys.PlaylistItemCheckCache, playlists?.map((p) => p.Id).join(',')],
|
||||
enabled: !!playlists && playlists.length > 0,
|
||||
queryFn: () => {
|
||||
console.debug('Fetching playlist contents')
|
||||
return Promise.all(
|
||||
playlists!.map(async (playlist) => {
|
||||
const response = await getItemsApi(api!).getItems({
|
||||
parentId: playlist.Id!,
|
||||
})
|
||||
return {
|
||||
playlistId: playlist.Id!,
|
||||
tracks: response.data.Items || [],
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
// Check if a track is in a playlist
|
||||
const isTrackInPlaylist = useMemo(() => {
|
||||
if (!playlistsWithTracks.data) return {}
|
||||
|
||||
const result: Record<string, boolean> = {}
|
||||
playlistsWithTracks.data.forEach((playlistData) => {
|
||||
result[playlistData.playlistId] = playlistData.tracks.some(
|
||||
(playlistTrack) => playlistTrack.Id === track.Id,
|
||||
)
|
||||
})
|
||||
return result
|
||||
}, [playlistsWithTracks.data, track.Id])
|
||||
|
||||
const useAddToQueue = useAddToQueueContext()
|
||||
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
const useAddToPlaylist = useMutation({
|
||||
mutationFn: ({ track, playlist }: AddToPlaylistMutation) => {
|
||||
trigger('impactLight')
|
||||
return addToPlaylist(api, user, track, playlist)
|
||||
},
|
||||
onSuccess: (data, { playlist }) => {
|
||||
Toast.show({
|
||||
text1: 'Added to playlist',
|
||||
type: 'success',
|
||||
})
|
||||
|
||||
trigger('notificationSuccess')
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.Playlists],
|
||||
})
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.ItemTracks, playlist.Id!],
|
||||
})
|
||||
|
||||
// Invalidate our playlist check cache
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QueryKeys.PlaylistItemCheckCache],
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
Toast.show({
|
||||
text1: 'Unable to add',
|
||||
type: 'error',
|
||||
})
|
||||
|
||||
trigger('notificationError')
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<YStack>
|
||||
<XStack marginHorizontal={'$1'} justifyContent='space-between'>
|
||||
{albumFetchSuccess && album ? (
|
||||
<IconButton
|
||||
name='music-box'
|
||||
title='Go to Album'
|
||||
onPress={() => {
|
||||
if (isNested) navigation.goBack()
|
||||
|
||||
navigation.goBack()
|
||||
|
||||
if (isNested)
|
||||
navigation.navigate('Tabs', {
|
||||
screen: 'Home',
|
||||
params: {
|
||||
screen: 'Album',
|
||||
params: {
|
||||
album,
|
||||
},
|
||||
},
|
||||
})
|
||||
else
|
||||
navigation.navigate('Album', {
|
||||
album,
|
||||
})
|
||||
}}
|
||||
size={getToken('$12') * 1.5}
|
||||
/>
|
||||
) : (
|
||||
<Spacer />
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
circular
|
||||
name='table-column-plus-before'
|
||||
title='Play Next'
|
||||
onPress={() => {
|
||||
useAddToQueue.mutate({
|
||||
track: track,
|
||||
queuingType: QueuingType.PlayingNext,
|
||||
})
|
||||
}}
|
||||
size={getToken('$12') * 1.5}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
circular
|
||||
name='table-column-plus-after'
|
||||
title='Add to Queue'
|
||||
onPress={() => {
|
||||
useAddToQueue.mutate({
|
||||
track: track,
|
||||
})
|
||||
}}
|
||||
size={getToken('$12') * 1.5}
|
||||
/>
|
||||
|
||||
{useDownload.isPending ? (
|
||||
<Circle size={width / 6} disabled>
|
||||
<Spinner marginHorizontal={10} size='small' color={'$primary'} />
|
||||
</Circle>
|
||||
) : (
|
||||
<IconButton
|
||||
disabled={!!isDownloaded}
|
||||
name={isDownloaded ? 'delete' : 'download'}
|
||||
title={isDownloaded ? 'Clear Download' : 'Download'}
|
||||
onPress={() => {
|
||||
if (isDownloaded) useRemoveDownload.mutate(track)
|
||||
else useDownload.mutate(track)
|
||||
}}
|
||||
size={getToken('$12') * 1.5}
|
||||
/>
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
<Spacer />
|
||||
|
||||
{(playlistsFetchPending || playlistsWithTracks.isFetching) && <Spinner />}
|
||||
|
||||
{!playlistsFetchPending && playlistsFetchSuccess && (
|
||||
<>
|
||||
<Text bold fontSize={'$6'}>
|
||||
Add to Playlist
|
||||
</Text>
|
||||
|
||||
<YGroup separator={<Separator />}>
|
||||
{playlists?.map((playlist) => {
|
||||
const isInPlaylist = isTrackInPlaylist[playlist.Id!]
|
||||
|
||||
return (
|
||||
<YGroup.Item key={playlist.Id!}>
|
||||
<ListItem
|
||||
hoverTheme
|
||||
disabled={isInPlaylist}
|
||||
opacity={isInPlaylist ? 0.7 : 1}
|
||||
onPress={() => {
|
||||
if (!isInPlaylist) {
|
||||
useAddToPlaylist.mutate({
|
||||
track,
|
||||
playlist,
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<XStack alignItems='center'>
|
||||
<YStack flex={1}>
|
||||
<FastImage
|
||||
source={{
|
||||
uri:
|
||||
getImageApi(api!).getItemImageUrlById(
|
||||
playlist.Id!,
|
||||
ImageType.Primary,
|
||||
{
|
||||
tag: playlist.ImageTags
|
||||
?.Primary,
|
||||
},
|
||||
) || '',
|
||||
}}
|
||||
style={{
|
||||
borderRadius: getToken('$1.5'),
|
||||
width: getToken('$12'),
|
||||
height: getToken('$12'),
|
||||
marginRight: getToken('$2'),
|
||||
}}
|
||||
/>
|
||||
</YStack>
|
||||
|
||||
<YStack alignItems='flex-start' flex={5}>
|
||||
<Text bold fontSize={'$6'}>
|
||||
{playlist.Name ?? 'Untitled Playlist'}
|
||||
</Text>
|
||||
|
||||
<Text color={getTokens().color.amethyst.val}>{`${
|
||||
playlist.ChildCount ?? 0
|
||||
} tracks`}</Text>
|
||||
</YStack>
|
||||
|
||||
{isInPlaylist ? (
|
||||
<Icon
|
||||
flex={1}
|
||||
name='check-circle-outline'
|
||||
color={'$success'}
|
||||
/>
|
||||
) : (
|
||||
<Spacer flex={1} />
|
||||
)}
|
||||
</XStack>
|
||||
</ListItem>
|
||||
</YGroup.Item>
|
||||
)
|
||||
})}
|
||||
</YGroup>
|
||||
</>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
export interface AddToPlaylistMutation {
|
||||
track: BaseItemDto
|
||||
playlist: BaseItemDto
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { getToken, ScrollView, Separator, View } from 'tamagui'
|
||||
import RecentlyAdded from './helpers/just-added'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../types'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { useDiscoverContext } from '../../providers/Discover'
|
||||
import { RefreshControl } from 'react-native'
|
||||
import PublicPlaylists from './helpers/public-playlists'
|
||||
@@ -11,7 +11,7 @@ import SuggestedArtists from './helpers/suggested-artists'
|
||||
export default function Index({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { refreshing, refresh, recentlyAdded, publicPlaylists, suggestedArtistsInfiniteQuery } =
|
||||
useDiscoverContext()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
@@ -10,7 +10,7 @@ import Icon from '../../Global/components/icon'
|
||||
export default function RecentlyAdded({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const {
|
||||
recentlyAdded,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { View, XStack } from 'tamagui'
|
||||
import { useDiscoverContext } from '../../../providers/Discover'
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
@@ -12,7 +12,7 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
export default function PublicPlaylists({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}) {
|
||||
const {
|
||||
publicPlaylists,
|
||||
|
||||
@@ -5,12 +5,12 @@ import { ItemCard } from '../../Global/components/item-card'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useDiscoverContext } from '../../../providers/Discover'
|
||||
import { H4 } from '../../Global/helpers/text'
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
|
||||
export default function SuggestedArtists({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { suggestedArtistsInfiniteQuery } = useDiscoverContext()
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,7 @@ import Animated, {
|
||||
import { Text } from '../helpers/text'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import { useSettingsContext } from '../../../providers/Settings'
|
||||
import { useReducedHapticsContext } from '../../../providers/Settings'
|
||||
|
||||
const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||
export function AZScroller({ onLetterSelect }: { onLetterSelect: (letter: string) => void }) {
|
||||
const { width, height } = useSafeAreaFrame()
|
||||
const theme = useTheme()
|
||||
const { reducedHaptics } = useSettingsContext()
|
||||
const reducedHaptics = useReducedHapticsContext()
|
||||
|
||||
const overlayOpacity = useSharedValue(0)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchUserData } from '../../../api/queries/favorites'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { ListItem, XStack, YGroup } from 'tamagui'
|
||||
import { getToken, ListItem } from 'tamagui'
|
||||
import Icon from './icon'
|
||||
import { useJellifyUserDataContext } from '../../../providers/UserData'
|
||||
import { useEffect, useState } from 'react'
|
||||
@@ -28,11 +28,20 @@ export default function FavoriteContextMenuRow({ item }: { item: BaseItemDto }):
|
||||
setIsFavorite(userData?.IsFavorite ?? false)
|
||||
}, [userData])
|
||||
|
||||
return (
|
||||
<YGroup.Item>
|
||||
return isFavorite ? (
|
||||
<Animated.View
|
||||
entering={FadeIn}
|
||||
exiting={FadeOut}
|
||||
key={`${item.Id}-remove-favorite-row`}
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<ListItem
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
animation={'quick'}
|
||||
backgroundColor={'transparent'}
|
||||
gap={'$2'}
|
||||
justifyContent='flex-start'
|
||||
onPress={() => {
|
||||
toggleFavorite(isFavorite, {
|
||||
item,
|
||||
@@ -40,37 +49,35 @@ export default function FavoriteContextMenuRow({ item }: { item: BaseItemDto }):
|
||||
onToggle: () => refetch(),
|
||||
})
|
||||
}}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
{isFavorite ? (
|
||||
<Animated.View
|
||||
entering={FadeIn}
|
||||
exiting={FadeOut}
|
||||
key={`${item.Id}-remove-favorite-row`}
|
||||
>
|
||||
<XStack alignContent='center' justifyContent='flex-start' gap={'$2'}>
|
||||
<Icon name={'heart'} small color={'$primary'} />
|
||||
<Icon name={'heart'} small color={'$primary'} />
|
||||
|
||||
<Text marginVertical={'$1.5'} bold>
|
||||
Remove from favorites
|
||||
</Text>
|
||||
</XStack>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Animated.View
|
||||
entering={FadeIn}
|
||||
exiting={FadeOut}
|
||||
key={`${item.Id}-favorite-row`}
|
||||
>
|
||||
<XStack alignContent='center' justifyContent='flex-start' gap={'$2'}>
|
||||
<Icon name={'heart-outline'} small color={'$primary'} />
|
||||
|
||||
<Text marginVertical={'$1.5'} bold>
|
||||
Add to favorites
|
||||
</Text>
|
||||
</XStack>
|
||||
</Animated.View>
|
||||
)}
|
||||
<Text bold>Remove from favorites</Text>
|
||||
</ListItem>
|
||||
</YGroup.Item>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Animated.View entering={FadeIn} exiting={FadeOut} key={`${item.Id}-favorite-row`}>
|
||||
<ListItem
|
||||
animation={'quick'}
|
||||
backgroundColor={'transparent'}
|
||||
justifyContent='flex-start'
|
||||
gap={'$2'}
|
||||
onPress={() => {
|
||||
toggleFavorite(isFavorite, {
|
||||
item,
|
||||
setFavorite: setIsFavorite,
|
||||
onToggle: () => refetch(),
|
||||
})
|
||||
}}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
>
|
||||
<Icon name={'heart-outline'} small color={'$primary'} />
|
||||
|
||||
<Text marginVertical={'$1.5'} bold>
|
||||
Add to favorites
|
||||
</Text>
|
||||
</ListItem>
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import {
|
||||
ColorTokens,
|
||||
getToken,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
useTheme,
|
||||
YStack,
|
||||
} from 'tamagui'
|
||||
import MaterialDesignIcon from '@react-native-vector-icons/material-design-icons'
|
||||
|
||||
const smallSize = 30
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function Icon({
|
||||
height={size + getToken('$1')}
|
||||
flex={flex}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
<MaterialDesignIcon
|
||||
color={
|
||||
color && !disabled
|
||||
? theme[color]?.val
|
||||
@@ -64,7 +64,8 @@ export default function Icon({
|
||||
? theme.neutral.val
|
||||
: theme.color.val
|
||||
}
|
||||
name={name}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
name={name as any}
|
||||
size={size}
|
||||
testID={testID ?? undefined}
|
||||
/>
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import React from 'react'
|
||||
import { StackParamList } from '../../types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchInstantMixFromItem } from '../../../api/queries/instant-mixes'
|
||||
import Icon from './icon'
|
||||
import { Spacer, Spinner } from 'tamagui'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
export default function InstantMixButton({
|
||||
item,
|
||||
navigation,
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
export default function InstantMixButton({ item }: { item: BaseItemDto }): React.JSX.Element {
|
||||
const { api, user } = useJellifyContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
|
||||
const { data, isFetching, refetch } = useQuery({
|
||||
queryKey: [QueryKeys.InstantMix, item.Id!],
|
||||
queryFn: () => fetchInstantMixFromItem(api, user, item),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import type { CardProps as TamaguiCardProps } from 'tamagui'
|
||||
import { CardProps as TamaguiCardProps } from 'tamagui'
|
||||
import { getToken, Card as TamaguiCard, View, YStack } from 'tamagui'
|
||||
import { BaseItemDto, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { Text } from '../helpers/text'
|
||||
@@ -9,7 +9,7 @@ import { useJellifyContext } from '../../../providers'
|
||||
import { fetchMediaInfo } from '../../../api/queries/media'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { getQualityParams } from '../../../utils/mappings'
|
||||
import { useSettingsContext } from '../../../providers/Settings'
|
||||
import { useStreamingQualityContext } from '../../../providers/Settings'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
interface CardProps extends TamaguiCardProps {
|
||||
@@ -29,7 +29,7 @@ interface CardProps extends TamaguiCardProps {
|
||||
*/
|
||||
export function ItemCard(props: CardProps) {
|
||||
const { api, user } = useJellifyContext()
|
||||
const { streamingQuality } = useSettingsContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
|
||||
useQuery({
|
||||
queryKey: [QueryKeys.MediaSources, streamingQuality, props.item.Id],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { StackParamList } from '../../types'
|
||||
import { BaseStackParamList, RootStackParamList } from '../../../screens/types'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { XStack, YStack } from 'tamagui'
|
||||
@@ -16,7 +16,9 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchMediaInfo } from '../../../api/queries/media'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { useSettingsContext } from '../../../providers/Settings'
|
||||
import { useStreamingQualityContext } from '../../../providers/Settings'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import navigate from '../../../../navigation'
|
||||
|
||||
/**
|
||||
* Displays an item as a row in a list.
|
||||
@@ -32,19 +34,20 @@ import { useSettingsContext } from '../../../providers/Settings'
|
||||
export default function ItemRow({
|
||||
item,
|
||||
queueName,
|
||||
navigation,
|
||||
onPress,
|
||||
circular,
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
queueName: string
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
onPress?: () => void
|
||||
circular?: boolean
|
||||
}): React.JSX.Element {
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
const { api, user } = useJellifyContext()
|
||||
const { streamingQuality } = useSettingsContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
useQuery({
|
||||
queryKey: [QueryKeys.MediaSources, streamingQuality, item.Id],
|
||||
@@ -84,9 +87,9 @@ export default function ItemRow({
|
||||
minHeight={'$7'}
|
||||
width={'100%'}
|
||||
onLongPress={() => {
|
||||
navigation.navigate('Details', {
|
||||
rootNavigation.navigate('Context', {
|
||||
item,
|
||||
isNested: false,
|
||||
navigation,
|
||||
})
|
||||
}}
|
||||
onPress={() => {
|
||||
@@ -97,16 +100,12 @@ export default function ItemRow({
|
||||
|
||||
switch (item.Type) {
|
||||
case 'MusicArtist': {
|
||||
navigation.navigate('Artist', {
|
||||
artist: item,
|
||||
})
|
||||
navigation.navigate('Artist', { artist: item })
|
||||
break
|
||||
}
|
||||
|
||||
case 'MusicAlbum': {
|
||||
navigation.navigate('Album', {
|
||||
album: item,
|
||||
})
|
||||
navigation.navigate('Album', { album: item })
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -164,9 +163,8 @@ export default function ItemRow({
|
||||
<Icon
|
||||
name='dots-horizontal'
|
||||
onPress={() => {
|
||||
navigation.navigate('Details', {
|
||||
rootNavigation.navigate('Context', {
|
||||
item,
|
||||
isNested: false,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { RunTimeTicks } from '../helpers/time-codes'
|
||||
import { BaseItemDto, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import Icon from './icon'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../types'
|
||||
import { BaseStackParamList, RootStackParamList } from '../../../screens/types'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { Queue } from '../../../player/types/queue-item'
|
||||
import FavoriteIcon from './favorite-icon'
|
||||
@@ -19,13 +19,13 @@ import DownloadedIcon from './downloaded-icon'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchMediaInfo } from '../../../api/queries/media'
|
||||
import { useSettingsContext } from '../../../providers/Settings'
|
||||
import { useStreamingQualityContext } from '../../../providers/Settings'
|
||||
import { getQualityParams } from '../../../utils/mappings'
|
||||
import { useNowPlayingContext } from '../../../providers/Player'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export interface TrackProps {
|
||||
track: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
tracklist?: BaseItemDto[] | undefined
|
||||
index: number
|
||||
queue: Queue
|
||||
@@ -43,7 +43,6 @@ export interface TrackProps {
|
||||
export default function Track({
|
||||
track,
|
||||
tracklist,
|
||||
navigation,
|
||||
index,
|
||||
queue,
|
||||
showArtwork,
|
||||
@@ -56,12 +55,16 @@ export default function Track({
|
||||
onRemove,
|
||||
}: TrackProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
|
||||
const stackNavigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const { api, user } = useJellifyContext()
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
const playQueue = usePlayQueueContext()
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
const { downloadedTracks, networkStatus } = useNetworkContext()
|
||||
const { streamingQuality } = useSettingsContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
|
||||
const isPlaying = nowPlaying?.item.Id === track.Id
|
||||
|
||||
@@ -102,9 +105,8 @@ export default function Track({
|
||||
onLongPress
|
||||
? () => onLongPress()
|
||||
: () => {
|
||||
navigation.navigate('Details', {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
isNested: isNested,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -202,9 +204,8 @@ export default function Track({
|
||||
if (showRemove) {
|
||||
if (onRemove) onRemove()
|
||||
} else {
|
||||
navigation.navigate('Details', {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
isNested: isNested,
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons'
|
||||
import { CheckboxProps, XStack, Checkbox, Label } from 'tamagui'
|
||||
|
||||
export function CheckboxWithLabel({
|
||||
@@ -12,7 +12,7 @@ export function CheckboxWithLabel({
|
||||
<XStack width={150} alignItems='center' gap='$4'>
|
||||
<Checkbox id={id} size={size} {...checkboxProps}>
|
||||
<Checkbox.Indicator>
|
||||
<Icon name='check' />
|
||||
<MaterialDesignIcons name='check' />
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { StackParamList } from '../../types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import React from 'react'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
@@ -9,12 +8,14 @@ import Icon from '../../Global/components/icon'
|
||||
import { useHomeContext } from '../../../providers/Home'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import HomeStackParamList from '../../../screens/Home/types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
|
||||
export default function FrequentArtists(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
export default function FrequentArtists({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { frequentArtistsInfiniteQuery } = useHomeContext()
|
||||
const theme = useTheme()
|
||||
const { horizontalItems } = useDisplayContext()
|
||||
@@ -44,6 +45,12 @@ export default function FrequentArtists({
|
||||
artist,
|
||||
})
|
||||
}}
|
||||
onLongPress={() => {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: artist,
|
||||
navigation,
|
||||
})
|
||||
}}
|
||||
size={'$11'}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { StackParamList } from '../../types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useHomeContext } from '../../../providers/Home'
|
||||
import { View, XStack } from 'tamagui'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import { ItemCard } from '../../../components/Global/components/item-card'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useLoadQueueContext } from '../../../providers/Player/queue'
|
||||
import { H4 } from '../../../components/Global/helpers/text'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
export default function FrequentlyPlayedTracks({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
import HomeStackParamList from '../../../screens/Home/types'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
export default function FrequentlyPlayedTracks(): React.JSX.Element {
|
||||
const {
|
||||
frequentlyPlayed,
|
||||
fetchNextFrequentlyPlayed,
|
||||
@@ -22,6 +19,10 @@ export default function FrequentlyPlayedTracks({
|
||||
isFetchingFrequentlyPlayed,
|
||||
} = useHomeContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
const { horizontalItems } = useDisplayContext()
|
||||
|
||||
@@ -44,9 +45,9 @@ export default function FrequentlyPlayedTracks({
|
||||
|
||||
<HorizontalCardList
|
||||
data={
|
||||
(frequentlyPlayed?.pages.flatMap((page) => page).length ?? 0 > horizontalItems)
|
||||
? frequentlyPlayed?.pages.flatMap((page) => page).slice(0, horizontalItems)
|
||||
: frequentlyPlayed?.pages.flatMap((page) => page)
|
||||
(frequentlyPlayed?.length ?? 0 > horizontalItems)
|
||||
? frequentlyPlayed?.slice(0, horizontalItems)
|
||||
: frequentlyPlayed
|
||||
}
|
||||
renderItem={({ item: track, index }) => (
|
||||
<ItemCard
|
||||
@@ -59,18 +60,16 @@ export default function FrequentlyPlayedTracks({
|
||||
useLoadNewQueue({
|
||||
track,
|
||||
index,
|
||||
tracklist: frequentlyPlayed?.pages.flatMap((page) => page) ?? [
|
||||
track,
|
||||
],
|
||||
tracklist: frequentlyPlayed ?? [track],
|
||||
queue: 'On Repeat',
|
||||
queuingType: QueuingType.FromSelection,
|
||||
startPlayback: true,
|
||||
})
|
||||
}}
|
||||
onLongPress={() => {
|
||||
trigger('impactMedium')
|
||||
navigation.navigate('Context', {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
navigation,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,21 +2,23 @@ import React from 'react'
|
||||
import { View, XStack } from 'tamagui'
|
||||
import { useHomeContext } from '../../../providers/Home'
|
||||
import { H4, Text } from '../../Global/helpers/text'
|
||||
import { StackParamList } from '../../types'
|
||||
import { BaseStackParamList, RootStackParamList } from '../../../screens/types'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import HomeStackParamList from '../../../screens/Home/types'
|
||||
|
||||
export default function RecentArtists({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
export default function RecentArtists(): React.JSX.Element {
|
||||
const { recentArtistsInfiniteQuery } = useHomeContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const { horizontalItems } = useDisplayContext()
|
||||
return (
|
||||
<View>
|
||||
@@ -43,6 +45,12 @@ export default function RecentArtists({
|
||||
artist: recentArtist,
|
||||
})
|
||||
}}
|
||||
onLongPress={() => {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: recentArtist,
|
||||
navigation,
|
||||
})
|
||||
}}
|
||||
size={'$11'}
|
||||
></ItemCard>
|
||||
)}
|
||||
|
||||
@@ -4,22 +4,22 @@ import { useHomeContext } from '../../../providers/Home'
|
||||
import { H4 } from '../../Global/helpers/text'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import { useNowPlayingContext } from '../../../providers/Player'
|
||||
import { StackParamList } from '../../types'
|
||||
import { BaseStackParamList, RootStackParamList } from '../../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useLoadQueueContext } from '../../../providers/Player/queue'
|
||||
import { useDisplayContext } from '../../../providers/Display/display-provider'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import HomeStackParamList from '../../../screens/Home/types'
|
||||
|
||||
export default function RecentlyPlayed({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
export default function RecentlyPlayed(): React.JSX.Element {
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
|
||||
const { recentTracks, fetchNextRecentTracks, hasNextRecentTracks, isFetchingRecentTracks } =
|
||||
@@ -46,9 +46,9 @@ export default function RecentlyPlayed({
|
||||
|
||||
<HorizontalCardList
|
||||
data={
|
||||
(recentTracks?.pages.flatMap((page) => page).length ?? 0 > horizontalItems)
|
||||
? recentTracks?.pages.flatMap((page) => page).slice(0, horizontalItems)
|
||||
: recentTracks?.pages.flatMap((page) => page)
|
||||
(recentTracks?.length ?? 0 > horizontalItems)
|
||||
? recentTracks?.slice(0, horizontalItems)
|
||||
: recentTracks
|
||||
}
|
||||
renderItem={({ index, item: recentlyPlayedTrack }) => (
|
||||
<ItemCard
|
||||
@@ -62,19 +62,16 @@ export default function RecentlyPlayed({
|
||||
useLoadNewQueue({
|
||||
track: recentlyPlayedTrack,
|
||||
index: index,
|
||||
tracklist: recentTracks?.pages.flatMap((page) => page) ?? [
|
||||
recentlyPlayedTrack,
|
||||
],
|
||||
tracklist: recentTracks ?? [recentlyPlayedTrack],
|
||||
queue: 'Recently Played',
|
||||
queuingType: QueuingType.FromSelection,
|
||||
startPlayback: true,
|
||||
})
|
||||
}}
|
||||
onLongPress={() => {
|
||||
trigger('impactMedium')
|
||||
navigation.navigate('Details', {
|
||||
rootNavigation.navigate('Context', {
|
||||
item: recentlyPlayedTrack,
|
||||
isNested: false,
|
||||
navigation,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { StackParamList } from '../types'
|
||||
import { ScrollView, RefreshControl } from 'react-native'
|
||||
import { YStack, Separator, getToken } from 'tamagui'
|
||||
import RecentArtists from './helpers/recent-artists'
|
||||
import RecentlyPlayed from './helpers/recently-played'
|
||||
import { useHomeContext } from '../../providers/Home'
|
||||
import { H5 } from '../Global/helpers/text'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import FrequentArtists from './helpers/frequent-artists'
|
||||
import FrequentlyPlayedTracks from './helpers/frequent-tracks'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { usePreventRemove } from '@react-navigation/native'
|
||||
|
||||
export function ProvidedHome({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
export function ProvidedHome(): React.JSX.Element {
|
||||
usePreventRemove(true, () => {})
|
||||
const { user } = useJellifyContext()
|
||||
const { refreshing: refetching, onRefresh } = useHomeContext()
|
||||
@@ -32,19 +25,19 @@ export function ProvidedHome({
|
||||
removeClippedSubviews // Save memory usage
|
||||
>
|
||||
<YStack alignContent='flex-start'>
|
||||
<RecentArtists navigation={navigation} />
|
||||
<RecentArtists />
|
||||
|
||||
<Separator marginVertical={'$3'} />
|
||||
|
||||
<RecentlyPlayed navigation={navigation} />
|
||||
<RecentlyPlayed />
|
||||
|
||||
<Separator marginVertical={'$3'} />
|
||||
|
||||
<FrequentArtists navigation={navigation} />
|
||||
<FrequentArtists />
|
||||
|
||||
<Separator marginVertical={'$3'} />
|
||||
|
||||
<FrequentlyPlayedTracks navigation={navigation} />
|
||||
<FrequentlyPlayedTracks />
|
||||
</YStack>
|
||||
</ScrollView>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { InstantMixProps } from '../types'
|
||||
import { InstantMixProps } from '../../screens/types'
|
||||
import { FlatList } from 'react-native'
|
||||
import Track from '../Global/components/track'
|
||||
import { Separator } from 'tamagui'
|
||||
|
||||
export default function InstantMix({ route, navigation }: InstantMixProps): React.JSX.Element {
|
||||
const { item, mix } = route.params
|
||||
export default function InstantMix({ route }: InstantMixProps): React.JSX.Element {
|
||||
const { mix } = route.params
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
@@ -14,7 +14,6 @@ export default function InstantMix({ route, navigation }: InstantMixProps): Reac
|
||||
<Track
|
||||
showArtwork
|
||||
track={item}
|
||||
navigation={navigation}
|
||||
index={index}
|
||||
queue={'Instant Mix'}
|
||||
tracklist={mix}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { StackParamList } from '../types'
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'
|
||||
import PlaylistsTab from './components/playlists-tab'
|
||||
import { getToken, useTheme } from 'tamagui'
|
||||
@@ -8,13 +7,17 @@ import ArtistsTab from './components/artists-tab'
|
||||
import AlbumsTab from './components/albums-tab'
|
||||
import LibraryTabBar from './tab-bar'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import LibraryStackParamList from '../../screens/Library/types'
|
||||
|
||||
const LibraryTabsNavigator = createMaterialTopTabNavigator()
|
||||
|
||||
export default function Library({
|
||||
route,
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
route: RouteProp<LibraryStackParamList, 'Library'>
|
||||
navigation: NativeStackNavigationProp<LibraryStackParamList, 'Library'>
|
||||
}): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Albums from '../../Albums/component'
|
||||
import { StackParamList } from '../../types'
|
||||
import { useAlbumsInfiniteQueryContext } from '../../../providers/Library'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export default function AlbumsTab(): React.JSX.Element {
|
||||
const albumsInfiniteQuery = useAlbumsInfiniteQueryContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<StackParamList>>()
|
||||
|
||||
return (
|
||||
<Albums
|
||||
albums={albumsInfiniteQuery.data}
|
||||
navigation={navigation}
|
||||
fetchNextPage={albumsInfiniteQuery.fetchNextPage}
|
||||
hasNextPage={albumsInfiniteQuery.hasNextPage}
|
||||
isPending={albumsInfiniteQuery.isPending}
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import Artists from '../../Artists/component'
|
||||
import {
|
||||
useArtistPageParamsContext,
|
||||
useArtistsInfiniteQueryContext,
|
||||
} from '../../../providers/Library'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../types'
|
||||
|
||||
export default function ArtistsTab(): React.JSX.Element {
|
||||
const artistsInfiniteQuery = useArtistsInfiniteQueryContext()
|
||||
const artistPageParams = useArtistPageParamsContext()
|
||||
const navigation = useNavigation<NativeStackNavigationProp<StackParamList>>()
|
||||
|
||||
return (
|
||||
<Artists
|
||||
artistsInfiniteQuery={artistsInfiniteQuery}
|
||||
navigation={navigation}
|
||||
showAlphabeticalSelector={true}
|
||||
artistPageParams={artistPageParams}
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../types'
|
||||
import Playlists from '../../Playlists/component'
|
||||
import React from 'react'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { usePlaylistsInfiniteQueryContext } from '../../../providers/Library'
|
||||
|
||||
export default function PlaylistsTab(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<StackParamList>>()
|
||||
|
||||
const playlistsInfiniteQuery = usePlaylistsInfiniteQueryContext()
|
||||
|
||||
return (
|
||||
<Playlists
|
||||
navigation={navigation}
|
||||
playlists={playlistsInfiniteQuery.data}
|
||||
refetch={playlistsInfiniteQuery.refetch}
|
||||
fetchNextPage={playlistsInfiniteQuery.fetchNextPage}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import Tracks from '../../Tracks/component'
|
||||
import { useTracksInfiniteQueryContext } from '../../../providers/Library'
|
||||
import { useLibrarySortAndFilterContext } from '../../../providers/Library/sorting-filtering'
|
||||
|
||||
export default function TracksTab(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<StackParamList>>()
|
||||
|
||||
const tracksInfiniteQuery = useTracksInfiniteQueryContext()
|
||||
|
||||
const { isFavorites, isDownloaded } = useLibrarySortAndFilterContext()
|
||||
|
||||
return (
|
||||
<Tracks
|
||||
navigation={navigation}
|
||||
tracks={tracksInfiniteQuery.data}
|
||||
queue={isFavorites ? 'Favorite Tracks' : isDownloaded ? 'Downloaded Tracks' : 'Library'}
|
||||
filterDownloaded={isDownloaded}
|
||||
|
||||
@@ -7,13 +7,13 @@ import { Text } from '../Global/helpers/text'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
import { useSettingsContext } from '../../providers/Settings'
|
||||
import { useReducedHapticsContext } from '../../providers/Settings'
|
||||
|
||||
export default function LibraryTabBar(props: MaterialTopTabBarProps) {
|
||||
const { isFavorites, setIsFavorites, isDownloaded, setIsDownloaded } =
|
||||
useLibrarySortAndFilterContext()
|
||||
|
||||
const { reducedHaptics } = useSettingsContext()
|
||||
const reducedHaptics = useReducedHapticsContext()
|
||||
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useNowPlayingContext } from '../../../providers/Player'
|
||||
import { getToken, useTheme, View, YStack, ZStack } from 'tamagui'
|
||||
import { useColorScheme } from 'react-native'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import { useSettingsContext } from '../../../providers/Settings'
|
||||
import { useThemeSettingContext } from '../../../providers/Settings'
|
||||
import { getPrimaryBlurhashFromDto } from '../../../utils/blurhash'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
|
||||
@@ -17,7 +17,7 @@ export default function BlurredBackground({
|
||||
}): React.JSX.Element {
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
|
||||
const { theme: themeSetting } = useSettingsContext()
|
||||
const themeSetting = useThemeSettingContext()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
|
||||
@@ -3,13 +3,12 @@ import { XStack } from 'tamagui'
|
||||
import Icon from '../../Global/components/icon'
|
||||
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export default function Footer(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
export default function Footer({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
return (
|
||||
<XStack justifyContent='flex-end' alignItems='center' marginHorizontal={'$5'} flex={1}>
|
||||
<XStack alignItems='center' justifyContent='flex-start' flex={1}>
|
||||
|
||||
@@ -5,18 +5,15 @@ import { getToken, useWindowDimensions, XStack, YStack, useTheme } from 'tamagui
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import React from 'react'
|
||||
import { State } from 'react-native-track-player'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
export default function PlayerHeader({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
export default function PlayerHeader(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
const playbackState = usePlaybackStateContext()
|
||||
@@ -57,7 +54,7 @@ export default function PlayerHeader({
|
||||
small
|
||||
name='dots-vertical'
|
||||
onPress={() => {
|
||||
navigation.navigate('Details', {
|
||||
navigation.navigate('Context', {
|
||||
item: nowPlaying!.item,
|
||||
isNested: true,
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useNowPlayingContext, useSeekToContext } from '../../../providers/Playe
|
||||
import { RunTimeSeconds } from '../../../components/Global/helpers/time-codes'
|
||||
import { UPDATE_INTERVAL } from '../../../player/config'
|
||||
import { ProgressMultiplier } from '../component.config'
|
||||
import { useSettingsContext } from '../../../providers/Settings'
|
||||
import { useReducedHapticsContext } from '../../../providers/Settings'
|
||||
|
||||
// Create a simple pan gesture
|
||||
const scrubGesture = Gesture.Pan().runOnJS(true)
|
||||
@@ -18,7 +18,7 @@ export default function Scrubber(): React.JSX.Element {
|
||||
const useSeekTo = useSeekToContext()
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
const { width } = useSafeAreaFrame()
|
||||
const { reducedHaptics } = useSettingsContext()
|
||||
const reducedHaptics = useReducedHapticsContext()
|
||||
|
||||
// Get progress from the track player with the specified update interval
|
||||
const { position, duration } = useProgress(UPDATE_INTERVAL)
|
||||
|
||||
@@ -4,28 +4,37 @@ import { TextTickerConfig } from '../component.config'
|
||||
import { useNowPlayingContext } from '../../../providers/Player'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../types'
|
||||
import { RootStackParamList } from '../../../screens/types'
|
||||
import React, { useMemo } from 'react'
|
||||
import ItemImage from '../../Global/components/image'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchItem } from '../../../api/queries/item'
|
||||
import { fetchItem, fetchItems } from '../../../api/queries/item'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import FavoriteButton from '../../Global/components/favorite-button'
|
||||
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import navigate from '../../../../navigation'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
export default function SongInfo({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
export default function SongInfo(): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const { data: album } = useQuery({
|
||||
queryKey: ['album', nowPlaying!.item.AlbumId],
|
||||
queryKey: [QueryKeys.Album, nowPlaying!.item.AlbumId],
|
||||
queryFn: () => fetchItem(api, nowPlaying!.item.AlbumId!),
|
||||
})
|
||||
|
||||
const { data: artists } = useQuery({
|
||||
queryKey: [QueryKeys.TrackArtists, nowPlaying!.item.ArtistItems],
|
||||
queryFn: () => fetchItems(api, user, library, [BaseItemKind.MusicArtist]),
|
||||
select: (data) => data.data,
|
||||
})
|
||||
|
||||
return useMemo(() => {
|
||||
return (
|
||||
<XStack flex={1}>
|
||||
@@ -34,7 +43,8 @@ export default function SongInfo({
|
||||
onPress={() => {
|
||||
if (album) {
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigation.navigate('Tabs', {
|
||||
|
||||
navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'Album',
|
||||
@@ -80,12 +90,12 @@ export default function SongInfo({
|
||||
})
|
||||
} else {
|
||||
navigation.goBack() // Dismiss player modal
|
||||
navigation.navigate('Tabs', {
|
||||
navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'Artist',
|
||||
params: {
|
||||
artist: nowPlaying!.item.ArtistItems![0],
|
||||
artist: nowPlaying!.item.ArtistItems[0],
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -99,7 +109,16 @@ export default function SongInfo({
|
||||
</Animated.View>
|
||||
</YStack>
|
||||
|
||||
<XStack justifyContent='flex-end' alignItems='center' flexShrink={1}>
|
||||
<XStack gap={'$3'} justifyContent='flex-end' alignItems='center' flexShrink={1}>
|
||||
<Icon
|
||||
name='dots-horizontal-circle-outline'
|
||||
onPress={() => {
|
||||
navigation.navigate('Context', {
|
||||
item: nowPlaying!.item,
|
||||
isNested: true,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<FavoriteButton item={nowPlaying!.item} />
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { StackParamList } from '../types'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { useNowPlayingContext } from '../../providers/Player'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
@@ -27,7 +27,7 @@ import SongInfo from './components/song-info'
|
||||
export default function PlayerScreen({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const [showToast, setShowToast] = useState(true)
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function PlayerScreen({
|
||||
<BlurredBackground width={width} height={height} />
|
||||
|
||||
<YStack fullscreen marginBottom={bottom}>
|
||||
<PlayerHeader navigation={navigation} />
|
||||
<PlayerHeader />
|
||||
|
||||
<XStack
|
||||
justifyContent='center'
|
||||
@@ -64,7 +64,7 @@ export default function PlayerScreen({
|
||||
maxWidth={width / 1.1}
|
||||
flex={2}
|
||||
>
|
||||
<SongInfo navigation={navigation} />
|
||||
<SongInfo />
|
||||
</XStack>
|
||||
|
||||
<XStack justifyContent='center' flex={1}>
|
||||
@@ -74,7 +74,7 @@ export default function PlayerScreen({
|
||||
|
||||
<Controls />
|
||||
|
||||
<Footer navigation={navigation} />
|
||||
<Footer />
|
||||
</YStack>
|
||||
</ZStack>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Icon from '../Global/components/icon'
|
||||
import Track from '../Global/components/track'
|
||||
import { StackParamList } from '../types'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { useNowPlayingContext } from '../../providers/Player'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import DraggableFlatList from 'react-native-draggable-flatlist'
|
||||
@@ -20,7 +20,7 @@ import { useLayoutEffect } from 'react'
|
||||
export default function Queue({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const nowPlaying = useNowPlayingContext()
|
||||
|
||||
@@ -84,7 +84,6 @@ export default function Queue({
|
||||
>
|
||||
<Track
|
||||
queue={queueRef}
|
||||
navigation={navigation}
|
||||
track={queueItem.item}
|
||||
index={getIndex() ?? 0}
|
||||
showArtwork
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../types'
|
||||
import { BaseStackParamList } from '../../../screens/types'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { getToken, getTokens, Separator, View, XStack, YStack } from 'tamagui'
|
||||
import { AnimatedH5 } from '../../Global/helpers/text'
|
||||
@@ -13,15 +13,16 @@ import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { useJellifyContext } from '../../../providers'
|
||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useNetworkContext } from '../../../../src/providers/Network'
|
||||
import { useSettingsContext } from '../../../../src/providers/Settings'
|
||||
import { ActivityIndicator } from 'react-native'
|
||||
import { mapDtoToTrack } from '../../../utils/mappings'
|
||||
import { useLoadQueueContext } from '../../../providers/Player/queue'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { useDownloadQualityContext, useStreamingQualityContext } from '../../../providers/Settings'
|
||||
import navigate from '../../../../navigation'
|
||||
|
||||
export default function PlayliistTracklistHeader(
|
||||
playlist: BaseItemDto,
|
||||
navigation: NativeStackNavigationProp<StackParamList>,
|
||||
navigation: NativeStackNavigationProp<BaseStackParamList>,
|
||||
editing: boolean,
|
||||
playlistTracks: BaseItemDto[],
|
||||
canEdit: boolean | undefined,
|
||||
@@ -123,7 +124,6 @@ export default function PlayliistTracklistHeader(
|
||||
<PlaylistHeaderControls
|
||||
editing={editing}
|
||||
setEditing={setEditing}
|
||||
navigation={navigation}
|
||||
playlist={playlist}
|
||||
playlistTracks={playlistTracks}
|
||||
canEdit={canEdit}
|
||||
@@ -138,20 +138,19 @@ export default function PlayliistTracklistHeader(
|
||||
function PlaylistHeaderControls({
|
||||
editing,
|
||||
setEditing,
|
||||
navigation,
|
||||
playlist,
|
||||
playlistTracks,
|
||||
canEdit,
|
||||
}: {
|
||||
editing: boolean
|
||||
setEditing: (editing: boolean) => void
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
playlist: BaseItemDto
|
||||
playlistTracks: BaseItemDto[]
|
||||
canEdit: boolean | undefined
|
||||
}): React.JSX.Element {
|
||||
const { useDownloadMultiple, pendingDownloads } = useNetworkContext()
|
||||
const { downloadQuality, streamingQuality } = useSettingsContext()
|
||||
const downloadQuality = useDownloadQualityContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
const useLoadNewQueue = useLoadQueueContext()
|
||||
const isDownloading = pendingDownloads.length != 0
|
||||
const { sessionId, api } = useJellifyContext()
|
||||
@@ -185,11 +184,19 @@ function PlaylistHeaderControls({
|
||||
<Icon
|
||||
color={'$danger'}
|
||||
name='delete-sweep-outline' // otherwise use "delete-circle"
|
||||
onPress={() => navigation.navigate('DeletePlaylist', { playlist })}
|
||||
onPress={() => {
|
||||
navigate('Tabs', {
|
||||
screen: 'Library',
|
||||
params: {
|
||||
screen: 'DeletePlaylist',
|
||||
params: { playlist },
|
||||
},
|
||||
})
|
||||
}}
|
||||
small
|
||||
/>
|
||||
) : (
|
||||
<InstantMixButton item={playlist} navigation={navigation} />
|
||||
<InstantMixButton item={playlist} />
|
||||
)}
|
||||
</YStack>
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ import PlayliistTracklistHeader from './components/header'
|
||||
import { usePlaylistContext } from '../../providers/Playlist'
|
||||
import { useAnimatedScrollHandler } from 'react-native-reanimated'
|
||||
import AnimatedDraggableFlatList from '../Global/components/animated-draggable-flat-list'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
|
||||
export default function Playlist({
|
||||
playlist,
|
||||
@@ -25,6 +28,8 @@ export default function Playlist({
|
||||
useRemoveFromPlaylist,
|
||||
} = usePlaylistContext()
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const scrollOffsetHandler = useAnimatedScrollHandler({
|
||||
onScroll: (event) => {
|
||||
'worklet'
|
||||
@@ -77,7 +82,6 @@ export default function Playlist({
|
||||
{editing && canEdit && <Icon name='drag' onPress={drag} />}
|
||||
|
||||
<Track
|
||||
navigation={navigation}
|
||||
track={track}
|
||||
tracklist={playlistTracks ?? []}
|
||||
index={getIndex() ?? 0}
|
||||
@@ -86,9 +90,8 @@ export default function Playlist({
|
||||
onLongPress={() => {
|
||||
editing
|
||||
? drag()
|
||||
: navigation.navigate('Details', {
|
||||
: rootNavigation.navigate('Context', {
|
||||
item: track,
|
||||
isNested: false,
|
||||
})
|
||||
}}
|
||||
showRemove={editing}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../types'
|
||||
import { BaseStackParamList } from '../../screens/types'
|
||||
|
||||
export interface PlaylistProps {
|
||||
playlist: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<BaseStackParamList>
|
||||
canEdit?: boolean | undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import { RefreshControl } from 'react-native-gesture-handler'
|
||||
import { Separator } from 'tamagui'
|
||||
import { PlaylistsProps } from '../types'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { FetchNextPageOptions } from '@tanstack/react-query'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { BaseStackParamList } from '@/src/screens/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
|
||||
export interface PlaylistsProps {
|
||||
canEdit?: boolean | undefined
|
||||
playlists: BaseItemDto[] | undefined
|
||||
refetch: () => void
|
||||
fetchNextPage: (options?: FetchNextPageOptions | undefined) => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
}
|
||||
export default function Playlists({
|
||||
playlists,
|
||||
navigation,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
@@ -14,6 +26,7 @@ export default function Playlists({
|
||||
isFetchingNextPage,
|
||||
canEdit,
|
||||
}: PlaylistsProps): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<BaseStackParamList>>()
|
||||
return (
|
||||
<FlashList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
@@ -28,7 +41,6 @@ export default function Playlists({
|
||||
onPress={() => {
|
||||
navigation.navigate('Playlist', { playlist, canEdit })
|
||||
}}
|
||||
navigation={navigation}
|
||||
queueName={playlist.Name ?? 'Untitled Playlist'}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react'
|
||||
import Input from '../Global/helpers/input'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../types'
|
||||
import { RootStackParamList } from '../../screens/types'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchSearchResults } from '../../api/queries/search'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
@@ -15,10 +15,11 @@ import { isEmpty } from 'lodash'
|
||||
import HorizontalCardList from '../Global/components/horizontal-list'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import SearchParamList from '../../screens/Search/types'
|
||||
export default function Search({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<SearchParamList, 'Search'>
|
||||
}): React.JSX.Element {
|
||||
const { api, library, user } = useJellifyContext()
|
||||
|
||||
@@ -104,20 +105,14 @@ export default function Search({
|
||||
ListEmptyComponent={() => {
|
||||
return (
|
||||
<YStack alignContent='center' justifyContent='flex-end' marginTop={'$4'}>
|
||||
{fetchingResults ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<Suggestions suggestions={suggestions} navigation={navigation} />
|
||||
)}
|
||||
{fetchingResults ? <Spinner /> : <Suggestions suggestions={suggestions} />}
|
||||
</YStack>
|
||||
)
|
||||
}}
|
||||
// We're displaying artists separately so we're going to filter them out here
|
||||
data={items?.filter((result) => result.Type !== 'MusicArtist')}
|
||||
refreshing={fetchingResults}
|
||||
renderItem={({ item }) => (
|
||||
<ItemRow item={item} queueName={searchString ?? 'Search'} navigation={navigation} />
|
||||
)}
|
||||
renderItem={({ item }) => <ItemRow item={item} queueName={searchString ?? 'Search'} />}
|
||||
style={{
|
||||
marginHorizontal: getToken('$2'),
|
||||
marginTop: getToken('$4'),
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { FlatList, RefreshControl } from 'react-native'
|
||||
import { StackParamList } from '../types'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import ItemRow from '../Global/components/item-row'
|
||||
import { H3, Text } from '../Global/helpers/text'
|
||||
import { getToken, Separator, YStack } from 'tamagui'
|
||||
import { Separator, YStack } from 'tamagui'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import HorizontalCardList from '../Global/components/horizontal-list'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import SearchParamList from '../../screens/Search/types'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
|
||||
interface SuggestionsProps {
|
||||
export default function Suggestions({
|
||||
suggestions,
|
||||
}: {
|
||||
suggestions: BaseItemDto[] | undefined
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}
|
||||
}): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<SearchParamList>>()
|
||||
|
||||
export default function Suggestions(props: SuggestionsProps): React.JSX.Element {
|
||||
return (
|
||||
<FlashList
|
||||
// Artists are displayed in the header, so we'll filter them out here
|
||||
data={props.suggestions?.filter((suggestion) => suggestion.Type !== 'MusicArtist')}
|
||||
data={suggestions?.filter((suggestion) => suggestion.Type !== 'MusicArtist')}
|
||||
ListHeaderComponent={
|
||||
<YStack>
|
||||
<H3>Suggestions</H3>
|
||||
|
||||
<HorizontalCardList
|
||||
data={props.suggestions?.filter(
|
||||
data={suggestions?.filter(
|
||||
(suggestion) => suggestion.Type === 'MusicArtist',
|
||||
)}
|
||||
renderItem={({ item: suggestedArtist }) => {
|
||||
@@ -32,7 +33,7 @@ export default function Suggestions(props: SuggestionsProps): React.JSX.Element
|
||||
<ItemCard
|
||||
item={suggestedArtist}
|
||||
onPress={() => {
|
||||
props.navigation.push('Artist', {
|
||||
navigation.push('Artist', {
|
||||
artist: suggestedArtist,
|
||||
})
|
||||
}}
|
||||
@@ -51,9 +52,7 @@ export default function Suggestions(props: SuggestionsProps): React.JSX.Element
|
||||
</Text>
|
||||
}
|
||||
renderItem={({ item }) => {
|
||||
return (
|
||||
<ItemRow item={item} queueName={'Suggestions'} navigation={props.navigation} />
|
||||
)
|
||||
return <ItemRow item={item} queueName={'Suggestions'} />
|
||||
}}
|
||||
style={{
|
||||
marginHorizontal: 2,
|
||||
|
||||
@@ -9,13 +9,13 @@ import PlaybackTab from './components/playback-tab'
|
||||
import InfoTab from './components/info-tab'
|
||||
import SettingsTabBar from './components/tab-bar'
|
||||
import StorageTab from './components/storage-tab'
|
||||
import { useSettingsContext } from '../../providers/Settings'
|
||||
import { useDevToolsContext } from '../../providers/Settings'
|
||||
const SettingsTabsNavigator = createMaterialTopTabNavigator()
|
||||
|
||||
export default function Settings(): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
|
||||
const { devTools } = useSettingsContext()
|
||||
const devTools = useDevToolsContext()
|
||||
|
||||
return (
|
||||
<SettingsTabsNavigator.Navigator
|
||||
|
||||
@@ -11,11 +11,11 @@ import { FlatList, Linking } from 'react-native'
|
||||
import { H6, ScrollView, Separator, XStack, YStack } from 'tamagui'
|
||||
import Icon from '../../../Global/components/icon'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSettingsContext } from '../../../../providers/Settings'
|
||||
import { useSetDevToolsContext } from '../../../../providers/Settings'
|
||||
export default function InfoTabIndex({ navigation }: InfoTabNativeStackNavigationProp) {
|
||||
const { api } = useJellifyContext()
|
||||
|
||||
const { setDevTools } = useSettingsContext()
|
||||
const setDevTools = useSetDevToolsContext()
|
||||
|
||||
const [versionNumberPresses, setVersionNumberPresses] = useState(0)
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function InfoTabIndex({ navigation }: InfoTabNativeStackNavigatio
|
||||
Linking.openURL('https://discord.gg/yf8fBatktn')
|
||||
}
|
||||
>
|
||||
<Icon name='discord' small color='$borderColor' />
|
||||
<Icon name='chat' small color='$borderColor' />
|
||||
<Text>Join Discord</Text>
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
@@ -3,10 +3,15 @@ import { RadioGroup, YStack } from 'tamagui'
|
||||
import { RadioGroupItemWithLabel } from '../../Global/helpers/radio-group-item-with-label'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { getQualityLabel, getBandwidthEstimate } from '../utils/quality'
|
||||
import { StreamingQuality, useSettingsContext } from '../../../providers/Settings'
|
||||
import {
|
||||
StreamingQuality,
|
||||
useSetStreamingQualityContext,
|
||||
useStreamingQualityContext,
|
||||
} from '../../../providers/Settings'
|
||||
|
||||
export default function PlaybackTab(): React.JSX.Element {
|
||||
const { streamingQuality, setStreamingQuality } = useSettingsContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
const setStreamingQuality = useSetStreamingQualityContext()
|
||||
|
||||
return (
|
||||
<SettingsListGroup
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
import { RadioGroup, YStack } from 'tamagui'
|
||||
import { Theme, useSettingsContext } from '../../../providers/Settings'
|
||||
import {
|
||||
Theme,
|
||||
useReducedHapticsContext,
|
||||
useSendMetricsContext,
|
||||
useSetReducedHapticsContext,
|
||||
useSetSendMetricsContext,
|
||||
useSetThemeSettingContext,
|
||||
useThemeSettingContext,
|
||||
} from '../../../providers/Settings'
|
||||
import { SwitchWithLabel } from '../../Global/helpers/switch-with-label'
|
||||
import SettingsListGroup from './settings-list-group'
|
||||
import { RadioGroupItemWithLabel } from '../../Global/helpers/radio-group-item-with-label'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
|
||||
export default function PreferencesTab(): React.JSX.Element {
|
||||
const { setSendMetrics, sendMetrics, setReducedHaptics, reducedHaptics, theme, setTheme } =
|
||||
useSettingsContext()
|
||||
const setSendMetrics = useSetSendMetricsContext()
|
||||
const sendMetrics = useSendMetricsContext()
|
||||
const setReducedHaptics = useSetReducedHapticsContext()
|
||||
const reducedHaptics = useReducedHapticsContext()
|
||||
const themeSetting = useThemeSettingContext()
|
||||
const setThemeSetting = useSetThemeSettingContext()
|
||||
|
||||
return (
|
||||
<SettingsListGroup
|
||||
settingsList={[
|
||||
{
|
||||
title: 'Theme',
|
||||
subTitle: `Current: ${theme}`,
|
||||
subTitle: `Current: ${themeSetting}`,
|
||||
iconName: 'theme-light-dark',
|
||||
iconColor: `${theme === 'system' ? '$borderColor' : '$primary'}`,
|
||||
iconColor: `${themeSetting === 'system' ? '$borderColor' : '$primary'}`,
|
||||
children: (
|
||||
<YStack gap='$2' paddingVertical='$2'>
|
||||
<RadioGroup
|
||||
value={theme}
|
||||
onValueChange={(value) => setTheme(value as Theme)}
|
||||
value={themeSetting}
|
||||
onValueChange={(value) => setThemeSetting(value as Theme)}
|
||||
>
|
||||
<RadioGroupItemWithLabel size='$3' value='system' label='System' />
|
||||
<RadioGroupItemWithLabel size='$3' value='light' label='Light' />
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import SettingsListGroup from './settings-list-group'
|
||||
import { SwitchWithLabel } from '../../Global/helpers/switch-with-label'
|
||||
import { RadioGroupItemWithLabel } from '../../Global/helpers/radio-group-item-with-label'
|
||||
import { useSettingsContext, DownloadQuality } from '../../../providers/Settings'
|
||||
import {
|
||||
DownloadQuality,
|
||||
useAutoDownloadContext,
|
||||
useSetAutoDownloadContext,
|
||||
useDownloadQualityContext,
|
||||
useSetDownloadQualityContext,
|
||||
} from '../../../providers/Settings'
|
||||
import { useNetworkContext } from '../../../providers/Network'
|
||||
import { RadioGroup, YStack } from 'tamagui'
|
||||
import { Text } from '../../Global/helpers/text'
|
||||
import { getQualityLabel } from '../utils/quality'
|
||||
export default function StorageTab(): React.JSX.Element {
|
||||
const { autoDownload, setAutoDownload, downloadQuality, setDownloadQuality } =
|
||||
useSettingsContext()
|
||||
const autoDownload = useAutoDownloadContext()
|
||||
const setAutoDownload = useSetAutoDownloadContext()
|
||||
const downloadQuality = useDownloadQualityContext()
|
||||
const setDownloadQuality = useSetDownloadQualityContext()
|
||||
|
||||
const { downloadedTracks } = useNetworkContext()
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import Track from '../Global/components/track'
|
||||
import { FlatList } from 'react-native'
|
||||
import { getTokens, Separator } from 'tamagui'
|
||||
import { StackParamList } from '../types'
|
||||
import { BaseItemDto, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { Queue } from '../../player/types/queue-item'
|
||||
import { useNetworkContext } from '../../providers/Network'
|
||||
import { queryClient } from '../../constants/query-client'
|
||||
@@ -16,7 +13,6 @@ export default function Tracks({
|
||||
queue,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
navigation,
|
||||
filterDownloaded,
|
||||
filterFavorites,
|
||||
}: {
|
||||
@@ -24,7 +20,6 @@ export default function Tracks({
|
||||
queue: Queue
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
filterDownloaded?: boolean | undefined
|
||||
filterFavorites?: boolean | undefined
|
||||
}): React.JSX.Element {
|
||||
@@ -64,7 +59,6 @@ export default function Tracks({
|
||||
data={tracksToDisplay()}
|
||||
renderItem={({ index, item: track }) => (
|
||||
<Track
|
||||
navigation={navigation}
|
||||
showArtwork
|
||||
index={0}
|
||||
track={track}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { JellifyUserDataProvider } from '../providers/UserData'
|
||||
import { NetworkContextProvider } from '../providers/Network'
|
||||
import { QueueProvider } from '../providers/Player/queue'
|
||||
import { DisplayProvider } from '../providers/Display/display-provider'
|
||||
import { SettingsProvider, useSettingsContext } from '../providers/Settings'
|
||||
import { useSendMetricsContext, useThemeSettingContext } from '../providers/Settings'
|
||||
import {
|
||||
createTelemetryDeck,
|
||||
TelemetryDeckProvider,
|
||||
@@ -26,7 +26,7 @@ import { CarPlayProvider } from '../providers/CarPlay'
|
||||
* @returns The {@link Jellify} component
|
||||
*/
|
||||
export default function Jellify(): React.JSX.Element {
|
||||
const { theme } = useSettingsContext()
|
||||
const theme = useThemeSettingContext()
|
||||
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function Jellify(): React.JSX.Element {
|
||||
}
|
||||
|
||||
function JellifyLoggingWrapper({ children }: { children: React.ReactNode }): React.JSX.Element {
|
||||
const { sendMetrics } = useSettingsContext()
|
||||
const sendMetrics = useSendMetricsContext()
|
||||
|
||||
/**
|
||||
* Create the TelemetryDeck instance, which is used to send telemetry data to the server
|
||||
@@ -68,7 +68,7 @@ function JellifyLoggingWrapper({ children }: { children: React.ReactNode }): Rea
|
||||
* @returns The {@link App} component
|
||||
*/
|
||||
function App(): React.JSX.Element {
|
||||
const { sendMetrics } = useSettingsContext()
|
||||
const sendMetrics = useSendMetricsContext()
|
||||
const telemetrydeck = useTelemetryDeck()
|
||||
const theme = useTheme()
|
||||
|
||||
|
||||
227
src/components/types.d.ts
vendored
227
src/components/types.d.ts
vendored
@@ -1,227 +0,0 @@
|
||||
import { QueryKeys } from '../enums/query-keys'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp, NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { Queue } from '../player/types/queue-item'
|
||||
import { MaterialTopTabBarProps } from '@react-navigation/material-top-tabs'
|
||||
import {
|
||||
InfiniteData,
|
||||
InfiniteQueryObserverResult,
|
||||
UseInfiniteQueryResult,
|
||||
} from '@tanstack/react-query'
|
||||
import { RefObject } from 'react'
|
||||
|
||||
export type StackParamList = {
|
||||
Login: {
|
||||
screen: keyof StackParamList
|
||||
}
|
||||
ServerAddress: undefined
|
||||
ServerAuthentication: undefined
|
||||
|
||||
LibrarySelection: undefined
|
||||
|
||||
HomeScreen: undefined
|
||||
Home: undefined
|
||||
AddPlaylist: undefined
|
||||
RecentArtists: {
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
}
|
||||
MostPlayedArtists: {
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
}
|
||||
RecentTracks: {
|
||||
tracks: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
}
|
||||
MostPlayedTracks: {
|
||||
tracks: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
}
|
||||
UserPlaylists: {
|
||||
playlists: BaseItemDto[]
|
||||
}
|
||||
|
||||
Tracks: {
|
||||
tracks: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
queue: Queue
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
}
|
||||
|
||||
Discover: undefined
|
||||
RecentlyAdded: {
|
||||
albums: BaseItemDto[] | undefined
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
}
|
||||
PublicPlaylists: {
|
||||
playlists: BaseItemDto[] | undefined
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
refetch: () => void
|
||||
}
|
||||
SuggestedArtists: {
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}
|
||||
|
||||
LibraryScreen: undefined
|
||||
Library: undefined
|
||||
|
||||
DeletePlaylist: {
|
||||
playlist: BaseItemDto
|
||||
}
|
||||
|
||||
Search: undefined
|
||||
|
||||
Settings: undefined
|
||||
Account: undefined
|
||||
Server: undefined
|
||||
Playback: undefined
|
||||
Labs: undefined
|
||||
SignOut: undefined
|
||||
|
||||
Tabs: {
|
||||
screen: keyof StackParamList
|
||||
params: object
|
||||
}
|
||||
|
||||
PlayerScreen: undefined
|
||||
Player: undefined
|
||||
Queue: undefined
|
||||
|
||||
MultipleArtists: {
|
||||
artists: BaseItemDto[]
|
||||
}
|
||||
|
||||
Artist: {
|
||||
artist: BaseItemDto
|
||||
}
|
||||
ArtistAlbums: undefined
|
||||
ArtistEps: undefined
|
||||
ArtistFeaturedOn: undefined
|
||||
|
||||
SimilarArtists: {
|
||||
artist: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}
|
||||
|
||||
Album: {
|
||||
album: BaseItemDto
|
||||
}
|
||||
Playlist: {
|
||||
playlist: BaseItemDto
|
||||
canEdit?: boolean | undefined
|
||||
}
|
||||
Details: {
|
||||
item: BaseItemDto
|
||||
isNested: boolean | undefined
|
||||
}
|
||||
Offline: undefined
|
||||
InstantMix: {
|
||||
item: BaseItemDto
|
||||
mix: BaseItemDto[]
|
||||
}
|
||||
|
||||
Context: {
|
||||
item: BaseItemDto
|
||||
}
|
||||
}
|
||||
|
||||
export type LoginProps = NativeStackScreenProps<StackParamList, 'Login'>
|
||||
export type ServerAddressProps = NativeStackScreenProps<StackParamList, 'ServerAddress'>
|
||||
export type ServerAuthenticationProps = NativeStackScreenProps<
|
||||
StackParamList,
|
||||
'ServerAuthentication'
|
||||
>
|
||||
export type LibrarySelectionProps = NativeStackScreenProps<StackParamList, 'LibrarySelection'>
|
||||
|
||||
export type TabProps = NativeStackScreenProps<StackParamList, 'Tabs'>
|
||||
export type PlayerProps = NativeStackScreenProps<StackParamList, 'Player'>
|
||||
export type MultipleArtistsProps = NativeStackScreenProps<StackParamList, 'MultipleArtists'>
|
||||
|
||||
export type ProvidedHomeProps = NativeStackScreenProps<StackParamList, 'HomeScreen'>
|
||||
export type AddPlaylistProps = NativeStackScreenProps<StackParamList, 'AddPlaylist'>
|
||||
export type RecentArtistsProps = NativeStackScreenProps<StackParamList, 'RecentArtists'>
|
||||
export type RecentTracksProps = NativeStackScreenProps<StackParamList, 'RecentTracks'>
|
||||
export type MostPlayedArtistsProps = NativeStackScreenProps<StackParamList, 'MostPlayedArtists'>
|
||||
export type MostPlayedTracksProps = NativeStackScreenProps<StackParamList, 'MostPlayedTracks'>
|
||||
export type UserPlaylistsProps = NativeStackScreenProps<StackParamList, 'UserPlaylists'>
|
||||
|
||||
export type DiscoverProps = NativeStackScreenProps<StackParamList, 'Discover'>
|
||||
export type RecentlyAddedProps = NativeStackScreenProps<StackParamList, 'RecentlyAdded'>
|
||||
export type PublicPlaylistsProps = NativeStackScreenProps<StackParamList, 'PublicPlaylists'>
|
||||
export type SuggestedArtistsProps = NativeStackScreenProps<StackParamList, 'SuggestedArtists'>
|
||||
|
||||
export type HomeArtistProps = NativeStackScreenProps<StackParamList, 'Artist'>
|
||||
export type ArtistAlbumsProps = NativeStackScreenProps<StackParamList, 'ArtistAlbums'>
|
||||
export type ArtistEpsProps = NativeStackScreenProps<StackParamList, 'ArtistEps'>
|
||||
export type ArtistFeaturedOnProps = NativeStackScreenProps<StackParamList, 'ArtistFeaturedOn'>
|
||||
|
||||
export type AlbumProps = NativeStackScreenProps<StackParamList, 'Album'>
|
||||
|
||||
export type HomePlaylistProps = NativeStackScreenProps<StackParamList, 'Playlist'>
|
||||
|
||||
export type QueueProps = NativeStackScreenProps<StackParamList, 'Queue'>
|
||||
|
||||
export type LibraryProps = NativeStackScreenProps<StackParamList, 'LibraryScreen'>
|
||||
export type TracksProps = NativeStackScreenProps<StackParamList, 'Tracks'>
|
||||
|
||||
export type ArtistsProps = {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<
|
||||
BaseItemDto[] | (string | number | BaseItemDto)[],
|
||||
Error
|
||||
>
|
||||
showAlphabeticalSelector: boolean
|
||||
artistPageParams?: RefObject<Set<string>>
|
||||
}
|
||||
export type AlbumsProps = {
|
||||
albums: (string | number | BaseItemDto)[] | undefined
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
fetchNextPage: (options?: FetchNextPageOptions | undefined) => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
showAlphabeticalSelector: boolean
|
||||
}
|
||||
export type GenresProps = {
|
||||
genres: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
fetchNextPage: (options?: FetchNextPageOptions | undefined) => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
}
|
||||
export type PlaylistsProps = {
|
||||
canEdit?: boolean | undefined
|
||||
playlists: BaseItemDto[] | undefined
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
refetch: () => void
|
||||
fetchNextPage: (options?: FetchNextPageOptions | undefined) => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
}
|
||||
|
||||
export type DeletePlaylistProps = NativeStackScreenProps<StackParamList, 'DeletePlaylist'>
|
||||
export type DetailsProps = NativeStackScreenProps<StackParamList, 'Details'>
|
||||
|
||||
export type AccountDetailsProps = NativeStackScreenProps<StackParamList, 'Account'>
|
||||
export type ServerDetailsProps = NativeStackScreenProps<StackParamList, 'Server'>
|
||||
export type PlaybackDetailsProps = NativeStackScreenProps<StackParamList, 'Playback'>
|
||||
export type LabsProps = NativeStackScreenProps<StackParamList, 'Labs'>
|
||||
|
||||
export type InstantMixProps = NativeStackScreenProps<StackParamList, 'InstantMix'>
|
||||
|
||||
export type ContextProps = NativeStackScreenProps<StackParamList, 'Context'>
|
||||
@@ -116,4 +116,6 @@ export enum QueryKeys {
|
||||
* Query representing the fetching of suggested artists in an infinite query
|
||||
*/
|
||||
InfiniteSuggestedArtists = 'InfiniteSuggestedArtists',
|
||||
Album = 'Album',
|
||||
TrackArtists = 'TrackArtists',
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ export const useUpdateOptions = async (isFavorite: boolean) => {
|
||||
progressUpdateEventInterval: 1,
|
||||
capabilities: CAPABILITIES,
|
||||
notificationCapabilities: CAPABILITIES,
|
||||
compactCapabilities: CAPABILITIES,
|
||||
ratingType: RatingType.Heart,
|
||||
likeOptions: {
|
||||
isActive: isFavorite,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useJellifyContext } from '..'
|
||||
interface HomeContext {
|
||||
refreshing: boolean
|
||||
onRefresh: () => void
|
||||
recentTracks: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
recentTracks: BaseItemDto[] | undefined
|
||||
|
||||
fetchNextRecentTracks: () => void
|
||||
hasNextRecentTracks: boolean
|
||||
@@ -23,7 +23,7 @@ interface HomeContext {
|
||||
fetchNextFrequentlyPlayed: () => void
|
||||
hasNextFrequentlyPlayed: boolean
|
||||
|
||||
frequentlyPlayed: InfiniteData<BaseItemDto[], unknown> | undefined
|
||||
frequentlyPlayed: BaseItemDto[] | undefined
|
||||
|
||||
isFetchingRecentTracks: boolean
|
||||
isFetchingFrequentlyPlayed: boolean
|
||||
@@ -49,6 +49,7 @@ const HomeContextInitializer = () => {
|
||||
queryKey: [QueryKeys.RecentlyPlayed, library?.musicLibraryId],
|
||||
queryFn: ({ pageParam }) => fetchRecentlyPlayed(api, user, library, pageParam),
|
||||
initialPageParam: 0,
|
||||
select: (data) => data.pages.flatMap((page) => page),
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
|
||||
console.debug('Getting next page for recent tracks')
|
||||
return lastPage.length === QueryConfig.limits.recents ? lastPageParam + 1 : undefined
|
||||
@@ -63,7 +64,7 @@ const HomeContextInitializer = () => {
|
||||
console.debug('Getting next page for recent artists')
|
||||
return lastPage.length > 0 ? lastPageParam + 1 : undefined
|
||||
},
|
||||
enabled: !!recentTracks && recentTracks.pages.length > 0 && !isPendingRecentTracks,
|
||||
enabled: !!recentTracks && recentTracks.length > 0 && !isPendingRecentTracks,
|
||||
})
|
||||
|
||||
const {
|
||||
@@ -77,6 +78,7 @@ const HomeContextInitializer = () => {
|
||||
} = useInfiniteQuery({
|
||||
queryKey: [QueryKeys.FrequentlyPlayed, library?.musicLibraryId],
|
||||
queryFn: ({ pageParam }) => fetchFrequentlyPlayed(api, library, pageParam),
|
||||
select: (data) => data.pages.flatMap((page) => page),
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => {
|
||||
console.debug('Getting next page for frequently played')
|
||||
@@ -93,8 +95,7 @@ const HomeContextInitializer = () => {
|
||||
console.debug('Getting next page for frequent artists')
|
||||
return lastPage.length === 100 ? lastPageParam + 1 : undefined
|
||||
},
|
||||
enabled:
|
||||
!!frequentlyPlayed && frequentlyPlayed.pages.length > 0 && !isStaleFrequentlyPlayed,
|
||||
enabled: !!frequentlyPlayed && frequentlyPlayed.length > 0 && !isStaleFrequentlyPlayed,
|
||||
})
|
||||
|
||||
const onRefresh = async () => {
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import React, { createContext, ReactNode, useContext, useEffect, useState, useMemo } from 'react'
|
||||
import { JellifyDownload, JellifyDownloadProgress } from '../../types/JellifyDownload'
|
||||
import {
|
||||
useMutation,
|
||||
UseMutationResult,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
UseQueryResult,
|
||||
} from '@tanstack/react-query'
|
||||
import { useMutation, UseMutationResult, useQuery } from '@tanstack/react-query'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
import { deleteAudio, getAudioCache, saveAudio } from '../../components/Network/offlineModeUtils'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { networkStatusTypes } from '../../components/Network/internetConnectionWatcher'
|
||||
import DownloadProgress from '../../types/DownloadProgress'
|
||||
import { useJellifyContext } from '..'
|
||||
import { useSettingsContext } from '../Settings'
|
||||
import { useDownloadQualityContext, useStreamingQualityContext } from '../Settings'
|
||||
import { isUndefined } from 'lodash'
|
||||
import RNFS from 'react-native-fs'
|
||||
import { JellifyStorage } from './types'
|
||||
@@ -39,7 +32,8 @@ interface NetworkContext {
|
||||
const MAX_CONCURRENT_DOWNLOADS = 1
|
||||
const NetworkContextInitializer = () => {
|
||||
const { api, sessionId } = useJellifyContext()
|
||||
const { downloadQuality, streamingQuality } = useSettingsContext()
|
||||
const downloadQuality = useDownloadQualityContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
|
||||
const [downloadProgress, setDownloadProgress] = useState<JellifyDownloadProgress>({})
|
||||
const [networkStatus, setNetworkStatus] = useState<networkStatusTypes | null>(null)
|
||||
|
||||
@@ -31,7 +31,7 @@ import { PlaystateApi } from '@jellyfin/sdk/lib/generated-client/api/playstate-a
|
||||
import { networkStatusTypes } from '../../components/Network/internetConnectionWatcher'
|
||||
import { useJellifyContext } from '..'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { useSettingsContext } from '../Settings'
|
||||
import { useAutoDownloadContext } from '../Settings'
|
||||
import {
|
||||
getTracksToPreload,
|
||||
shouldStartPrefetching,
|
||||
@@ -448,7 +448,7 @@ const PlayerContextInitializer = () => {
|
||||
const { state: playbackState } = usePlaybackState()
|
||||
const { useDownload, useDownloadMultiple, downloadedTracks, networkStatus } =
|
||||
useNetworkContext()
|
||||
const { autoDownload } = useSettingsContext()
|
||||
const autoDownload = useAutoDownloadContext()
|
||||
const prefetchedTrackIds = useRef<Set<string>>(new Set())
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,9 +45,9 @@ export interface QueueMutation {
|
||||
*/
|
||||
export interface AddToQueueMutation {
|
||||
/**
|
||||
* The track to add to the queue.
|
||||
* The tracks to add to the queue.
|
||||
*/
|
||||
track: BaseItemDto
|
||||
tracks: BaseItemDto[]
|
||||
/**
|
||||
* The type of queuing to use, dictates the placement of the track in the queue,
|
||||
* be it playing next, or playing in the queue later
|
||||
|
||||
@@ -9,7 +9,7 @@ import JellifyTrack from '../../types/JellifyTrack'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { mapDtoToTrack } from '../../utils/mappings'
|
||||
import { useNetworkContext } from '../Network'
|
||||
import { useSettingsContext } from '../Settings'
|
||||
import { useDownloadQualityContext, useStreamingQualityContext } from '../Settings'
|
||||
import { QueuingType } from '../../enums/queuing-type'
|
||||
import TrackPlayer, { Event, Track, useTrackPlayerEvents } from 'react-native-track-player'
|
||||
import { findPlayQueueIndexStart } from './utils'
|
||||
@@ -22,7 +22,7 @@ import { SKIP_TO_PREVIOUS_THRESHOLD } from '../../player/config'
|
||||
import { isUndefined } from 'lodash'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useJellifyContext } from '..'
|
||||
import { networkStatusTypes } from '@/src/components/Network/internetConnectionWatcher'
|
||||
import { networkStatusTypes } from '../../components/Network/internetConnectionWatcher'
|
||||
import { ensureUpcomingTracksInQueue } from '../../player/helpers/gapless'
|
||||
|
||||
/**
|
||||
@@ -166,7 +166,8 @@ const QueueContextInitailizer = () => {
|
||||
//#region Context
|
||||
const { api, sessionId } = useJellifyContext()
|
||||
const { downloadedTracks, networkStatus } = useNetworkContext()
|
||||
const { downloadQuality, streamingQuality } = useSettingsContext()
|
||||
const downloadQuality = useDownloadQualityContext()
|
||||
const streamingQuality = useStreamingQualityContext()
|
||||
|
||||
//#endregion Context
|
||||
|
||||
@@ -359,36 +360,38 @@ const QueueContextInitailizer = () => {
|
||||
*
|
||||
* @param item The track to play next
|
||||
*/
|
||||
const playNextInQueue = async (item: BaseItemDto) => {
|
||||
const playNextInQueue = async (items: BaseItemDto[]) => {
|
||||
console.debug(`Playing item next in queue`)
|
||||
|
||||
const playNextTrack = mapDtoToTrack(
|
||||
api!,
|
||||
sessionId,
|
||||
item,
|
||||
downloadedTracks ?? [],
|
||||
QueuingType.PlayingNext,
|
||||
downloadQuality,
|
||||
streamingQuality,
|
||||
const tracksToPlayNext = items.map((item) =>
|
||||
mapDtoToTrack(
|
||||
api!,
|
||||
sessionId,
|
||||
item,
|
||||
downloadedTracks ?? [],
|
||||
QueuingType.PlayingNext,
|
||||
downloadQuality,
|
||||
streamingQuality,
|
||||
),
|
||||
)
|
||||
|
||||
// Update app state first to prevent race conditions
|
||||
const newQueue = [
|
||||
...playQueue.slice(0, currentIndex + 1),
|
||||
playNextTrack,
|
||||
...tracksToPlayNext,
|
||||
...playQueue.slice(currentIndex + 1),
|
||||
]
|
||||
setPlayQueue(newQueue)
|
||||
|
||||
// Then update RNTP
|
||||
await TrackPlayer.add([playNextTrack], currentIndex + 1)
|
||||
await TrackPlayer.add(tracksToPlayNext, currentIndex + 1)
|
||||
|
||||
const nowPlaying = playQueue[currentIndex]
|
||||
|
||||
// Add to the state unshuffled queue, using the currently playing track as the index
|
||||
setUnshuffledQueue([
|
||||
...unshuffledQueue.slice(0, unshuffledQueue.indexOf(nowPlaying) + 1),
|
||||
playNextTrack,
|
||||
...tracksToPlayNext,
|
||||
...unshuffledQueue.slice(unshuffledQueue.indexOf(nowPlaying) + 1),
|
||||
])
|
||||
|
||||
@@ -492,10 +495,10 @@ const QueueContextInitailizer = () => {
|
||||
|
||||
//#region Hooks
|
||||
const useAddToQueue = useMutation({
|
||||
mutationFn: ({ track, queuingType }: AddToQueueMutation) => {
|
||||
mutationFn: ({ tracks, queuingType }: AddToQueueMutation) => {
|
||||
return queuingType === QueuingType.PlayingNext
|
||||
? playNextInQueue(track)
|
||||
: playInQueue([track])
|
||||
? playNextInQueue(tracks)
|
||||
: playInQueue(tracks)
|
||||
},
|
||||
onSuccess: (data, { queuingType }) => {
|
||||
trigger('notificationSuccess')
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Platform } from 'react-native'
|
||||
import { storage } from '../../constants/storage'
|
||||
import { MMKVStorageKeys } from '../../enums/mmkv-storage-keys'
|
||||
import { createContext, useContext, useEffect, useState, useMemo } from 'react'
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import { createContext, useContextSelector } from 'use-context-selector'
|
||||
|
||||
export type DownloadQuality = 'original' | 'high' | 'medium' | 'low'
|
||||
export type StreamingQuality = 'original' | 'high' | 'medium' | 'low'
|
||||
@@ -156,4 +157,37 @@ export const SettingsProvider = ({ children }: { children: React.ReactNode }) =>
|
||||
return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>
|
||||
}
|
||||
|
||||
export const useSettingsContext = () => useContext(SettingsContext)
|
||||
export const useSendMetricsContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.sendMetrics)
|
||||
export const useSetSendMetricsContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setSendMetrics)
|
||||
|
||||
export const useAutoDownloadContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.autoDownload)
|
||||
export const useSetAutoDownloadContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setAutoDownload)
|
||||
|
||||
export const useDevToolsContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.devTools)
|
||||
export const useSetDevToolsContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setDevTools)
|
||||
|
||||
export const useDownloadQualityContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.downloadQuality)
|
||||
export const useSetDownloadQualityContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setDownloadQuality)
|
||||
|
||||
export const useStreamingQualityContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.streamingQuality)
|
||||
export const useSetStreamingQualityContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setStreamingQuality)
|
||||
|
||||
export const useReducedHapticsContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.reducedHaptics)
|
||||
export const useSetReducedHapticsContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setReducedHaptics)
|
||||
|
||||
export const useThemeSettingContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.theme)
|
||||
export const useSetThemeSettingContext = () =>
|
||||
useContextSelector(SettingsContext, (context) => context.setTheme)
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import { Album } from '../../components/Album'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { AlbumProps } from '../types'
|
||||
import { AlbumProvider } from '../../providers/Album'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
|
||||
export default function AlbumScreen({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList, 'Album'>
|
||||
route: RouteProp<StackParamList, 'Album'>
|
||||
}): React.JSX.Element {
|
||||
export default function AlbumScreen({ route }: AlbumProps): React.JSX.Element {
|
||||
const { album } = route.params
|
||||
|
||||
return (
|
||||
<AlbumProvider album={album}>
|
||||
<Album route={route} navigation={navigation} />
|
||||
<Album />
|
||||
</AlbumProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { BaseStackParamList } from '../types'
|
||||
import { ArtistProvider } from '../../providers/Artist'
|
||||
import ArtistNavigation from '../../components/Artist'
|
||||
|
||||
@@ -8,14 +8,14 @@ export function ArtistScreen({
|
||||
route,
|
||||
navigation,
|
||||
}: {
|
||||
route: RouteProp<StackParamList, 'Artist'>
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
route: RouteProp<BaseStackParamList, 'Artist'>
|
||||
navigation: NativeStackNavigationProp<BaseStackParamList, 'Artist'>
|
||||
}): React.JSX.Element {
|
||||
const { artist } = route.params
|
||||
|
||||
return (
|
||||
<ArtistProvider artist={artist}>
|
||||
<ArtistNavigation navigation={navigation} />
|
||||
<ArtistNavigation />
|
||||
</ArtistProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import ItemContext from '../../components/Context'
|
||||
import { ContextProps } from '../../components/types'
|
||||
import { ContextProps } from '../types'
|
||||
|
||||
export default function ItemContextScreen({ route }: ContextProps): React.JSX.Element {
|
||||
return <ItemContext item={route.params.item} />
|
||||
return (
|
||||
<ItemContext
|
||||
item={route.params.item}
|
||||
isNested={route.params.isNested}
|
||||
navigation={route.params.navigation}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MultipleArtistsProps } from '../../components/types'
|
||||
import MultipleArtists from '../../components/Context/components/multiple-artists'
|
||||
import { MultipleArtistsProps } from '../Player/types'
|
||||
|
||||
export default function MultipleArtistsSheet(props: MultipleArtistsProps): React.JSX.Element {
|
||||
return <MultipleArtists {...props} />
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import ItemDetail from '../../components/Detail/component'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import React from 'react'
|
||||
|
||||
export default function DetailsScreen({
|
||||
route,
|
||||
navigation,
|
||||
}: {
|
||||
route: RouteProp<StackParamList, 'Details'>
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
return (
|
||||
<ItemDetail
|
||||
item={route.params.item}
|
||||
navigation={navigation}
|
||||
isNested={route.params.isNested}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
import Albums from '../../components/Albums/component'
|
||||
import { RecentlyAddedProps } from '../../components/types'
|
||||
import DiscoverStackParamList, { RecentlyAddedProps } from './types'
|
||||
|
||||
export default function RecentlyAdded({
|
||||
route,
|
||||
navigation,
|
||||
}: RecentlyAddedProps): React.JSX.Element {
|
||||
}: {
|
||||
route: RouteProp<DiscoverStackParamList, 'RecentlyAdded'>
|
||||
}): React.JSX.Element {
|
||||
return (
|
||||
<Albums
|
||||
navigation={navigation}
|
||||
albums={route.params.albums}
|
||||
fetchNextPage={route.params.fetchNextPage}
|
||||
hasNextPage={route.params.hasNextPage}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import Artists from '../../components/Artists/component'
|
||||
import { SuggestedArtistsProps } from '../../components/types'
|
||||
import { SuggestedArtistsProps } from './types'
|
||||
|
||||
export default function SuggestedArtists({ navigation, route }: SuggestedArtistsProps) {
|
||||
return (
|
||||
<Artists
|
||||
navigation={navigation}
|
||||
artistsInfiniteQuery={route.params.artistsInfiniteQuery}
|
||||
showAlphabeticalSelector={false}
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import Index from '../../components/Discover/component'
|
||||
import DetailsScreen from '../Detail'
|
||||
import AlbumScreen from '../Album'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import { DiscoverProvider } from '../../providers/Discover'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { useTheme } from 'tamagui'
|
||||
import RecentlyAdded from './albums'
|
||||
import PublicPlaylists from './playlists'
|
||||
import { PlaylistScreen } from '../Playlist'
|
||||
import SuggestedArtists from './artists'
|
||||
import DiscoverStackParamList from './types'
|
||||
|
||||
export const DiscoverStack = createNativeStackNavigator<StackParamList>()
|
||||
export const DiscoverStack = createNativeStackNavigator<DiscoverStackParamList>()
|
||||
|
||||
export function Discover(): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
@@ -90,26 +88,6 @@ export function Discover(): React.JSX.Element {
|
||||
title: 'Suggested Artists',
|
||||
}}
|
||||
/>
|
||||
|
||||
<DiscoverStack.Screen
|
||||
name='InstantMix'
|
||||
component={InstantMix}
|
||||
options={({ route }) => ({
|
||||
title: route.params.item.Name
|
||||
? `${route.params.item.Name} Mix`
|
||||
: 'Instant Mix',
|
||||
})}
|
||||
/>
|
||||
|
||||
<DiscoverStack.Group screenOptions={{ presentation: 'modal' }}>
|
||||
<DiscoverStack.Screen
|
||||
name='Details'
|
||||
component={DetailsScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</DiscoverStack.Group>
|
||||
</DiscoverStack.Navigator>
|
||||
</DiscoverProvider>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Playlists from '../../components/Playlists/component'
|
||||
import { PublicPlaylistsProps } from '../../components/types'
|
||||
import { PublicPlaylistsProps } from './types'
|
||||
|
||||
export default function PublicPlaylists({
|
||||
navigation,
|
||||
@@ -7,7 +7,6 @@ export default function PublicPlaylists({
|
||||
}: PublicPlaylistsProps): React.JSX.Element {
|
||||
return (
|
||||
<Playlists
|
||||
navigation={navigation}
|
||||
playlists={route.params.playlists}
|
||||
fetchNextPage={route.params.fetchNextPage}
|
||||
hasNextPage={route.params.hasNextPage}
|
||||
|
||||
38
src/screens/Discover/types.d.ts
vendored
Normal file
38
src/screens/Discover/types.d.ts
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
import { BaseStackParamList } from '../types'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { UseInfiniteQueryResult } from '@tanstack/react-query'
|
||||
|
||||
type DiscoverStackParamList = BaseStackParamList & {
|
||||
Discover: undefined
|
||||
RecentlyAdded: {
|
||||
albums: BaseItemDto[] | undefined
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
}
|
||||
PublicPlaylists: {
|
||||
playlists: BaseItemDto[] | undefined
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
isFetchingNextPage: boolean
|
||||
refetch: () => void
|
||||
}
|
||||
SuggestedArtists: {
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscoverStackParamList
|
||||
|
||||
export type RecentlyAddedProps = NativeStackScreenProps<DiscoverStackParamList, 'RecentlyAdded'>
|
||||
export type PublicPlaylistsProps = NativeStackScreenProps<DiscoverStackParamList, 'PublicPlaylists'>
|
||||
export type SuggestedArtistsProps = NativeStackScreenProps<
|
||||
DiscoverStackParamList,
|
||||
'SuggestedArtists'
|
||||
>
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import Artists from '../../components/Artists/component'
|
||||
import { MostPlayedArtistsProps, RecentArtistsProps } from '../../components/types'
|
||||
import { MostPlayedArtistsProps, RecentArtistsProps } from './types'
|
||||
import { useHomeContext } from '../../providers/Home'
|
||||
|
||||
export default function HomeArtistsScreen({
|
||||
@@ -12,7 +12,6 @@ export default function HomeArtistsScreen({
|
||||
if (route.name === 'MostPlayedArtists') {
|
||||
return (
|
||||
<Artists
|
||||
navigation={navigation}
|
||||
artistsInfiniteQuery={frequentArtistsInfiniteQuery}
|
||||
showAlphabeticalSelector={false}
|
||||
/>
|
||||
@@ -21,7 +20,6 @@ export default function HomeArtistsScreen({
|
||||
|
||||
return (
|
||||
<Artists
|
||||
navigation={navigation}
|
||||
artistsInfiniteQuery={recentArtistsInfiniteQuery}
|
||||
showAlphabeticalSelector={false}
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import _ from 'lodash'
|
||||
import { HomeProvider } from '../../providers/Home'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { PlaylistScreen } from '../Playlist'
|
||||
import { ProvidedHome } from '../../components/Home'
|
||||
import DetailsScreen from '../Detail'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { useTheme } from 'tamagui'
|
||||
import HomeArtistsScreen from './artists'
|
||||
import HomeTracksScreen from './tracks'
|
||||
import AlbumScreen from '../Album'
|
||||
import HomeStackParamList from './types'
|
||||
import { HomeTabProps } from '../Tabs/types'
|
||||
|
||||
const HomeStack = createNativeStackNavigator<StackParamList>()
|
||||
const HomeStack = createNativeStackNavigator<HomeStackParamList>()
|
||||
|
||||
/**
|
||||
* The main screen for the home tab.
|
||||
@@ -23,13 +22,10 @@ export default function Home(): React.JSX.Element {
|
||||
|
||||
return (
|
||||
<HomeProvider>
|
||||
<HomeStack.Navigator
|
||||
initialRouteName='HomeScreen'
|
||||
screenOptions={{ headerShown: true }}
|
||||
>
|
||||
<HomeStack.Navigator initialRouteName='Home' screenOptions={{ headerShown: true }}>
|
||||
<HomeStack.Group>
|
||||
<HomeStack.Screen
|
||||
name='HomeScreen'
|
||||
name='Home'
|
||||
component={ProvidedHome}
|
||||
options={{
|
||||
title: 'Home',
|
||||
@@ -94,26 +90,6 @@ export default function Home(): React.JSX.Element {
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
||||
<HomeStack.Screen
|
||||
name='InstantMix'
|
||||
component={InstantMix}
|
||||
options={({ route }) => ({
|
||||
title: route.params.item.Name
|
||||
? `${route.params.item.Name} Mix`
|
||||
: 'Instant Mix',
|
||||
})}
|
||||
/>
|
||||
</HomeStack.Group>
|
||||
|
||||
<HomeStack.Group screenOptions={{ presentation: 'modal' }}>
|
||||
<HomeStack.Screen
|
||||
name='Details'
|
||||
component={DetailsScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</HomeStack.Group>
|
||||
</HomeStack.Navigator>
|
||||
</HomeProvider>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Tracks from '../../components/Tracks/component'
|
||||
import { MostPlayedTracksProps, RecentTracksProps } from '../../components/types'
|
||||
import { useHomeContext } from '../../providers/Home'
|
||||
import { MostPlayedTracksProps, RecentTracksProps } from './types'
|
||||
|
||||
export default function HomeTracksScreen({
|
||||
navigation,
|
||||
@@ -18,7 +18,6 @@ export default function HomeTracksScreen({
|
||||
if (route.name === 'MostPlayedTracks') {
|
||||
return (
|
||||
<Tracks
|
||||
navigation={navigation}
|
||||
tracks={frequentlyPlayed}
|
||||
fetchNextPage={fetchNextFrequentlyPlayed}
|
||||
hasNextPage={hasNextFrequentlyPlayed}
|
||||
@@ -29,7 +28,6 @@ export default function HomeTracksScreen({
|
||||
|
||||
return (
|
||||
<Tracks
|
||||
navigation={navigation}
|
||||
tracks={recentTracks}
|
||||
fetchNextPage={fetchNextRecentTracks}
|
||||
hasNextPage={hasNextRecentTracks}
|
||||
|
||||
32
src/screens/Home/types.d.ts
vendored
Normal file
32
src/screens/Home/types.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { BaseStackParamList } from '../types'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { UseInfiniteQueryResult } from '@tanstack/react-query'
|
||||
|
||||
type HomeStackParamList = BaseStackParamList & {
|
||||
RecentArtists: {
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
}
|
||||
MostPlayedArtists: {
|
||||
artistsInfiniteQuery: UseInfiniteQueryResult<BaseItemDto[], Error>
|
||||
}
|
||||
RecentTracks: {
|
||||
tracks: BaseItemDto[] | undefined
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
}
|
||||
MostPlayedTracks: {
|
||||
tracks: BaseItemDto[] | undefined
|
||||
fetchNextPage: () => void
|
||||
hasNextPage: boolean
|
||||
isPending: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export default HomeStackParamList
|
||||
|
||||
export type RecentArtistsProps = NativeStackScreenProps<HomeStackParamList, 'RecentArtists'>
|
||||
export type RecentTracksProps = NativeStackScreenProps<HomeStackParamList, 'RecentTracks'>
|
||||
export type MostPlayedArtistsProps = NativeStackScreenProps<HomeStackParamList, 'MostPlayedArtists'>
|
||||
export type MostPlayedTracksProps = NativeStackScreenProps<HomeStackParamList, 'MostPlayedTracks'>
|
||||
@@ -4,7 +4,7 @@ import React, { useState } from 'react'
|
||||
import { View, XStack } from 'tamagui'
|
||||
import Button from '../../components/Global/helpers/button'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { RootStackParamList } from '../types'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { createPlaylist } from '../../api/mutations/playlists'
|
||||
import { trigger } from 'react-native-haptic-feedback'
|
||||
@@ -13,13 +13,14 @@ import { QueryKeys } from '../../enums/query-keys'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import LibraryStackParamList from './types'
|
||||
|
||||
export default function AddPlaylist({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList, 'AddPlaylist'>
|
||||
navigation: NativeStackNavigationProp<LibraryStackParamList, 'AddPlaylist'>
|
||||
}): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const { api, user } = useJellifyContext()
|
||||
const [name, setName] = useState<string>('')
|
||||
|
||||
const useAddPlaylist = useMutation({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { View, XStack } from 'tamagui'
|
||||
import { DeletePlaylistProps } from '../../components/types'
|
||||
import Button from '../../components/Global/helpers/button'
|
||||
import { H5, Text } from '../../components/Global/helpers/text'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
@@ -10,12 +9,13 @@ import { queryClient } from '../../constants/query-client'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { LibraryDeletePlaylistProps } from './types'
|
||||
// import * as Burnt from 'burnt'
|
||||
|
||||
export default function DeletePlaylist({
|
||||
navigation,
|
||||
route,
|
||||
}: DeletePlaylistProps): React.JSX.Element {
|
||||
}: LibraryDeletePlaylistProps): React.JSX.Element {
|
||||
const { api, user, library } = useJellifyContext()
|
||||
const useDeletePlaylist = useMutation({
|
||||
mutationFn: (playlist: BaseItemDto) => deletePlaylist(api, playlist.Id!),
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import React from 'react'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import Library from '../../components/Library/component'
|
||||
import { PlaylistScreen } from '../Playlist'
|
||||
import DetailsScreen from '../Detail'
|
||||
import AddPlaylist from './add-playlist'
|
||||
import DeletePlaylist from './delete-playlist'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import InstantMix from '../../components/InstantMix/component'
|
||||
import { useTheme } from 'tamagui'
|
||||
import { LibraryProvider } from '../../providers/Library'
|
||||
import { LibrarySortAndFilterProvider } from '../../providers/Library/sorting-filtering'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import AlbumScreen from '../Album'
|
||||
import LibraryStackParamList from './types'
|
||||
|
||||
const Stack = createNativeStackNavigator<StackParamList>()
|
||||
const Stack = createNativeStackNavigator<LibraryStackParamList>()
|
||||
|
||||
export default function LibraryStack(): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
@@ -21,9 +19,9 @@ export default function LibraryStack(): React.JSX.Element {
|
||||
return (
|
||||
<LibrarySortAndFilterProvider>
|
||||
<LibraryProvider>
|
||||
<Stack.Navigator initialRouteName='LibraryScreen'>
|
||||
<Stack.Navigator initialRouteName='Library'>
|
||||
<Stack.Screen
|
||||
name='LibraryScreen'
|
||||
name='Library'
|
||||
component={Library}
|
||||
options={{
|
||||
title: 'Library',
|
||||
@@ -70,26 +68,6 @@ export default function LibraryStack(): React.JSX.Element {
|
||||
})}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name='InstantMix'
|
||||
component={InstantMix}
|
||||
options={({ route }) => ({
|
||||
title: route.params.item.Name
|
||||
? `${route.params.item.Name} Mix`
|
||||
: 'Instant Mix',
|
||||
})}
|
||||
/>
|
||||
|
||||
<Stack.Group screenOptions={{ presentation: 'modal' }}>
|
||||
<Stack.Screen
|
||||
name='Details'
|
||||
component={DetailsScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</Stack.Group>
|
||||
|
||||
<Stack.Group
|
||||
screenOptions={{
|
||||
presentation: 'formSheet',
|
||||
|
||||
22
src/screens/Library/types.d.ts
vendored
Normal file
22
src/screens/Library/types.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { BaseStackParamList } from '../types'
|
||||
|
||||
type LibraryStackParamList = BaseStackParamList & {
|
||||
AddPlaylist: undefined
|
||||
|
||||
DeletePlaylist: {
|
||||
playlist: BaseItemDto
|
||||
}
|
||||
}
|
||||
|
||||
export default LibraryStackParamList
|
||||
|
||||
export type LibraryArtistProps = NativeStackScreenProps<LibraryStackParamList, 'Artist'>
|
||||
export type LibraryAlbumProps = NativeStackScreenProps<LibraryStackParamList, 'Album'>
|
||||
|
||||
export type LibraryAddPlaylistProps = NativeStackScreenProps<LibraryStackParamList, 'AddPlaylist'>
|
||||
export type LibraryDeletePlaylistProps = NativeStackScreenProps<
|
||||
LibraryStackParamList,
|
||||
'DeletePlaylist'
|
||||
>
|
||||
@@ -9,10 +9,10 @@ import Button from '../../components/Global/helpers/button'
|
||||
import { http, https } from '../../components/Login/utils/constants'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { RootStackParamList } from '../types'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
import { useSettingsContext } from '../../providers/Settings'
|
||||
import { useSendMetricsContext, useSetSendMetricsContext } from '../../providers/Settings'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { PublicSystemInfo } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { connectToServer } from '../../api/mutations/login'
|
||||
@@ -22,7 +22,7 @@ import { sleepify } from '../../utils/sleep'
|
||||
export default function ServerAddress({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const [serverAddressContainsProtocol, setServerAddressContainsProtocol] =
|
||||
useState<boolean>(false)
|
||||
@@ -33,7 +33,8 @@ export default function ServerAddress({
|
||||
|
||||
const { server, setServer, signOut } = useJellifyContext()
|
||||
|
||||
const { setSendMetrics, sendMetrics } = useSettingsContext()
|
||||
const sendMetrics = useSendMetricsContext()
|
||||
const setSendMetrics = useSetSendMetricsContext()
|
||||
|
||||
useEffect(() => {
|
||||
setServerAddressContainsProtocol(
|
||||
|
||||
@@ -7,7 +7,7 @@ import { H2, H5, Text } from '../../components/Global/helpers/text'
|
||||
import Button from '../../components/Global/helpers/button'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { JellifyUser } from '../../types/JellifyUser'
|
||||
import { StackParamList } from '../../components/types'
|
||||
import { RootStackParamList } from '../types'
|
||||
import Input from '../../components/Global/helpers/input'
|
||||
import Icon from '../../components/Global/components/icon'
|
||||
import { useJellifyContext } from '../../providers'
|
||||
@@ -18,7 +18,7 @@ import { IS_MAESTRO_BUILD } from '../../configs/config'
|
||||
export default function ServerAuthentication({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
navigation: NativeStackNavigationProp<RootStackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
const [username, setUsername] = useState<string | undefined>(undefined)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user