diff --git a/.github/workflows/publish-ota-update.yml b/.github/workflows/publish-ota-update.yml index 46e31454..68dfe2a2 100644 --- a/.github/workflows/publish-ota-update.yml +++ b/.github/workflows/publish-ota-update.yml @@ -1,8 +1,7 @@ name: Publish Over-the-Air Update on: - push: - branches: - - 'main' + workflow_dispatch: + jobs: publish-ota-update: runs-on: macos-15 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 357ca6cb..3614c397 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1722,6 +1722,35 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga + - react-native-blurhash (2.1.1): + - 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-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga - react-native-carplay (2.4.1-beta.0): - React - react-native-config (1.5.5): @@ -2689,7 +2718,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNScreens (4.11.1): + - RNScreens (4.12.0): - boost - DoubleConversion - fast_float @@ -2717,10 +2746,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.11.1) + - RNScreens/common (= 4.12.0) - SocketRocket - Yoga - - RNScreens/common (4.11.1): + - RNScreens/common (4.12.0): - boost - DoubleConversion - fast_float @@ -2867,6 +2896,7 @@ DEPENDENCIES: - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - react-native-background-actions (from `../node_modules/react-native-background-actions`) - react-native-blob-util (from `../node_modules/react-native-blob-util`) + - react-native-blurhash (from `../node_modules/react-native-blurhash`) - react-native-carplay (from `../node_modules/react-native-carplay`) - react-native-config (from `../node_modules/react-native-config`) - react-native-mmkv (from `../node_modules/react-native-mmkv`) @@ -3023,6 +3053,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-background-actions" react-native-blob-util: :path: "../node_modules/react-native-blob-util" + react-native-blurhash: + :path: "../node_modules/react-native-blurhash" react-native-carplay: :path: "../node_modules/react-native-carplay" react-native-config: @@ -3176,6 +3208,7 @@ SPEC CHECKSUMS: React-microtasksnativemodule: d9499269ad1f484ae71319bac1d9231447f2094e react-native-background-actions: 48e6bad9e2a47e3b04858634c5a05ea11062f680 react-native-blob-util: a9a07801b63e97d1bbdcf4eba3b98ff16c249bd5 + react-native-blurhash: 7aa21188beaed1eef0d9261e98befa56449eb65a react-native-carplay: 8f388f6f73e5e0f73ed154ad8794371343ee20c0 react-native-config: 644074ab88db883fcfaa584f03520ec29589d7df react-native-mmkv: 7fb4729ad5cb787a4394e6c4bd48e4b8ec30f25c @@ -3225,7 +3258,7 @@ SPEC CHECKSUMS: RNGestureHandler: 5e1a1605659c22098719fc2e8aee453fe728f52e RNReactNativeHapticFeedback: 8eb91a6f48567d02ec8026e515102e18c41030cf RNReanimated: bc1ddb7a5352648bcf0d592256069833bf935a46 - RNScreens: ee2abe7e0c548eed14e92742e81ed991165c56aa + RNScreens: ab490a252dd536fca261c72ab7e5538e656dcb2b RNSentry: 2b690575f638f588e51b6817e5f77c8ab62de2cf RNVectorIcons: ef9b4b0b786053ebdd63ee2972f48de9633ba166 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d diff --git a/package.json b/package.json index 6a8e524c..5d8dc98d 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "react-native": "0.80.1", "react-native-background-actions": "^4.0.1", "react-native-blob-util": "^0.22.2", + "react-native-blurhash": "^2.1.1", "react-native-carplay": "^2.4.1-beta.0", "react-native-config": "^1.5.5", "react-native-device-info": "^14.0.4", @@ -78,7 +79,7 @@ "react-native-pager-view": "^6.8.1", "react-native-reanimated": "^3.18.0", "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "^4.11.1", + "react-native-screens": "^4.12.0", "react-native-swipeable-item": "^2.0.9", "react-native-text-ticker": "^1.14.0", "react-native-toast-message": "^2.3.3", diff --git a/src/api/queries/artist.ts b/src/api/queries/artist.ts index 52f5c186..f41ccfd0 100644 --- a/src/api/queries/artist.ts +++ b/src/api/queries/artist.ts @@ -6,9 +6,10 @@ import { ItemSortBy, SortOrder, } from '@jellyfin/sdk/lib/generated-client/models' -import { getItemsApi } from '@jellyfin/sdk/lib/utils/api' -import { fetchItems } from './item' +import { getArtistsApi, getItemsApi } from '@jellyfin/sdk/lib/utils/api' import { JellifyUser } from '../../types/JellifyUser' +import QueryConfig from './query.config' +import { alphabet } from '../../providers/Library' export function fetchArtists( api: Api | undefined, user: JellifyUser | undefined, @@ -19,16 +20,32 @@ export function fetchArtists( sortOrder: SortOrder[] = [SortOrder.Ascending], ): Promise<{ title: string | number; data: BaseItemDto[] }> { console.debug('Fetching artists', page) - return fetchItems( - api, - user, - library, - [BaseItemKind.MusicArtist], - page, - sortBy, - sortOrder, - isFavorite, - ) + + return new Promise((resolve, reject) => { + if (!api) return reject('No API instance provided') + + getArtistsApi(api) + .getAlbumArtists({ + parentId: library?.musicLibraryId, + userId: user?.id, + enableUserData: true, + sortBy: sortBy, + sortOrder: sortOrder, + startIndex: typeof page === 'number' ? page * QueryConfig.limits.library : 0, + limit: QueryConfig.limits.library, + isFavorite: isFavorite, + nameStartsWith: typeof page === 'string' && page !== alphabet[0] ? page : undefined, + nameLessThan: typeof page === 'string' && page === alphabet[0] ? 'A' : undefined, + }) + .then((response) => { + return response.data.Items + ? resolve({ title: page, data: response.data.Items }) + : resolve({ title: page, data: [] }) + }) + .catch((error) => { + reject(error) + }) + }) } /** diff --git a/src/components/Artists/component.tsx b/src/components/Artists/component.tsx index a2ef678c..135f0beb 100644 --- a/src/components/Artists/component.tsx +++ b/src/components/Artists/component.tsx @@ -66,6 +66,7 @@ export default function Artists({ useEffect(() => { artistsRef.current = artists ?? [] + console.debug(`artists: ${JSON.stringify(artists)}`) }, [artists]) return ( diff --git a/src/components/Global/components/image.tsx b/src/components/Global/components/image.tsx index 8aa648ab..4aa02afa 100644 --- a/src/components/Global/components/image.tsx +++ b/src/components/Global/components/image.tsx @@ -27,11 +27,12 @@ export default function ItemImage({ const theme = useTheme() const imageUrl = - (item.AlbumId && getImageApi(api!).getItemImageUrlById(item.AlbumId)) || - (item.Id && getImageApi(api!).getItemImageUrlById(item.Id)) || - '' + api && + ((item.AlbumId && getImageApi(api).getItemImageUrlById(item.AlbumId)) || + (item.Id && getImageApi(api).getItemImageUrlById(item.Id)) || + '') - return ( + return api && imageUrl ? ( + ) : ( + <> ) } diff --git a/src/components/Global/components/item-row.tsx b/src/components/Global/components/item-row.tsx index de36b10c..2020b243 100644 --- a/src/components/Global/components/item-row.tsx +++ b/src/components/Global/components/item-row.tsx @@ -119,28 +119,25 @@ export default function ItemRow({ {item.Name ?? ''} {(item.Type === 'Audio' || item.Type === 'MusicAlbum') && ( - + {item.AlbumArtist ?? 'Untitled Artist'} )} - - {item.Type === 'MusicAlbum' && {item.RunTimeTicks}} {/* Runtime ticks for Songs */} - {item.Type === 'Audio' ? ( + {['Audio', 'MusicAlbum'].includes(item.Type ?? '') ? ( {item.RunTimeTicks} + ) : ['Playlist'].includes(item.Type ?? '') ? ( + {`${item.ChildCount ?? 0} ${item.ChildCount === 1 ? 'Track' : 'Tracks'}`} ) : null} {item.Type === 'Audio' || item.Type === 'MusicAlbum' ? ( diff --git a/src/components/Player/components/blurred-background.tsx b/src/components/Player/components/blurred-background.tsx index 020b340e..3a4cfb6d 100644 --- a/src/components/Player/components/blurred-background.tsx +++ b/src/components/Player/components/blurred-background.tsx @@ -1,11 +1,11 @@ import React from 'react' import { usePlayerContext } from '../../../providers/Player' -import { BlurView } from 'blur-react-native' -import ItemImage from '../../Global/components/image' import { getToken, useTheme, View, YStack, ZStack } from 'tamagui' import { useColorScheme } from 'react-native' import LinearGradient from 'react-native-linear-gradient' - +import { getPrimaryBlurhashFromDto } from '../../../utils/blurhash' +import { BlurView } from 'blur-react-native' +import ItemImage from '../../Global/components/image' export default function BlurredBackground({ width, height, @@ -18,24 +18,8 @@ export default function BlurredBackground({ const isDarkMode = useColorScheme() === 'dark' return ( - - + + diff --git a/src/components/Player/components/header.tsx b/src/components/Player/components/header.tsx index e5153586..a47e7878 100644 --- a/src/components/Player/components/header.tsx +++ b/src/components/Player/components/header.tsx @@ -1,16 +1,13 @@ import { useJellifyContext } from '../../../providers' import { usePlayerContext } from '../../../providers/Player' import { useQueueContext } from '../../../providers/Player/queue' -import { getToken, useWindowDimensions, XStack, YStack, Spacer, useTheme, View } from 'tamagui' -import { getImageApi } from '@jellyfin/sdk/lib/utils/api' -import FastImage from 'react-native-fast-image' +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 React from 'react' import { State } from 'react-native-track-player' -import { Platform } from 'react-native' import ItemImage from '../../Global/components/image' export default function PlayerHeader({ @@ -35,7 +32,7 @@ export default function PlayerHeader({ { navigation.goBack() }} diff --git a/src/components/Playlists/component.tsx b/src/components/Playlists/component.tsx index 6b03786e..e1578f40 100644 --- a/src/components/Playlists/component.tsx +++ b/src/components/Playlists/component.tsx @@ -1,7 +1,7 @@ import { FlatList, RefreshControl } from 'react-native-gesture-handler' import { ItemCard } from '../Global/components/item-card' import Icon from '../Global/components/icon' -import { getToken, getTokens } from 'tamagui' +import { getToken, getTokens, Separator } from 'tamagui' import { fetchFavoritePlaylists } from '../../api/queries/favorites' import { QueryKeys } from '../../enums/query-keys' import { useQuery } from '@tanstack/react-query' @@ -10,6 +10,8 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack' import { StackParamList } from '../types' import { useDisplayContext } from '../../providers/Display/display-provider' import { useLayoutEffect } from 'react' +import { FlashList } from '@shopify/flash-list' +import ItemRow from '../Global/components/item-row' export default function Playlists({ navigation, @@ -42,31 +44,22 @@ export default function Playlists({ queryFn: () => fetchFavoritePlaylists(api, user, library), }) - const { numberOfColumns } = useDisplayContext() - return ( - } + ItemSeparatorComponent={() => } renderItem={({ index, item: playlist }) => ( - { navigation.navigate('Playlist', { playlist }) }} - size={'$11'} - squared + navigation={navigation} + queueName={playlist.Name ?? 'Untitled Playlist'} /> )} - removeClippedSubviews /> ) } diff --git a/src/screens/index.tsx b/src/screens/index.tsx index 03367002..e95fa8fa 100644 --- a/src/screens/index.tsx +++ b/src/screens/index.tsx @@ -37,7 +37,6 @@ export default function Root(): React.JSX.Element { options={{ headerShown: false, presentation: 'modal', - sheetAllowedDetents: Platform.OS === 'ios' ? 'fitToContents' : [1.0], }} /> - ) - }, }} tabBar={(props) => ( <> {nowPlaying && ( /* Hide miniplayer if the queue is empty */ - <> - - + )} diff --git a/yarn.lock b/yarn.lock index 42e5be20..b77052da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8376,6 +8376,11 @@ react-native-blob-util@^0.22.2: base-64 "0.1.0" glob "^10.3.10" +react-native-blurhash@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-native-blurhash/-/react-native-blurhash-2.1.1.tgz#c8079881422664691b387e43b020b846dfd196e6" + integrity sha512-b1aA5Tn31pPbqmaWnhJv7zSuN6o9M1t4yHciPunfP89LDkH2dvDIynvkE00Hen4Vmt6SnyXViSYH34MyvTvRiA== + react-native-carplay@^2.4.1-beta.0: version "2.4.1-beta.0" resolved "https://registry.yarnpkg.com/react-native-carplay/-/react-native-carplay-2.4.1-beta.0.tgz#985c44e4eae622e7d2cf17c5b996a0182dbf5a52" @@ -8496,10 +8501,10 @@ react-native-safe-area-context@^5.5.2: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.5.2.tgz#a3c0e99385a45e31ef24d24358aa622cc9e1a069" integrity sha512-t4YVbHa9uAGf+pHMabGrb0uHrD5ogAusSu842oikJ3YKXcYp6iB4PTGl0EZNkUIR3pCnw/CXKn42OCfhsS0JIw== -react-native-screens@^4.11.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.11.1.tgz#7d0f3d313d8ddc1e55437c5e038f15f8805dc991" - integrity sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw== +react-native-screens@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.12.0.tgz#9ee3da954dbbfcaa6fd69a21e736a2265b73395b" + integrity sha512-T2KL6RcDSYDRZswh9glRe600Hvaeq240U21eaqv0uxCNmJz05UeFc4YGQgbFPI8XsakPKx3HjNonItxElFy+QA== dependencies: react-freeze "^1.0.0" react-native-is-edge-to-edge "^1.1.7"