Upgrade to React Native's New Architecture (#302)

Makes the UI operate more synchronously (read: fast 🔥🔥🔥)

Upgrades several dependencies
This commit is contained in:
Violet Caulfield
2025-04-30 03:30:24 -05:00
committed by GitHub
parent f12c272175
commit 5754a3d236
29 changed files with 5484 additions and 4967 deletions

View File

@@ -17,8 +17,8 @@ jobs:
- name: 💬 Echo package.json version to Github ENV
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
- name: 🍎 Run yarn init:ios
run: yarn init:ios
- name: 🍎 Run yarn init-ios:new-arch
run: yarn init-ios:new-arch
- name: 🍫 Install CocoaPods
run: yarn pod:install

View File

@@ -17,11 +17,8 @@ jobs:
with:
node-version: 20
- name: 🧵 Run yarn install
run: yarn install
- name: 🍫 Install CocoaPods
run: yarn pod:install
- name: 🍎 Run yarn init-ios:new-arch
run: yarn init-ios:new-arch
- name: Version Up
run: yarn react-native bump-version --type patch

View File

@@ -19,8 +19,8 @@ jobs:
- name: 💬 Echo package.json version to Github ENV
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
- name: 🟢 Run yarn init:ios
run: yarn init:ios
- name: 🍎 Run yarn init-ios:new-arch
run: yarn init-ios:new-arch
- name: 🧪 Run npm test
run: yarn test

132
README.md
View File

