Re-enable New Architecture on Android, Remove Blurhash Deps, Improve Album Layout on Larger Viewports (#318)

Re-enables new architecture on Android by switching to a different fork of React Native Track Player

Removes dependency on react-native-blurhash - we aren't using it as our image component can handle this for us

Improves album layout on artist screen by adjusting flex of album details and album artwork
This commit is contained in:
Violet Caulfield
2025-05-01 14:53:54 -05:00
committed by GitHub
parent 48ac98784b
commit e78b9d2114
16 changed files with 183 additions and 1300 deletions

View File

@@ -17,9 +17,9 @@ jobs:
with:
node-version: 20
- name: 🤖 Run yarn init-android
run: yarn init-android
- name: 🍎 Run yarn init-ios:new-arch
run: yarn init-ios:new-arch
- name: Version Up
run: yarn react-native bump-version --type patch
@@ -33,9 +33,6 @@ jobs:
- name: 🚀 Run Android fastlane build
run: yarn fastlane:android:build
- name: 🍎 Run yarn init-ios:new-arch
run: yarn init-ios:new-arch
- name: 🚀 Run iOS fastlane build and publish to TestFlight
run: yarn fastlane:ios:beta
env:

View File

@@ -32,7 +32,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.

View File

@@ -5,7 +5,6 @@ module.exports = {
setupFilesAfterEnv: [
'./jest/setup.ts',
'./jest/setup-carplay.ts',
'./jest/setup-blurhash.ts',
'./jest/setup-device-info.js', // JS to prevent Typescript implicit any warning
'./jest/setup-reanimated.ts',
'./jest/setup-rnfs.ts',

View File

@@ -1,5 +0,0 @@
jest.mock('react-native-blurhash', () => {
return {
Blurhash: () => null,
}
})

View File

@@ -1,121 +1,120 @@
{
"name": "jellify",
"version": "0.11.19",
"private": true,
"scripts": {
"init-android": "yarn && yarn add react-native-mmkv@2.12.0",
"init-ios": "echo 'Please run `yarn init-ios:new-arch` to enable the new architecture'",
"init-ios:new-arch": "yarn && yarn add react-native-mmkv@3.2.0 && yarn pod:install:new-arch",
"reinstall": "rm -rf ./node_modules && yarn install",
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"tsc": "tsc",
"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'",
"pod:install:new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
"pod:clean": "cd ios && pod deintegrate",
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
"prepare": "husky",
"format:check": "prettier --check .",
"format": "prettier --write .",
"postinstall": "patch-package"
},
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^18.0.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/material-top-tabs": "^7.2.10",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@tamagui/config": "^1.126.4",
"@tanstack/query-sync-storage-persister": "^5.74.6",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-persist-client": "^5.74.6",
"@testing-library/react-native": "^13.2.0",
"axios": "^1.8.4",
"bundle": "^2.1.0",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"lodash": "^4.17.21",
"react": "19.0.0",
"react-freeze": "^1.0.4",
"react-native": "0.79.1",
"react-native-background-actions": "^4.0.1",
"react-native-blurhash": "^2.1.1",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.2",
"react-native-fast-image": "^8.6.3",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.25.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "3.2.0",
"react-native-pager-view": "^6.7.1",
"react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "^4.11.0-beta.2",
"react-native-swipeable-item": "^2.0.9",
"react-native-text-ticker": "^1.14.0",
"react-native-toast-message": "^2.3.0",
"react-native-track-player": "4.1.1",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1",
"tamagui": "^1.126.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli-platform-android": "18.0.0",
"@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.1",
"@react-native/eslint-config": "0.79.1",
"@react-native/metro-config": "0.79.1",
"@react-native/typescript-config": "0.79.1",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10",
"@types/react": "^19.1.2",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "19.0.0",
"babel-plugin-module-resolver": "^5.0.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"husky": "^9.1.7",
"jest": "^29.6.3",
"jscodeshift": "^0.15.2",
"lint-staged": "^15.5.0",
"patch-package": "8.0.0",
"prettier": "^2.8.8",
"react-dom": "^19.1.0",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "19.0.0",
"typescript": "5.8.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
]
},
"engines": {
"node": ">=18"
}
"name": "jellify",
"version": "0.11.19",
"private": true,
"scripts": {
"init-android": "yarn",
"init-ios": "yarn init-ios:new-arch",
"init-ios:new-arch": "yarn && yarn pod:install:new-arch",
"reinstall": "rm -rf ./node_modules && yarn install",
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"tsc": "tsc",
"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'",
"pod:install:new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
"pod:clean": "cd ios && pod deintegrate",
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
"prepare": "husky",
"format:check": "prettier --check .",
"format": "prettier --write .",
"postinstall": "patch-package"
},
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^18.0.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/material-top-tabs": "^7.2.10",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@tamagui/config": "^1.126.4",
"@tanstack/query-sync-storage-persister": "^5.74.6",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-persist-client": "^5.74.6",
"@testing-library/react-native": "^13.2.0",
"axios": "^1.8.4",
"bundle": "^2.1.0",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"lodash": "^4.17.21",
"react": "19.0.0",
"react-freeze": "^1.0.4",
"react-native": "0.79.1",
"react-native-background-actions": "^4.0.1",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.2",
"react-native-fast-image": "^8.6.3",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.25.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "3.2.0",
"react-native-pager-view": "^6.7.1",
"react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "^4.11.0-beta.2",
"react-native-swipeable-item": "^2.0.9",
"react-native-text-ticker": "^1.14.0",
"react-native-toast-message": "^2.3.0",
"react-native-track-player": "git+https://github.com/riteshshukla04/react-native-track-player#APM",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1",
"tamagui": "^1.126.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli-platform-android": "18.0.0",
"@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.1",
"@react-native/eslint-config": "0.79.1",
"@react-native/metro-config": "0.79.1",
"@react-native/typescript-config": "0.79.1",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10",
"@types/react": "^19.1.2",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "19.0.0",
"babel-plugin-module-resolver": "^5.0.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"husky": "^9.1.7",
"jest": "^29.6.3",
"jscodeshift": "^0.15.2",
"lint-staged": "^15.5.0",
"patch-package": "8.0.0",
"prettier": "^2.8.8",
"react-dom": "^19.1.0",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "19.0.0",
"typescript": "5.8.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
]
},
"engines": {
"node": ">=18"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,12 @@ export async function fetchRecentlyAdded(
})
}
/**
* Fetches recently played tracks for a user from the Jellyfin server.
* @param limit The number of items to fetch. Defaults to 50
* @param offset The offset of the items to fetch.
* @returns The recently played items.
*/
export async function fetchRecentlyPlayed(
limit: number = QueryConfig.limits.recents,
offset?: number | undefined,
@@ -56,6 +62,13 @@ export async function fetchRecentlyPlayed(
})
}
/**
* Fetches recently played artists for a user from the Jellyfin server,
* referencing the recently played tracks.
* @param limit The number of items to fetch. Defaults to 50
* @param offset The offset of the items to fetch.
* @returns The recently played artists.
*/
export function fetchRecentlyPlayedArtists(
limit: number = QueryConfig.limits.recents,
offset?: number | undefined,

View File

@@ -82,7 +82,7 @@ function AlbumTrackListHeader(
return (
<YStack marginTop={'$4'} alignItems='center'>
<XStack justifyContent='center'>
<ItemImage item={album} width={getToken('$20')} height={getToken('$20')} />
<ItemImage item={album} width={'$20'} height={'$20'} />
<Spacer />
@@ -91,7 +91,8 @@ function AlbumTrackListHeader(
lineBreakStrategyIOS='standard'
textAlign='center'
numberOfLines={5}
maxWidth={width / 2}
minWidth={width / 2.25}
maxWidth={width / 2.25}
>
{album.Name ?? 'Untitled Album'}
</H5>

View File

@@ -1,108 +0,0 @@
import { BaseItemDto, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
import { Blurhash } from 'react-native-blurhash'
import { Square, View } from 'tamagui'
import { isEmpty } from 'lodash'
import { Image } from 'react-native'
import { QueryKeys } from '../../../enums/query-keys'
import { useQuery } from '@tanstack/react-query'
import { fetchItemImage } from '../../../api/queries/images'
interface BlurhashLoadingProps {
item: BaseItemDto
width: number
height?: number
type?: ImageType
borderRadius?: number | undefined
}
/**
* @deprecated
*
* Please use the `Image` component from
* the `expo-image` module instead, as that is more performant
*
* A React component that will render a Blurhash
* string as an image while loading the full image
* from the server
*
* Image Query is stale after 30 minutes and collected
* after an hour to keep the cache size down and the
* app performant
*
* TODO: Keep images in offline mode
*
* @param param0
* @returns
*/
export default function BlurhashedImage({
item,
width,
height,
type,
borderRadius,
}: BlurhashLoadingProps): React.JSX.Element {
const { data: image, isSuccess } = useQuery({
queryKey: [
QueryKeys.ItemImage,
item.AlbumId ? item.AlbumId : item.Id!,
type ?? ImageType.Primary,
Math.ceil(width / 100) * 100, // Images are fetched at a higher, generic resolution
Math.ceil(height ?? width / 100) * 100, // So these keys need to match
],
queryFn: () =>
fetchItemImage(
item.AlbumId ? item.AlbumId : item.Id!,
type ?? ImageType.Primary,
width,
height ?? width,
),
staleTime: 1000 * 60 * 30, // 30 minutes
gcTime: 1000 * 60 * 60, // 1 hour
refetchOnMount: false,
refetchOnWindowFocus: false,
})
const blurhash =
!isEmpty(item.ImageBlurHashes) &&
!isEmpty(type ? item.ImageBlurHashes[type] : item.ImageBlurHashes.Primary)
? Object.values(type ? item.ImageBlurHashes[type]! : item.ImageBlurHashes.Primary!)[0]
: undefined
return (
<View
minHeight={height ?? width}
minWidth={width}
borderRadius={borderRadius ? borderRadius : 25}
>
{isSuccess ? (
<Image
source={{
uri: image ?? undefined,
}}
style={{
height: height ?? width,
width,
borderRadius: borderRadius ? borderRadius : 25,
resizeMode: 'contain',
}}
/>
) : blurhash ? (
<Blurhash
blurhash={blurhash!}
style={{
height: height ?? width,
width: width,
borderRadius: borderRadius ? borderRadius : 25,
}}
/>
) : (
<Square
backgroundColor='$amethyst'
width={width}
height={height ?? width}
borderRadius={borderRadius ? borderRadius : 25}
/>
)}
</View>
)
}

View File

@@ -4,13 +4,13 @@ import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
import { isUndefined } from 'lodash'
import { StyleProp } from 'react-native'
import FastImage, { ImageStyle } from 'react-native-fast-image'
import { FontSizeTokens, getToken, getTokenValue } from 'tamagui'
import { FontSizeTokens, getFontSizeToken, getToken, getTokenValue, Token } from 'tamagui'
interface ImageProps {
item: BaseItemDto
circular?: boolean | undefined
width?: FontSizeTokens | undefined
height?: FontSizeTokens | undefined
width?: Token | undefined
height?: Token | undefined
style?: ImageStyle | undefined
}
@@ -25,16 +25,34 @@ export default function ItemImage({
<FastImage
source={{ uri: getImageApi(Client.api!).getItemImageUrlById(item.Id!) }}
style={{
borderRadius: circular
? width
? width
: getTokenValue('$12') + getToken('$5')
: getTokenValue('$2'),
width: !isUndefined(width) ? width : getToken('$12') + getToken('$5'),
height: !isUndefined(height) ? height : getToken('$12') + getToken('$5'),
borderRadius: getBorderRadius(circular, width),
width: !isUndefined(width)
? getTokenValue(width)
: getToken('$12') + getToken('$5'),
height: !isUndefined(height)
? getTokenValue(height)
: getToken('$12') + getToken('$5'),
alignSelf: 'center',
...style,
}}
/>
)
}
/**
* Get the border radius for the image
* @param circular - Whether the image is circular
* @param width - The width of the image
* @returns The border radius of the image
*/
function getBorderRadius(circular: boolean | undefined, width: Token | undefined): number {
let borderRadius
if (circular) {
borderRadius = width ? getTokenValue(width) : getTokenValue('$12') + getToken('$5')
} else if (!isUndefined(width)) {
borderRadius = getTokenValue(width) / 10
}
return borderRadius
}

View File

@@ -4,12 +4,12 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { getTokens, Separator, Spacer, View, XStack, YStack } from 'tamagui'
import { Text } from '../helpers/text'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import BlurhashedImage from './blurhashed-image'
import Icon from '../helpers/icon'
import { QueuingType } from '../../../enums/queuing-type'
import { RunTimeTicks } from '../helpers/time-codes'
import { useQueueContext } from '../../../player/queue-provider'
import { usePlayerContext } from '../../../player/player-provider'
import ItemImage from './image'
export default function Item({
item,
@@ -75,11 +75,9 @@ export default function Item({
paddingVertical={'$2'}
marginHorizontal={'$1'}
>
<BlurhashedImage
item={item}
width={width / 9}
borderRadius={item.Type === 'MusicArtist' ? width / 9 : 2}
/>
<YStack flex={1}>
<ItemImage item={item} height={'$12'} width={'$12'} />
</YStack>
<YStack
marginLeft={'$1'}
@@ -97,7 +95,7 @@ export default function Item({
)}
</YStack>
<XStack justifyContent='space-between' alignItems='center' flex={1}>
<XStack justifyContent='space-between' alignItems='center' flex={2}>
{item.UserData?.IsFavorite ? (
<Icon small color={getTokens().color.telemagenta.val} name='heart' />
) : (

View File

@@ -1,7 +1,6 @@
import { InstantMixProps } from '../types'
import { FlatList } from 'react-native'
import Track from '../Global/components/track'
import ItemImage from '../Global/components/image'
import { Separator } from 'tamagui'
export default function InstantMix({ route, navigation }: InstantMixProps): React.JSX.Element {
@@ -10,7 +9,6 @@ export default function InstantMix({ route, navigation }: InstantMixProps): Reac
return (
<FlatList
data={mix}
ListHeaderComponent={<ItemImage item={item} height={'$16'} width={'$16'} />}
ItemSeparatorComponent={() => <Separator />}
renderItem={({ item, index }) => (
<Track

View File

@@ -1,3 +1,6 @@
/**
* An enum of all the keys used with MMKV storage.
*/
export enum MMKVStorageKeys {
PlayQueue = 'PLAY_QUEUE',
Server = 'SERVER',

View File

@@ -1,3 +1,6 @@
/**
* An enum of all the keys of mutation functions.
*/
export enum MutationKeys {
AuthenticationWithCredentials = 'AUTH_WITH_CREDS',
AccessToken = 'ACCESS_TOKEN',

View File

@@ -1,3 +1,6 @@
/**
* An enum of all the keys of query functions.
*/
export enum QueryKeys {
AddToQueue = 'ADD_TO_QUEUE',
AlbumTracks = 'ALBUM_TRACKS',
@@ -7,7 +10,7 @@ export enum QueryKeys {
Credentials = 'CREDENTIALS',
/**
* @deprecated Expo Image is being used instead of
* @deprecated React Native Fast Image is being used instead of
* querying for the images with Tanstack
*/
ItemImage = 'IMAGE_BY_ITEM_ID',

View File

@@ -7542,11 +7542,6 @@ react-native-background-actions@^4.0.1:
dependencies:
eventemitter3 "^4.0.7"
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"
@@ -7666,10 +7661,9 @@ react-native-toast-message@^2.3.0:
resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-2.3.0.tgz#c25fb40d7af4388f6a798efbfca479d53916ed00"
integrity sha512-d7LldTK1ei1Bl7RFhoOYw8hVQ4oKPQHORYI//xR9Pyz3HxSlFlvQbueE5X3KLoemRRgBrOUg3zY6DxXnxrVLRg==
react-native-track-player@4.1.1:
"react-native-track-player@git+https://github.com/riteshshukla04/react-native-track-player#APM":
version "4.1.1"
resolved "https://registry.yarnpkg.com/react-native-track-player/-/react-native-track-player-4.1.1.tgz#96f8688f270c5a01bc597b6331843b191402502b"
integrity sha512-E5N/eK/+HtAVJUAzXpm1cWz8ROheV9jb0TI6h2bM+333U+DWibTTnT2T1122FkCoXLXIYavtm2FR2if+5jH8cA==
resolved "git+https://github.com/riteshshukla04/react-native-track-player#867785387fe6686b669a255df72e17e928b20251"
react-native-url-polyfill@^2.0.0:
version "2.0.0"