@@ -1,16 +1,16 @@
![Jellify App Icon](assets/icon_dark_60pt_3x.png)
# 🪼 Jellify
![Jellify App Icon](assets/icon_dark_60pt_3x.png)
[![publish-beta](https://github.com/anultravioletaurora/Jellify/actions/workflows/publish-beta.yml/badge.svg?branch=main)](https://github.com/anultravioletaurora/Jellify/actions/workflows/publish-beta.yml)
### 🔗 Quick Links
## 🔗 Quick Links
[Discord Server](https://discord.gg/yf8fBatktn)
[TestFlight](https://testflight.apple.com/join/etVSc7ZQ)
### About
## About
> **jellify** (verb) - _to make gelatinous_ <br>
> [see also](https://www.merriam-webster.com/dictionary/jellify)
@@ -31,24 +31,24 @@ This app was designed with me and my dad in mind, since I wanted to give him a s
### ✨ Current
- Available via Testflight and Android APK
- APKs are associated with each [release](https://github.com/anultravioletaurora/Jellify/releases)
- Light and Dark modes
- Home screen access to previously played tracks, artists, and your playlists
- Quick access to similar artists and items for discovering music in your library
- Jellyfin playback reporting and [Last.FM Plugin](https://github.com/jesseward/jellyfin-plugin-lastfm) support
- Library of Favorited Music, not too dissimilar to how streaming services handle your 'library'
- Full playlist support, including creating, updating, and reordering
- Available via Testflight and Android APK
- APKs are associated with each [release](https://github.com/anultravioletaurora/Jellify/releases)
- Light and Dark modes
- Home screen access to previously played tracks, artists, and your playlists
- Quick access to similar artists and items for discovering music in your library
- Jellyfin playback reporting and [Last.FM Plugin](https://github.com/jesseward/jellyfin-plugin-lastfm) support
- Library of Favorited Music, not too dissimilar to how streaming services handle your 'library'
- Full playlist support, including creating, updating, and reordering
- Offline Playback
### 🛠 Roadmap
- [Offline Playback](https://github.com/anultravioletaurora/Jellify/issues/10)
- [CarPlay / Android Auto Support](https://github.com/anultravioletaurora/Jellify/issues/5)
- [Support for Jellyfin Instant Mixes](https://github.com/anultravioletaurora/Jellify/issues/50)
- [Shared, Public, and Collaborative Playlists](https://github.com/anultravioletaurora/Jellify/issues/175)
- [Web / Desktop support](https://github.com/anultravioletaurora/Jellify/issues/71)
- [Watch (Apple Watch / WearOS) Support](https://github.com/anultravioletaurora/Jellify/issues/61)
- [TV (Android, Apple, Samsung) Support](https://github.com/anultravioletaurora/Jellify/issues/85)
- [CarPlay / Android Auto Support](https://github.com/anultravioletaurora/Jellify/issues/5)
- [Support for Jellyfin Instant Mixes](https://github.com/anultravioletaurora/Jellify/issues/50)
- [Shared, Public, and Collaborative Playlists](https://github.com/anultravioletaurora/Jellify/issues/175)
- [Web / Desktop support](https://github.com/anultravioletaurora/Jellify/issues/71)
- [Watch (Apple Watch / WearOS) Support](https://github.com/anultravioletaurora/Jellify/issues/61)
- [TV (Android, Apple, Samsung) Support](https://github.com/anultravioletaurora/Jellify/issues/85)
## 👀 Lemme see!
@@ -80,7 +80,7 @@ Album
<img src="screenshots/album.png" alt="Album" width="275" height="600">
Offline Mode
Album (In Offline Mode)
<img src="screenshots/offline_album.png" alt="Offline Album" width="275" height="600">
@@ -126,7 +126,7 @@ Playlist
[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)\
[React Native Vector Icons](https://github.com/oblador/react-native-vector-icons)
- Specifically using [Material Community Icons](https://oblador.github.io/react-native-vector-icons/#MaterialCommunityIcons)
- Specifically using [Material Community Icons](https://oblador.github.io/react-native-vector-icons/#MaterialCommunityIcons)
### 🎛️ Backend
@@ -151,86 +151,86 @@ This is undoubtedly a passion project of [mine](https://github.com/anultraviolet
### ⚛️ Universal Dependencies
- [Ruby](https://www.ruby-lang.org/en/documentation/installation/) for Fastlane
- [NodeJS v22](https://nodejs.org/en/download) for React Native
- [Ruby](https://www.ruby-lang.org/en/documentation/installation/) for Fastlane
- [NodeJS v22](https://nodejs.org/en/download) for React Native
### 🍎 iOS
#### Dependencies
- [Xcode](https://developer.apple.com/xcode/) for building
- [Xcode](https://developer.apple.com/xcode/) for building
#### Instructions
##### Setup
- Clone this repository
- Run `yarn init:ios` to initialize the project
- This will install `npm` packages, install `bundler` and required gems, and install required CocoaPods
- In the `ios` directory, run `fastlane match development --readonly` to fetch the development signing certificates
- *You will need access to the *Jellify Signing* private repository*
- Clone this repository
- Run `yarn init-ios:new-arch` to initialize the project
- This will install `npm` packages, install `bundler` and required gems, and install required CocoaPods with [React Native's New Architecture](https://reactnative.dev/blog/2024/10/23/the-new-architecture-is-here#what-is-the-new-architecture)
- In the `ios` directory, run `fastlane match development --readonly` to fetch the development signing certificates
- _You will need access to the "Jellify Signing" private repository_
##### Running
- Run `yarn start` to start the dev server
- Open the `Jellify.xcodeworkspace` with Xcode, _not_ the `Jellify.xcodeproject`
- Run either on a device or in the simulator
- _You will need to wait for Xcode to finish it's "Indexing" step_
- Run `yarn start` to start the dev server
- Open the `Jellify.xcodeworkspace` with Xcode, _not_ the `Jellify.xcodeproject`
- Run either on a device or in the simulator
- _You will need to wait for Xcode to finish it's "Indexing" step_
##### Building
- To create a build, run `yarn fastlane:ios:build` to use fastlane to compile an `.ipa`
- To create a build, run `yarn fastlane:ios:build` to use fastlane to compile an `.ipa`
### 🤖 Android
#### Dependencies
- [Android Studio](https://developer.android.com/studio)
- [Java Development Kit](https://www.oracle.com/th/java/technologies/downloads/)
- [Android Studio](https://developer.android.com/studio)
- [Java Development Kit](https://www.oracle.com/th/java/technologies/downloads/)
#### Instructions
##### Setup
- Clone this repository
- Run `yarn install` to install `npm` packages
- Clone this repository
- Run `yarn install` to install `npm` packages
##### Running
- Run `yarn start` to start the dev server
- Open the `android` folder with Android Studio
- _Android Studio should automatically grab the "Run Configurations" and initialize Gradle_
- Run either on a device or in the simulator
- Run `yarn start` to start the dev server
- Open the `android` folder with Android Studio
- _Android Studio should automatically grab the "Run Configurations" and initialize Gradle_
- Run either on a device or in the simulator
##### Building
- To create a build, run `yarn fastlane:android:build` to use fastlane to compile an `.apk` for all architectures
- To create a build, run `yarn fastlane:android:build` to use fastlane to compile an `.apk` for all architectures
#### References
- [Setting up Android SDK](https://developer.android.com/about/versions/14/setup-sdk)
- [ANDROID_HOME not being set](https://stackoverflow.com/questions/26356359/error-android-home-is-not-set-and-android-command-not-in-your-path-you-must/54888107#54888107)
- [Android Auto app not showing up](https://www.reddit.com/r/AndroidAuto/s/LGYHoSPdXm)
- [Setting up Android SDK](https://developer.android.com/about/versions/14/setup-sdk)
- [ANDROID_HOME not being set](https://stackoverflow.com/questions/26356359/error-android-home-is-not-set-and-android-command-not-in-your-path-you-must/54888107#54888107)
- [Android Auto app not showing up](https://www.reddit.com/r/AndroidAuto/s/LGYHoSPdXm)
## 🙏 Special Thanks To
- The [Jellyfin Team](https://jellyfin.org/) for making this possible with their software, SDKs, and unequivocal helpfulness.
- Extra thanks to [Niels](https://github.com/nielsvanvelzen) and [Bill](https://github.com/thornbill)
- [James](https://github.com/jmshrv) and all other contributors of [Finamp](https://github.com/jmshrv/finamp). _Jellify_ draws inspiration and wisdom from it, and is another fantastic music app for Jellyfin.
- James [API Blog Post](https://jmshrv.com/posts/jellyfin-api/) proved to be exceptionally valuable during development
- The folks in the [Margelo Community Discord](https://discord.com/invite/6CSHz2qAvA) for their assistance
- [Nicolas Charpentier](https://github.com/charpeni) for his [React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill) module and for his assistance with getting Jest working
- The team behind [Podverse](https://github.com/podverse/podverse-rn) for their incredible open source project, of which was used as a reference extensively during development
- My fellow [contributors](https://github.com/anultravioletaurora/Jellify/graphs/contributors) who have poured so much heart and a lot of sweat into making _Jellify_ a great experience
- Extra thanks to [John](https://github.com/johngrantdev) and [Vali-98](https://github.com/Vali-98) for shaping and designing the user experience in many places
- Huge thank you to [Ritesh](https://github.com/riteshshukla04) for your project automation and backend expertise (and for the memes)
- The friends I made along the way that have been critical in fostering an amazing community around _Jellify_
- [Thalia](https://github.com/PercyGabriel1129)
- [BotBlake](https://github.com/BotBlake)
- My long time friends that have heard me talk about _Jellify_ for literally **eons**. Thank you for testing _Jellify_ during it's infancy and for supporting me all the way back at the beginning of this project
- Tony (iOS, Android)
- Trevor (Android)
- [Laine](https://github.com/lainie-ftw) (Android)
- [Jordan](https://github.com/jordanbleu) (iOS)
- My best(est) friend [Alyssa](https://www.instagram.com/uhh.lyssarae?igsh=MTRmczExempnbjBwZw==), for your design knowledge and for making various artwork for _Jellify_.
- Youve been instrumental in shaping its user experience, my rock during development, and an overall inspiration in my life
- The [Jellyfin Team](https://jellyfin.org/) for making this possible with their software, SDKs, and unequivocal helpfulness.
- Extra thanks to [Niels](https://github.com/nielsvanvelzen) and [Bill](https://github.com/thornbill)
- [James](https://github.com/jmshrv) and all other contributors of [Finamp](https://github.com/jmshrv/finamp). _Jellify_ draws inspiration and wisdom from it, and is another fantastic music app for Jellyfin.
- James [API Blog Post](https://jmshrv.com/posts/jellyfin-api/) proved to be exceptionally valuable during development
- The folks in the [Margelo Community Discord](https://discord.com/invite/6CSHz2qAvA) for their assistance
- [Nicolas Charpentier](https://github.com/charpeni) for his [React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill) module and for his assistance with getting Jest working
- The team behind [Podverse](https://github.com/podverse/podverse-rn) for their incredible open source project, of which was used as a reference extensively during development
- My fellow [contributors](https://github.com/anultravioletaurora/Jellify/graphs/contributors) who have poured so much heart and a lot of sweat into making _Jellify_ a great experience
- Extra thanks to [John](https://github.com/johngrantdev) and [Vali-98](https://github.com/Vali-98) for shaping and designing the user experience in many places
- Huge thank you to [Ritesh](https://github.com/riteshshukla04) for your project automation and backend expertise (and for the memes)
- The friends I made along the way that have been critical in fostering an amazing community around _Jellify_
- [Thalia](https://github.com/PercyGabriel1129)
- [BotBlake](https://github.com/BotBlake)
- My long time friends that have heard me talk about _Jellify_ for literally **eons**. Thank you for testing _Jellify_ during it's infancy and for supporting me all the way back at the beginning of this project
- Tony (iOS, Android)
- Trevor (Android)
- [Laine](https://github.com/lainie-ftw) (Android)
- [Jordan](https://github.com/jordanbleu) (iOS)
- My best(est) friend [Alyssa](https://www.instagram.com/uhh.lyssarae?igsh=MTRmczExempnbjBwZw==), for your design knowledge and for making various artwork for _Jellify_.
- Youve been instrumental in shaping its user experience, my rock during development, and an overall inspiration in my life

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

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -3,6 +3,8 @@ import { getModel, getUniqueIdSync } from 'react-native-device-info'
import { name, version } from '../package.json'
import { capitalize } from 'lodash'
console.debug(`Building Jellyfin Info`)
/**
* Client object that represents Jellify on the Jellyfin server.
*/

View File

@@ -1,4 +1,7 @@
{
"name": "Jellify",
"displayName": "Jellify"
"displayName": "Jellify",
"expo": {
"newArchEnabled": true
}
}

View File

@@ -1,7 +1,6 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
'react-native-boost/plugin',
// react-native-reanimated/plugin has to be listed last
'react-native-reanimated/plugin',
],

View File

@@ -10,6 +10,7 @@ import Client from '../../api/client'
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'
export function ProvidedHome({
navigation,
@@ -18,6 +19,8 @@ export function ProvidedHome({
}): React.JSX.Element {
const { refreshing: refetching, onRefresh } = useHomeContext()
const insets = useSafeAreaInsets()
return (
<ScrollView
contentInsetAdjustmentBehavior='automatic'

View File

@@ -1,8 +1,8 @@
import Playlist from '../../../components/Playlist/component'
import { StackParamList } from '../../../components/types'
import { RouteProp } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import React from 'react'
import Playlist from '..'
export function PlaylistScreen({
route,

View File

@@ -1,5 +1,4 @@
import React from 'react'
import { ScrollView } from 'tamagui'
import SignOut from './helpers/sign-out'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { StackParamList } from '../types'
@@ -7,7 +6,7 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context'
import { FlatList } from 'react-native'
import IconCard from '../Global/helpers/icon-card'
import Categories from './categories'
import { StorageBar } from '../Storage'
import StorageBar from '../Storage'
export default function Root({
navigation,

View File

@@ -1,14 +1,15 @@
import React, { useEffect, useState } from 'react'
import { View, Text, StyleSheet, Pressable, Alert, FlatList } from 'react-native'
import { StyleSheet, Pressable, Alert, FlatList } from 'react-native'
import RNFS from 'react-native-fs'
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
import { deleteAudioCache } from '../Network/offlineModeUtils'
import { useNetworkContext } from '../Network/provider'
import DownloadProgress from '../../types/DownloadProgress'
import Icon from '../Global/helpers/icon'
import { View } from 'tamagui'
import { Text } from '../Global/helpers/text'
// 🔹 Single Download Item with animated progress bar
const DownloadItem = ({
function DownloadItem({
name,
progress,
fileName,
@@ -16,7 +17,7 @@ const DownloadItem = ({
name: string
progress: number
fileName: string
}) => {
}): React.JSX.Element {
const progressValue = useSharedValue(progress)
useEffect(() => {
@@ -38,7 +39,7 @@ const DownloadItem = ({
}
// 🔹 Main UI Component
export const StorageBar = () => {
export default function StorageBar(): React.JSX.Element {
const [used, setUsed] = useState(0)
const [total, setTotal] = useState(1)
@@ -86,7 +87,9 @@ export const StorageBar = () => {
{/* Storage Usage */}
<Text style={styles.title}>📦 Storage Usage</Text>
<Text style={styles.usage}>
{(used / 1024 / 1024).toFixed(2)} MB / {(total / 1024 / 1024 / 1024).toFixed(2)} GB
{`${(used / 1024 / 1024).toFixed(2)} MB / ${(total / 1024 / 1024 / 1024).toFixed(
2,
)} GB`}
</Text>
<View style={styles.progressBackground}>
<Animated.View style={[styles.progressFill, storageBarStyle]} />

View File

@@ -1,5 +1,5 @@
import _ from 'lodash'
import React from 'react'
import React, { useEffect } from 'react'
import Navigation from './navigation'
import Login from './Login/component'
import { JellyfinAuthenticationProvider } from './Login/provider'
@@ -11,8 +11,15 @@ import { ToastProvider } from '@tamagui/toast'
import { JellifyUserDataProvider } from './user-data-provider'
import { NetworkContextProvider } from './Network/provider'
import { QueueProvider } from '../player/queue-provider'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
export default function Jellify(): React.JSX.Element {
const insets = useSafeAreaInsets()
useEffect(() => {
console.debug(insets)
}, [insets])
return (
<PortalProvider shouldAddRootHost>
<ToastProvider burntOptions={{ from: 'top' }}>

View File

@@ -1,22 +0,0 @@
import { StyleSheet } from 'react-native'
export const jellifyStyles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
marginHorizontal: 16,
},
title: {
textAlign: 'center',
marginVertical: 8,
},
fixToText: {
flexDirection: 'row',
justifyContent: 'space-between',
},
separator: {
marginVertical: 8,
borderBottomColor: '#737373',
borderBottomWidth: StyleSheet.hairlineWidth,
},
})

View File

@@ -2,7 +2,6 @@ import React from 'react'
import { BottomTabBar, createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import Home from './Home/stack'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import Library from './Library/stack'
import Settings from './Settings/stack'
import { Discover } from './Discover/stack'
import { Miniplayer } from './Player/mini-player'
@@ -10,7 +9,7 @@ import { getToken, getTokens, Separator } from 'tamagui'
import { usePlayerContext } from '../player/player-provider'
import SearchStack from './Search/stack'
import LibraryStack from './Library/stack'
import { useColorScheme, View } from 'react-native'
import { useColorScheme } from 'react-native'
import InternetConnectionWatcher from './Network/internetConnectionWatcher'
const Tab = createBottomTabNavigator()
@@ -20,99 +19,97 @@ export function Tabs(): React.JSX.Element {
const { nowPlaying } = usePlayerContext()
return (
<View style={{ flex: 1 }}>
<Tab.Navigator
initialRouteName='Home'
screenOptions={{
lazy: false,
animation: 'shift',
tabBarActiveTintColor: getTokens().color.telemagenta.val,
tabBarInactiveTintColor: isDarkMode
? getToken('$color.amethyst')
: getToken('$color.purpleGray'),
<Tab.Navigator
initialRouteName='Home'
screenOptions={{
lazy: false,
animation: 'shift',
tabBarActiveTintColor: getTokens().color.telemagenta.val,
tabBarInactiveTintColor: isDarkMode
? getToken('$color.amethyst')
: getToken('$color.purpleGray'),
}}
tabBar={(props) => (
<>
{nowPlaying && (
/* Hide miniplayer if the queue is empty */
<>
<Separator />
<Miniplayer navigation={props.navigation} />
</>
)}
<InternetConnectionWatcher />
<BottomTabBar {...props} />
</>
)}
>
<Tab.Screen
name='Home'
component={Home}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name='jellyfish-outline'
color={color}
size={size}
/>
),
}}
tabBar={(props) => (
<>
{nowPlaying && (
/* Hide miniplayer if the queue is empty */
<>
<Separator />
<Miniplayer navigation={props.navigation} />
</>
)}
<InternetConnectionWatcher />
/>
<BottomTabBar {...props} />
</>
)}
>
<Tab.Screen
name='Home'
component={Home}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name='jellyfish-outline'
color={color}
size={size}
/>
),
}}
/>
<Tab.Screen
name='Library'
component={LibraryStack}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name='book-music-outline'
color={color}
size={size}
/>
),
}}
/>
<Tab.Screen
name='Library'
component={LibraryStack}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name='book-music-outline'
color={color}
size={size}
/>
),
}}
/>
<Tab.Screen
name='Search'
component={SearchStack}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name='magnify' color={color} size={size} />
),
}}
/>
<Tab.Screen
name='Search'
component={SearchStack}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name='magnify' color={color} size={size} />
),
}}
/>
<Tab.Screen
name='Discover'
component={Discover}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name='music-box-multiple-outline'
color={color}
size={size}
/>
),
}}
/>
<Tab.Screen
name='Discover'
component={Discover}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons
name='music-box-multiple-outline'
color={color}
size={size}
/>
),
}}
/>
<Tab.Screen
name='Settings'
component={Settings}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name='dip-switch' color={color} size={size} />
),
}}
/>
</Tab.Navigator>
</View>
<Tab.Screen
name='Settings'
component={Settings}
options={{
headerShown: false,
tabBarIcon: ({ color, size }) => (
<MaterialCommunityIcons name='dip-switch' color={color} size={size} />
),
}}
/>
</Tab.Navigator>
)
}

View File

@@ -1,6 +1,8 @@
import { MMKV } from 'react-native-mmkv'
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
console.debug(`Building MMKV storage`)
export const storage = new MMKV()
const clientStorage = {

View File

@@ -10,8 +10,8 @@ import Client from './api/client'
/* eslint-disable @typescript-eslint/no-unused-expressions */
Client.instance
// Enable React Navigation freeze for detaching inactive screens
// enableFreeze();
Client.instance
console.debug('Created Jellify client')
AppRegistry.registerComponent(appName, () => App)
AppRegistry.registerComponent('RNCarPlayScene', () => App)

View File

@@ -2,36 +2,38 @@
import UIKit
import CarPlay
import React
#if DEBUG
#if FB_SONARKIT_ENABLED
import FlipperKit
#endif
#endif
import React_RCTAppDelegate
import ReactAppDependencyProvider
@main
class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var bridge: RCTBridge?;
var rootView: RCTRootView?;
static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate }
func sourceURL(for bridge: RCTBridge) -> URL? {
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index");
#else
return Bundle.main.url(forResource:"main", withExtension:"jsbundle")
#endif
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
initializeFlipper(with: application)
self.bridge = RCTBridge.init(delegate: self, launchOptions: launchOptions)
self.rootView = RCTRootView.init(bridge: self.bridge!, moduleName: "Jellify", initialProperties: nil)
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let delegate = ReactNativeDelegate()
let factory = RCTReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "Jellify",
in: window,
launchOptions: launchOptions
)
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if (connectingSceneSession.role == UISceneSession.Role.carTemplateApplication) {
let scene = UISceneConfiguration(name: "CarPlay", sessionRole: connectingSceneSession.role)
@@ -43,21 +45,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
return scene
}
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
}
}
private func initializeFlipper(with application: UIApplication) {
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
#if FB_SONARKIT_ENABLED
let client = FlipperClient.shared()
let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())
client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))
client?.add(FKUserDefaultsPlugin(suiteName: nil))
client?.add(FlipperKitReactPlugin())
client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
client?.start()
#endif
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index");
#else
return Bundle.main.url(forResource:"main", withExtension:"jsbundle")
#endif
}
}

View File

@@ -4,20 +4,6 @@ import CarPlay
class CarSceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) {
if let applicationDelegate = UIApplication.shared.delegate as? AppDelegate {
if applicationDelegate.bridge == nil {
applicationDelegate.bridge = RCTBridge.init(delegate: applicationDelegate, launchOptions: [:])
applicationDelegate.rootView = RCTRootView.init(
bridge: applicationDelegate.bridge!,
moduleName: "Jellify",
initialProperties: nil
)
}
}
RNCarPlay.connect(with: interfaceController, window: templateApplicationScene.carWindow);

View File

@@ -4,12 +4,17 @@ source 'https://rubygems.org'
ruby ">=3.0.0"
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
gem 'cocoapods', '>= 1.16', '!= 1.15.0', '!= 1.15.1'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.27.1'
gem 'concurrent-ruby', '< 1.3.4'
gem 'concurrent-ruby', '< 1.3.6'
gem "fastlane"
# Ruby 3.4.0 has removed some libraries from the standard library.
gem 'bigdecimal'
gem 'logger'
gem 'benchmark'
gem 'mutex_m'
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

View File

@@ -330,10 +330,14 @@ PLATFORMS
DEPENDENCIES
activesupport (>= 6.1.7.5, != 7.1.0)
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
concurrent-ruby (< 1.3.4)
benchmark
bigdecimal
cocoapods (>= 1.16, != 1.15.1, != 1.15.0)
concurrent-ruby (< 1.3.6)
fastlane
fastlane-plugin-discord_notifier
logger
mutex_m
xcodeproj (< 1.27.1)
RUBY VERSION

View File

@@ -1,19 +1,99 @@
// ios/PhoneScene.swift
import Foundation
import UIKit
import SwiftUI
extension UIColor {
convenience init(hex: String) {
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
var rgb: UInt64 = 0
Scanner(string: hexSanitized).scanHexInt64(&rgb)
let r = (rgb & 0xFF0000) >> 16
let g = (rgb & 0x00FF00) >> 8
let b = rgb & 0x0000FF
self.init(
red: CGFloat(r) / 255,
green: CGFloat(g) / 255,
blue: CGFloat(b) / 255,
alpha: 1.0
)
}
}
class PhoneSceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let appDelegate = (UIApplication.shared.delegate as? AppDelegate) else { return }
guard let windowScene = (scene as? UIWindowScene) else { return }
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
guard let windowScene = scene as? UIWindowScene else { return }
guard let appRootView = appDelegate.window?.rootViewController?.view else { return }
let rootViewController = UIViewController()
rootViewController.view = appDelegate.rootView;
let containerViewController = UIViewController()
containerViewController.view.backgroundColor = .systemBackground // dynamic light/dark
// Create safe area container
let safeAreaContainer = UIView()
safeAreaContainer.translatesAutoresizingMaskIntoConstraints = false
containerViewController.view.addSubview(safeAreaContainer)
NSLayoutConstraint.activate([
safeAreaContainer.topAnchor.constraint(equalTo: containerViewController.view.safeAreaLayoutGuide.topAnchor),
safeAreaContainer.bottomAnchor.constraint(equalTo: containerViewController.view.safeAreaLayoutGuide.bottomAnchor),
safeAreaContainer.leadingAnchor.constraint(equalTo: containerViewController.view.safeAreaLayoutGuide.leadingAnchor),
safeAreaContainer.trailingAnchor.constraint(equalTo: containerViewController.view.safeAreaLayoutGuide.trailingAnchor),
])
// Create colored views for top and bottom safe area
let topBar = UIView()
let bottomBar = UIView()
topBar.translatesAutoresizingMaskIntoConstraints = false
bottomBar.translatesAutoresizingMaskIntoConstraints = false
// Color that automatically adapts to dark/light mode
topBar.backgroundColor = UIColor { trait in
return trait.userInterfaceStyle == .dark ? UIColor(hex: "#0C0622") : UIColor(hex:"#FFFFFF")
}
bottomBar.backgroundColor = UIColor { trait in
return trait.userInterfaceStyle == .dark ? UIColor(hex:"#0C0622") : UIColor(hex:"#FFFFFF")
}
containerViewController.view.addSubview(topBar)
containerViewController.view.addSubview(bottomBar)
NSLayoutConstraint.activate([
// Top bar
topBar.topAnchor.constraint(equalTo: containerViewController.view.topAnchor),
topBar.leadingAnchor.constraint(equalTo: containerViewController.view.leadingAnchor),
topBar.trailingAnchor.constraint(equalTo: containerViewController.view.trailingAnchor),
topBar.bottomAnchor.constraint(equalTo: containerViewController.view.safeAreaLayoutGuide.topAnchor),
// Bottom bar
bottomBar.topAnchor.constraint(equalTo: containerViewController.view.safeAreaLayoutGuide.bottomAnchor),
bottomBar.leadingAnchor.constraint(equalTo: containerViewController.view.leadingAnchor),
bottomBar.trailingAnchor.constraint(equalTo: containerViewController.view.trailingAnchor),
bottomBar.bottomAnchor.constraint(equalTo: containerViewController.view.bottomAnchor),
])
// Add appRootView inside safeAreaContainer
appRootView.translatesAutoresizingMaskIntoConstraints = false
safeAreaContainer.addSubview(appRootView)
NSLayoutConstraint.activate([
appRootView.topAnchor.constraint(equalTo: safeAreaContainer.topAnchor),
appRootView.bottomAnchor.constraint(equalTo: safeAreaContainer.bottomAnchor),
appRootView.leadingAnchor.constraint(equalTo: safeAreaContainer.leadingAnchor),
appRootView.trailingAnchor.constraint(equalTo: safeAreaContainer.trailingAnchor),
])
// Set up the window
let window = UIWindow(windowScene: windowScene)
window.rootViewController = rootViewController
window.rootViewController = containerViewController
self.window = window
window.makeKeyAndVisible()
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { wrapWithReanimatedMetroConfig } = require('react-native-reanimated/metro-config')
const { getDefaultConfig } = require('@react-native/metro-config')
const config = getDefaultConfig(__dirname, {

View File

@@ -1,123 +1,120 @@
{
"name": "jellify",
"version": "0.11.15",
"private": true,
"scripts": {
"init:ios": "yarn install && yarn pod:install",
"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",
"clean:ios": "cd ios && pod deintegrate",
"clean:android": "cd android && rm -rf app/ build/",
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
"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": "^15.1.3",
"@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.1",
"@tamagui/toast": "^1.126.1",
"@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",
"burnt": "^0.13.0",
"expo": "^52.0.46",
"expo-image": "^2.0.7",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"jest-expo": "^52.0.6",
"lodash": "^4.17.21",
"npm-bundle": "^3.0.3",
"patch-package": "^8.0.0",
"react": "18.3.1",
"react-freeze": "^1.0.4",
"react-native": "0.77.0",
"react-native-background-actions": "^4.0.1",
"react-native-blurhash": "^2.1.1",
"react-native-boost": "^0.5.6",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.2",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.25.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "^2.12.2",
"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.10.0",
"react-native-swipeable-item": "^2.0.9",
"react-native-text-ticker": "^1.14.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.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli-platform-android": "15.1.3",
"@react-native-community/cli-platform-ios": "15.1.3",
"@react-native/babel-preset": "0.77.0",
"@react-native/eslint-config": "0.77.0",
"@react-native/metro-config": "0.77.0",
"@react-native/typescript-config": "0.77.0",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10",
"@types/react": "^18.2.6",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"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",
"prettier": "^2.8.8",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "18.3.1",
"typescript": "5.7.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
]
},
"engines": {
"node": ">=18"
}
}
"name": "jellify",
"version": "0.11.15",
"private": true,
"scripts": {
"init-ios": "yarn install && yarn pod:install",
"init-ios:new-arch": "yarn install && yarn run 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",
"clean:ios": "cd ios && pod deintegrate",
"clean:android": "cd android && rm -rf app/ build/",
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
"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/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",
"@tamagui/toast": "^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",
"burnt": "^0.13.0",
"expo": "^53.0.1",
"expo-image": "^2.0.7",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"jest-expo": "^53.0.1",
"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-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-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-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

7574
yarn.lock

File diff suppressed because it is too large Load Diff