[skip actions]

run prettier
This commit is contained in:
Violet Caulfield
2025-04-12 10:16:10 -05:00
parent fef070d95d
commit 5ca00df6f7
48 changed files with 23654 additions and 23592 deletions

View File

@@ -10,10 +10,13 @@ module.exports = {
env: {
browser: true,
node: true,
jest: true,
},
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'error', // Disallow usage of any
'no-mixed-spaces-and-tabs': 'off', // refer https://github.com/prettier/prettier/issues/4199

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
node_modules/

169
README.md
View File

@@ -1,56 +1,65 @@
![Jellify App Icon](assets/icon_dark_60pt_3x.png)
# 🪼 Jellify
[![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
[Discord Server](https://discord.gg/yf8fBatktn)
[TestFlight](https://testflight.apple.com/join/etVSc7ZQ)
### About
> **jellify** (verb) - *to make gelatinous* <br>
[see also](https://www.merriam-webster.com/dictionary/jellify)
> **jellify** (verb) - _to make gelatinous_ <br>
> [see also](https://www.merriam-webster.com/dictionary/jellify)
*Jellify* is a free and open source music player for [Jellyfin](https://jellyfin.org/). Built with [React Native](https://reactnative.dev/), *Jellify* provides a user experience that feels familar to other popular music apps and a has featureset to match
_Jellify_ is a free and open source music player for [Jellyfin](https://jellyfin.org/). Built with [React Native](https://reactnative.dev/), _Jellify_ provides a user experience that feels familar to other popular music apps and a has featureset to match
> *Jellify* requires a connection to a [Jellyfin](https://jellyfin.org/) server to work.
> _Jellify_ requires a connection to a [Jellyfin](https://jellyfin.org/) server to work.
### 🤓 Background
I was after a music app for Jellyfin that showcased my music with artwork, had a user interface congruent with what the big guys do, and had the ability to algorithmically curate music (not that you have to use *Jellify* that way). I also wanted to create a music app that could handle my extremely large music libraries (i.e., 100K+ songs) and not get bogged down.
This app was designed with me and my dad in mind, since I wanted to give him a sleek, one stop shop for live recordings of bands he likes (read: the Grateful Dead). The UI was designed so that he'd find it instantly familiar and useful. CarPlay / Android Auto support was also a must for us, as we both use CarPlay religiously.
I was after a music app for Jellyfin that showcased my music with artwork, had a user interface congruent with what the big guys do, and had the ability to algorithmically curate music (not that you have to use _Jellify_ that way). I also wanted to create a music app that could handle my extremely large music libraries (i.e., 100K+ songs) and not get bogged down.
**TL;DR** Designed to be lightweight and scalable, *Jellify* caters to those who want a mobile Jellyfin music experience similar to what's provided by the big music streaming services.
This app was designed with me and my dad in mind, since I wanted to give him a sleek, one stop shop for live recordings of bands he likes (read: the Grateful Dead). The UI was designed so that he'd find it instantly familiar and useful. CarPlay / Android Auto support was also a must for us, as we both use CarPlay religiously.
**TL;DR** Designed to be lightweight and scalable, _Jellify_ caters to those who want a mobile Jellyfin music experience similar to what's provided by the big music streaming services.
## 💡 Features
### ✨ 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
### 🛠 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)
- [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)
## 👀 Lemme see!
### Home
Home
<img src="screenshots/home.png" alt="Jellify Home" width="275" height="600">
### Library
Library
<img src="screenshots/library.png" alt="Library" width="275" height="600">
@@ -76,7 +85,7 @@ Album Artists
<img src="screenshots/album_artists.png" alt="Album Artists" width="275" height="600">
Track Options
<img src="screenshots/track_options.png" alt="Track Options" width="275" height="600">
Playlist
@@ -84,9 +93,11 @@ Playlist
<img src="screenshots/playlist.png" alt="Playlist" width="275" height="600">
### Search
<img src="screenshots/search.png" alt="Search" width="275" height="600">
### Player
<img src="screenshots/player.png" alt="Player" width="275" height="600">
<img src="screenshots/player_queue.png" alt="Queue" width="275" height="600">
@@ -94,14 +105,19 @@ Playlist
<img src='screenshots/favorite_track.png' alt='Favorite Track' width='275' height='600”'>
### CarPlay (Sneak Preview)
<img src="screenshots/carplay_nowplaying.jpeg" alt="Now Playing (CarPlay)" width="500" height="350">
### On the Server
<img src="https://github.com/user-attachments/assets/741884a2-b9b7-4081-b3a0-6655d08071dc" alt="Playback Tracking" width="300" height="200">
## 🏗 Built with good stuff
[![Made with React](https://img.shields.io/badge/React-18-blue?logo=react&logoColor=white)](https://reactjs.org “Go to React homepage”) [![Made with TypeScript](https://img.shields.io/badge/TypeScript-5-blue?logo=typescript&logoColor=white)](https://typescriptlang.org “Go to TypeScript homepage”)
### 🎨 Frontend
[Tamagui](https://tamagui.dev/)\
[Burnt](https://github.com/nandorojo/burnt)\
[React Navigation](https://reactnavigation.org/)\
@@ -109,9 +125,11 @@ Playlist
[React Native Draggable Flatlist](https://github.com/computerjazz/react-native-draggable-flatlist)\
[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
[Expo SDK](https://expo.dev/)\
[Jellyfin SDK](https://typescript-sdk.jellyfin.org/)\
[Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/react-native)\
@@ -122,78 +140,95 @@ Playlist
[React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill)
### 👩‍💻 Monitoring
[GlitchTip](https://glitchtip.com/)
### 💜 Love from Wisconsin 🧀
This is undoubtedly a passion project of [mine](https://github.com/anultravioletaurora), and I've learned a lot from working on it (and the many failed attempts before it). I hope you enjoy using it! Feature requests and bug reports are welcome :)
## 🏃Running Locally
### ⚛️ 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 `npm run init` to initialize the project
- This will install `npm` packages, install `bundler` and required gems, and installs 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 `npm run init` to initialize the project
- This will install `npm` packages, install `bundler` and required gems, and installs 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*
##### Running
- Run `npm run 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 `npm run 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 `npm run fastlane:ios:build` to use fastlane to compile an `.ipa` for you
- To create a build, run `npm run fastlane:ios:build` to use fastlane to compile an `.ipa` for you
### 🤖 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 `npm i` to install `npm` packages
- Clone this repository
- Run `npm i` to install `npm` packages
##### Running
- Run `npm run 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 `npm run 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 `npm run fastlane:android:build` to use fastlane to compile an `.apk` for you
- To create a build, run `npm run fastlane:android:build` to use fastlane to compile an `.apk` for you
#### 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)
- [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)
## 🙏 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
- Extra thanks to [Ritesh](https://github.com/riteshshukla04) for your help, knowledge, and guidance
- [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
- 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 Grant](https://github.com/johngrantdev) for shaping and designing the user experience in many places
- 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
- Extra thanks to [Ritesh](https://github.com/riteshshukla04) for your help, knowledge, and guidance
- [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
- 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 Grant](https://github.com/johngrantdev) for shaping and designing the user experience in many places
- 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

@@ -1,5 +1,4 @@
fastlane documentation
----
## fastlane documentation
# Installation
@@ -29,8 +28,6 @@ Runs all the tests
[bundle exec] fastlane android build
```
### android deploy
```sh
@@ -39,7 +36,7 @@ Runs all the tests
Deploy a new version to the Google Play
----
---
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.

View File

@@ -1,69 +1,69 @@
{
"migIndex": 1,
"data": [
{
"path": "assets/fonts/Aileron-Black.otf",
"sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999"
},
{
"path": "assets/fonts/Aileron-BlackItalic.otf",
"sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73"
},
{
"path": "assets/fonts/Aileron-Bold.otf",
"sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a"
},
{
"path": "assets/fonts/Aileron-BoldItalic.otf",
"sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e"
},
{
"path": "assets/fonts/Aileron-Heavy.otf",
"sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6"
},
{
"path": "assets/fonts/Aileron-HeavyItalic.otf",
"sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259"
},
{
"path": "assets/fonts/Aileron-Italic.otf",
"sha1": "338b043581d997314a4a03924ed30ff6461fd37e"
},
{
"path": "assets/fonts/Aileron-Light.otf",
"sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad"
},
{
"path": "assets/fonts/Aileron-LightItalic.otf",
"sha1": "48a4355b8792657845b3b0cd39c42994923a117a"
},
{
"path": "assets/fonts/Aileron-Regular.otf",
"sha1": "5a78965873fbce38941cd3da109280af89a42de5"
},
{
"path": "assets/fonts/Aileron-SemiBold.otf",
"sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824"
},
{
"path": "assets/fonts/Aileron-SemiBoldItalic.otf",
"sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f"
},
{
"path": "assets/fonts/Aileron-Thin.otf",
"sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c"
},
{
"path": "assets/fonts/Aileron-ThinItalic.otf",
"sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214"
},
{
"path": "assets/fonts/Aileron-UltraLight.otf",
"sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24"
},
{
"path": "assets/fonts/Aileron-UltraLightItalic.otf",
"sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0"
}
]
"migIndex": 1,
"data": [
{
"path": "assets/fonts/Aileron-Black.otf",
"sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999"
},
{
"path": "assets/fonts/Aileron-BlackItalic.otf",
"sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73"
},
{
"path": "assets/fonts/Aileron-Bold.otf",
"sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a"
},
{
"path": "assets/fonts/Aileron-BoldItalic.otf",
"sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e"
},
{
"path": "assets/fonts/Aileron-Heavy.otf",
"sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6"
},
{
"path": "assets/fonts/Aileron-HeavyItalic.otf",
"sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259"
},
{
"path": "assets/fonts/Aileron-Italic.otf",
"sha1": "338b043581d997314a4a03924ed30ff6461fd37e"
},
{
"path": "assets/fonts/Aileron-Light.otf",
"sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad"
},
{
"path": "assets/fonts/Aileron-LightItalic.otf",
"sha1": "48a4355b8792657845b3b0cd39c42994923a117a"
},
{
"path": "assets/fonts/Aileron-Regular.otf",
"sha1": "5a78965873fbce38941cd3da109280af89a42de5"
},
{
"path": "assets/fonts/Aileron-SemiBold.otf",
"sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824"
},
{
"path": "assets/fonts/Aileron-SemiBoldItalic.otf",
"sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f"
},
{
"path": "assets/fonts/Aileron-Thin.otf",
"sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c"
},
{
"path": "assets/fonts/Aileron-ThinItalic.otf",
"sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214"
},
{
"path": "assets/fonts/Aileron-UltraLight.otf",
"sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24"
},
{
"path": "assets/fonts/Aileron-UltraLightItalic.otf",
"sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0"
}
]
}

View File

@@ -1,4 +1,4 @@
{
"name": "Jellify",
"displayName": "Jellify"
"name": "Jellify",
"displayName": "Jellify"
}

View File

@@ -1,8 +1,8 @@
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',
]
};
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

@@ -72,7 +72,8 @@ export default function BlurhashedImage({
<View
minHeight={height ?? width}
minWidth={width}
borderRadius={borderRadius ? borderRadius : 25}>
borderRadius={borderRadius ? borderRadius : 25}
>
{isSuccess ? (
<Image
source={{

View File

@@ -24,7 +24,8 @@ export default function Login(): React.JSX.Element {
? 'ServerAuthentication'
: 'LibrarySelection'
}
screenOptions={{ headerShown: false }}>
screenOptions={{ headerShown: false }}
>
<Stack.Screen
name='ServerAddress'
options={{

View File

@@ -50,7 +50,8 @@ export default function ServerLibrary(): React.JSX.Element {
type='single'
disableDeactivation={true}
value={libraryId}
onValueChange={setLibraryId}>
onValueChange={setLibraryId}
>
{libraries!
.filter((library) => library.CollectionType === 'music')
.map((library) => {
@@ -63,7 +64,8 @@ export default function ServerLibrary(): React.JSX.Element {
libraryId == library.Id!
? getToken('$color.purpleGray')
: 'unset'
}>
}
>
<Text>{library.Name ?? 'Unnamed Library'}</Text>
</ToggleGroup.Item>
)
@@ -88,7 +90,8 @@ export default function ServerLibrary(): React.JSX.Element {
playlistLibraryPrimaryImageId: playlistLibrary?.ImageTags!.Primary,
})
setLoggedIn(true)
}}>
}}
>
{`Let's Go!`}
</Button>
@@ -96,7 +99,8 @@ export default function ServerLibrary(): React.JSX.Element {
onPress={() => {
Client.switchUser()
setUser(undefined)
}}>
}}
>
Switch User
</Button>
</SafeAreaView>

View File

@@ -80,7 +80,8 @@ export const JellifyProvider: ({ children }: { children: ReactNode }) => React.J
loggedIn,
setLoggedIn,
carPlayConnected,
}}>
}}
>
{children}
</JellifyContext.Provider>
)

View File

@@ -1,32 +1,32 @@
import { fonts } from "@tamagui/config/v4";
import { createFont } from "tamagui";
import { fonts } from '@tamagui/config/v4'
import { createFont } from 'tamagui'
const aileronFace = {
100: { normal: 'Aileron-UltraLight', italic: 'Aileron UltraLight Italic' },
200: { normal: 'Aileron-Thin', italic: 'Aileron Thin Italic' },
300: { normal: 'Aileron-Light', italic: 'Aileron Light Italic' },
400: { normal: 'Aileron-Regular', italic: 'Aileron Italic'} ,
500: { normal: 'Aileron-Regular', italic: 'Aileron Italic' },
600: { normal: 'Aileron-SemiBold', italic: 'Aileron SemiBold Italic' },
700: { normal: 'Aileron-Bold', italic: 'Aileron Bold Italic' },
800: { normal: 'Aileron-Heavy', italic: 'Aileron Heavy Italic' },
900: { normal: 'Aileron-Black', italic: 'Aileron-BlackItalic' }
};
100: { normal: 'Aileron-UltraLight', italic: 'Aileron UltraLight Italic' },
200: { normal: 'Aileron-Thin', italic: 'Aileron Thin Italic' },
300: { normal: 'Aileron-Light', italic: 'Aileron Light Italic' },
400: { normal: 'Aileron-Regular', italic: 'Aileron Italic' },
500: { normal: 'Aileron-Regular', italic: 'Aileron Italic' },
600: { normal: 'Aileron-SemiBold', italic: 'Aileron SemiBold Italic' },
700: { normal: 'Aileron-Bold', italic: 'Aileron Bold Italic' },
800: { normal: 'Aileron-Heavy', italic: 'Aileron Heavy Italic' },
900: { normal: 'Aileron-Black', italic: 'Aileron-BlackItalic' },
}
export const bodyFont = createFont({
family: "Aileron-Bold",
size: fonts.body.size,
lineHeight: fonts.body.lineHeight,
weight: fonts.body.weight,
letterSpacing: fonts.body.letterSpacing,
face: aileronFace
family: 'Aileron-Bold',
size: fonts.body.size,
lineHeight: fonts.body.lineHeight,
weight: fonts.body.weight,
letterSpacing: fonts.body.letterSpacing,
face: aileronFace,
})
export const headingFont = createFont({
family: "Aileron-Black",
size: fonts.heading.size,
lineHeight: fonts.heading.lineHeight,
weight: fonts.heading.weight,
letterSpacing: fonts.heading.letterSpacing,
face: aileronFace
})
family: 'Aileron-Black',
size: fonts.heading.size,
lineHeight: fonts.heading.lineHeight,
weight: fonts.heading.weight,
letterSpacing: fonts.heading.letterSpacing,
face: aileronFace,
})

View File

@@ -1 +1 @@
// Don't import react-native-gesture-handler on web
// Don't import react-native-gesture-handler on web

View File

@@ -1,2 +1,2 @@
// Only import react-native-gesture-handler on native platforms
import 'react-native-gesture-handler';
import 'react-native-gesture-handler'

View File

@@ -1,19 +1,20 @@
import 'react-native-gesture-handler';
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import 'react-native-gesture-handler'
import { AppRegistry } from 'react-native'
import App from './App'
import { name as appName } from './app.json'
import { PlaybackService } from './player/service'
import TrackPlayer from 'react-native-track-player';
import Client from './api/client';
import TrackPlayer from 'react-native-track-player'
import Client from './api/client'
// Initialize API client instance
Client.instance;
/* eslint-disable @typescript-eslint/no-unused-expressions */
Client.instance
// Enable React Navigation freeze for detaching inactive screens
// enableFreeze();
AppRegistry.registerComponent(appName, () => App);
AppRegistry.registerComponent('RNCarPlayScene', () => App);
AppRegistry.registerComponent(appName, () => App)
AppRegistry.registerComponent('RNCarPlayScene', () => App)
// Register RNTP playback service for remote controls
TrackPlayer.registerPlaybackService(() => PlaybackService);
TrackPlayer.registerPlaybackService(() => PlaybackService)

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
"info": {
"version": 1,
"author": "xcode"
}
}

View File

@@ -1,5 +1,4 @@
fastlane documentation
----
## fastlane documentation
# Installation
@@ -23,7 +22,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
Push a new beta build to TestFlight
----
---
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.

View File

@@ -1,69 +1,69 @@
{
"migIndex": 1,
"data": [
{
"path": "assets/fonts/Aileron-Black.otf",
"sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999"
},
{
"path": "assets/fonts/Aileron-BlackItalic.otf",
"sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73"
},
{
"path": "assets/fonts/Aileron-Bold.otf",
"sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a"
},
{
"path": "assets/fonts/Aileron-BoldItalic.otf",
"sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e"
},
{
"path": "assets/fonts/Aileron-Heavy.otf",
"sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6"
},
{
"path": "assets/fonts/Aileron-HeavyItalic.otf",
"sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259"
},
{
"path": "assets/fonts/Aileron-Italic.otf",
"sha1": "338b043581d997314a4a03924ed30ff6461fd37e"
},
{
"path": "assets/fonts/Aileron-Light.otf",
"sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad"
},
{
"path": "assets/fonts/Aileron-LightItalic.otf",
"sha1": "48a4355b8792657845b3b0cd39c42994923a117a"
},
{
"path": "assets/fonts/Aileron-Regular.otf",
"sha1": "5a78965873fbce38941cd3da109280af89a42de5"
},
{
"path": "assets/fonts/Aileron-SemiBold.otf",
"sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824"
},
{
"path": "assets/fonts/Aileron-SemiBoldItalic.otf",
"sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f"
},
{
"path": "assets/fonts/Aileron-Thin.otf",
"sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c"
},
{
"path": "assets/fonts/Aileron-ThinItalic.otf",
"sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214"
},
{
"path": "assets/fonts/Aileron-UltraLight.otf",
"sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24"
},
{
"path": "assets/fonts/Aileron-UltraLightItalic.otf",
"sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0"
}
]
"migIndex": 1,
"data": [
{
"path": "assets/fonts/Aileron-Black.otf",
"sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999"
},
{
"path": "assets/fonts/Aileron-BlackItalic.otf",
"sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73"
},
{
"path": "assets/fonts/Aileron-Bold.otf",
"sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a"
},
{
"path": "assets/fonts/Aileron-BoldItalic.otf",
"sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e"
},
{
"path": "assets/fonts/Aileron-Heavy.otf",
"sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6"
},
{
"path": "assets/fonts/Aileron-HeavyItalic.otf",
"sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259"
},
{
"path": "assets/fonts/Aileron-Italic.otf",
"sha1": "338b043581d997314a4a03924ed30ff6461fd37e"
},
{
"path": "assets/fonts/Aileron-Light.otf",
"sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad"
},
{
"path": "assets/fonts/Aileron-LightItalic.otf",
"sha1": "48a4355b8792657845b3b0cd39c42994923a117a"
},
{
"path": "assets/fonts/Aileron-Regular.otf",
"sha1": "5a78965873fbce38941cd3da109280af89a42de5"
},
{
"path": "assets/fonts/Aileron-SemiBold.otf",
"sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824"
},
{
"path": "assets/fonts/Aileron-SemiBoldItalic.otf",
"sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f"
},
{
"path": "assets/fonts/Aileron-Thin.otf",
"sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c"
},
{
"path": "assets/fonts/Aileron-ThinItalic.otf",
"sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214"
},
{
"path": "assets/fonts/Aileron-UltraLight.otf",
"sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24"
},
{
"path": "assets/fonts/Aileron-UltraLightItalic.otf",
"sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0"
}
]
}

View File

@@ -1,16 +1,16 @@
// https://docs.swmansion.com/react-native-gesture-handler/docs/guides/testing
module.exports = {
preset: 'jest-expo',
setupFiles: ["./node_modules/react-native-gesture-handler/jestSetup.js"],
setupFilesAfterEnv: [
"./jest/setup.js",
"./jest/setup-carplay.js",
"./jest/setup-blurhash.js",
"./jest/setup-reanimated.js",
"./tamagui.config.ts",
],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
'node_modules/(?!(@)?(react-native|react-native-.*|react-navigation|jellyfin|burnt|expo|expo-.*)/)',
],
};
preset: 'jest-expo',
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
setupFilesAfterEnv: [
'./jest/setup.js',
'./jest/setup-carplay.js',
'./jest/setup-blurhash.js',
'./jest/setup-reanimated.js',
'./tamagui.config.ts',
],
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
'node_modules/(?!(@)?(react-native|react-native-.*|react-navigation|jellyfin|burnt|expo|expo-.*)/)',
],
}

View File

@@ -2,16 +2,16 @@
* @format
*/
import 'react-native';
import React from 'react';
import App from '../App';
import 'react-native'
import React from 'react'
import App from '../App'
// Note: import explicitly to use the types shipped with jest.
import {it} from '@jest/globals';
import { it } from '@jest/globals'
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
import renderer from 'react-test-renderer'
it('renders correctly', () => {
renderer.create(<App />);
});
renderer.create(<App />)
})

View File

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

View File

@@ -1,7 +1,7 @@
jest.mock('react-native-carplay', () => {
return {
default: {
checkForConnection: jest.fn()
}
}
})
return {
default: {
checkForConnection: jest.fn(),
},
}
})

View File

@@ -1,6 +1,6 @@
jest.mock('react-native-reanimated', () => ({
...jest.requireActual('react-native-reanimated/mock'),
createAnimatedPropAdapter: jest.fn,
useReducedMotion: jest.fn,
LayoutAnimationConfig: jest.fn,
}));
...jest.requireActual('react-native-reanimated/mock'),
createAnimatedPropAdapter: jest.fn,
useReducedMotion: jest.fn,
LayoutAnimationConfig: jest.fn,
}))

View File

@@ -1,110 +1,109 @@
// https://github.com/react-native-device-info/react-native-device-info/issues/1360
import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock';
import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
jest.mock('react-native-device-info', () => mockRNDeviceInfo);
jest.mock('react-native-device-info', () => mockRNDeviceInfo)
jest.mock('react-native-haptic-feedback', () => {
return {
default: {
trigger: jest.fn()
}
}
});
return {
default: {
trigger: jest.fn(),
},
}
})
jest.mock('burnt', () => {
return {
default: {
alert: jest.fn()
}
}
return {
default: {
alert: jest.fn(),
},
}
})
// https://github.com/doublesymmetry/react-native-track-player/issues/501
jest.mock('react-native-track-player', () => {
return {
__esModule: true,
default: {
addEventListener: () => ({
remove: jest.fn(),
}),
registerEventHandler: jest.fn(),
registerPlaybackService: jest.fn(),
setupPlayer: jest.fn().mockResolvedValue(undefined),
destroy: jest.fn(),
updateOptions: jest.fn(),
reset: jest.fn(),
add: jest.fn(),
remove: jest.fn(),
skip: jest.fn(),
skipToNext: jest.fn(),
skipToPrevious: jest.fn(),
removeUpcomingTracks: jest.fn(),
// playback commands
play: jest.fn(),
pause: jest.fn(),
stop: jest.fn(),
seekTo: jest.fn(),
setVolume: jest.fn(),
setRate: jest.fn(),
// player getters
getQueue: jest.fn(),
getTrack: jest.fn(),
getActiveTrackIndex: jest.fn(),
getCurrentTrack: jest.fn(),
getVolume: jest.fn(),
getDuration: jest.fn(),
getPosition: jest.fn(),
getBufferedPosition: jest.fn(),
getState: jest.fn(),
getRate: jest.fn(),
},
useProgress: () => ({
position: 100,
buffered: 150,
duration: 200,
}),
Capability: {
Play: 1,
PlayFromId: 2,
PlayFromSearch: 4,
Pause: 8,
Stop: 16,
SeekTo: 32,
Skip: 64,
SkipToNext: 128,
SkipToPrevious: 256,
},
IOSCategoryOptions: {
MixWithOthers: 'mixWithOthers',
DuckOthers: 'duckOthers',
InterruptSpokenAudioAndMixWithOthers:
'interruptSpokenAudioAndMixWithOthers',
AllowBluetooth: 'allowBluetooth',
AllowBluetoothA2DP: 'allowBluetoothA2DP',
AllowAirPlay: 'allowAirPlay',
DefaultToSpeaker: 'defaultToSpeaker',
},
IOSCategoryMode: {
Default: 'default',
GameChat: 'gameChat',
Measurement: 'measurement',
MoviePlayback: 'moviePlayback',
SpokenAudio: 'spokenAudio',
VideoChat: 'videoChat',
VideoRecording: 'videoRecording',
VoiceChat: 'voiceChat',
VoicePrompt: 'voicePrompt',
},
IOSCategory: {
Playback: 'playback',
PlaybackAndRecord: 'playbackAndRecord',
MultiRoute: 'multiRoute',
Ambient: 'ambient',
SoloAmbient: 'soloAmbient',
Record: 'record',
PlayAndRecord: 'playAndRecord',
},
}
});
return {
__esModule: true,
default: {
addEventListener: () => ({
remove: jest.fn(),
}),
registerEventHandler: jest.fn(),
registerPlaybackService: jest.fn(),
setupPlayer: jest.fn().mockResolvedValue(undefined),
destroy: jest.fn(),
updateOptions: jest.fn(),
reset: jest.fn(),
add: jest.fn(),
remove: jest.fn(),
skip: jest.fn(),
skipToNext: jest.fn(),
skipToPrevious: jest.fn(),
removeUpcomingTracks: jest.fn(),
// playback commands
play: jest.fn(),
pause: jest.fn(),
stop: jest.fn(),
seekTo: jest.fn(),
setVolume: jest.fn(),
setRate: jest.fn(),
// player getters
getQueue: jest.fn(),
getTrack: jest.fn(),
getActiveTrackIndex: jest.fn(),
getCurrentTrack: jest.fn(),
getVolume: jest.fn(),
getDuration: jest.fn(),
getPosition: jest.fn(),
getBufferedPosition: jest.fn(),
getState: jest.fn(),
getRate: jest.fn(),
},
useProgress: () => ({
position: 100,
buffered: 150,
duration: 200,
}),
Capability: {
Play: 1,
PlayFromId: 2,
PlayFromSearch: 4,
Pause: 8,
Stop: 16,
SeekTo: 32,
Skip: 64,
SkipToNext: 128,
SkipToPrevious: 256,
},
IOSCategoryOptions: {
MixWithOthers: 'mixWithOthers',
DuckOthers: 'duckOthers',
InterruptSpokenAudioAndMixWithOthers: 'interruptSpokenAudioAndMixWithOthers',
AllowBluetooth: 'allowBluetooth',
AllowBluetoothA2DP: 'allowBluetoothA2DP',
AllowAirPlay: 'allowAirPlay',
DefaultToSpeaker: 'defaultToSpeaker',
},
IOSCategoryMode: {
Default: 'default',
GameChat: 'gameChat',
Measurement: 'measurement',
MoviePlayback: 'moviePlayback',
SpokenAudio: 'spokenAudio',
VideoChat: 'videoChat',
VideoRecording: 'videoRecording',
VoiceChat: 'voiceChat',
VoicePrompt: 'voicePrompt',
},
IOSCategory: {
Playback: 'playback',
PlaybackAndRecord: 'playbackAndRecord',
MultiRoute: 'multiRoute',
Ambient: 'ambient',
SoloAmbient: 'soloAmbient',
Record: 'record',
PlayAndRecord: 'playAndRecord',
},
}
})

View File

@@ -2,18 +2,14 @@
const { getDefaultConfig } = require('@react-native/metro-config')
const config = getDefaultConfig(__dirname, {
// [Web-only]: Enables CSS support in Metro.
isCSSEnabled: true,
// [Web-only]: Enables CSS support in Metro.
isCSSEnabled: true,
})
// Expo 49 issue: default metro config needs to include "mjs"
// https://github.com/expo/expo/issues/23180
config.resolver.sourceExts.push('mjs')
config.watchFolders = [
"components",
"api",
"player"
]
config.watchFolders = ['components', 'api', 'player']
module.exports = config;
module.exports = config

View File

@@ -1,11 +1,9 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
export class ArtistModel {
name?: string | undefined | null
name?: string | undefined | null;
constructor(itemDto : BaseItemDto) {
this.name = itemDto.Name
}
}
constructor(itemDto: BaseItemDto) {
this.name = itemDto.Name
}
}

43398
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +1,114 @@
{
"name": "jellify",
"version": "0.10.91",
"private": true,
"scripts": {
"init": "npm i",
"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: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"
},
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^15.1.3",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.1.1",
"@react-navigation/stack": "^7.1.0",
"@tamagui/config": "^1.124.17",
"@tamagui/toast": "^1.124.17",
"@tanstack/query-sync-storage-persister": "^5.66.0",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-persist-client": "^5.66.0",
"axios": "^1.7.9",
"bundle": "^2.1.0",
"bundler": "^0.8.0",
"burnt": "^0.12.2",
"expo": "^52.0.0",
"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",
"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.5",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.1",
"react-native-file-access": "^3.1.1",
"react-native-gesture-handler": "^2.23.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "^2.12.2",
"react-native-reanimated": "^3.17.2",
"react-native-safe-area-context": "^5.2.0",
"react-native-screens": "^4.6.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.124.17"
},
"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.10.91",
"private": true,
"scripts": {
"init": "npm i",
"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: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"
},
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^15.1.3",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.1.1",
"@react-navigation/stack": "^7.1.0",
"@tamagui/config": "^1.124.17",
"@tamagui/toast": "^1.124.17",
"@tanstack/query-sync-storage-persister": "^5.66.0",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-persist-client": "^5.66.0",
"axios": "^1.7.9",
"bundle": "^2.1.0",
"bundler": "^0.8.0",
"burnt": "^0.12.2",
"expo": "^52.0.0",
"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",
"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.5",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.1",
"react-native-file-access": "^3.1.1",
"react-native-gesture-handler": "^2.23.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "^2.12.2",
"react-native-reanimated": "^3.17.2",
"react-native-safe-area-context": "^5.2.0",
"react-native-screens": "^4.6.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.124.17"
},
"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"
}
}

View File

@@ -1 +1 @@
export const UPDATE_INTERVAL: number = 7 // 8 milliseconds
export const UPDATE_INTERVAL: number = 7 // 8 milliseconds

View File

@@ -1,14 +1,14 @@
import { Capability } from "react-native-track-player";
import { Capability } from 'react-native-track-player'
export const CAPABILITIES: Capability[] = [
Capability.Pause,
Capability.Play,
Capability.PlayFromId,
Capability.SeekTo,
// Capability.JumpForward,
// Capability.JumpBackward,
Capability.SkipToNext,
Capability.SkipToPrevious,
// Capability.Like,
// Capability.Dislike
]
Capability.Pause,
Capability.Play,
Capability.PlayFromId,
Capability.SeekTo,
// Capability.JumpForward,
// Capability.JumpBackward,
Capability.SkipToNext,
Capability.SkipToPrevious,
// Capability.Like,
// Capability.Dislike
]

View File

@@ -1,61 +1,71 @@
import { Progress, State } from "react-native-track-player";
import { JellifyTrack } from "../types/JellifyTrack";
import { PlaystateApi } from "@jellyfin/sdk/lib/generated-client/api/playstate-api";
import { convertSecondsToRunTimeTicks } from "../helpers/runtimeticks";
import { Progress, State } from 'react-native-track-player'
import { JellifyTrack } from '../types/JellifyTrack'
import { PlaystateApi } from '@jellyfin/sdk/lib/generated-client/api/playstate-api'
import { convertSecondsToRunTimeTicks } from '../helpers/runtimeticks'
export async function handlePlaybackState(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, state: State) {
switch (state) {
case (State.Playing) : {
console.debug("Report playback started")
await playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
}
});
break;
}
case (State.Ended) :
case (State.Paused) :
case (State.Stopped) : {
console.debug("Report playback stopped")
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
}
});
break;
}
export async function handlePlaybackState(
sessionId: string,
playstateApi: PlaystateApi,
track: JellifyTrack,
state: State,
) {
switch (state) {
case State.Playing: {
console.debug('Report playback started')
await playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
},
})
break
}
default : {
return;
}
}
case State.Ended:
case State.Paused:
case State.Stopped: {
console.debug('Report playback stopped')
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
},
})
break
}
default: {
return
}
}
}
export async function handlePlaybackProgressUpdated(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, progress: Progress) {
if (Math.floor(progress.duration - progress.position) === 5) {
console.debug("Track finished, scrobbling...");
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
PositionTicks: convertSecondsToRunTimeTicks(track.duration!)
}
});
} else {
// DO NOTHING, reporting playback will just eat up power
// Jellyfin can keep track of progress, we're going to intentionally
// only give it the "greatest hits" (i.e., anything that involves user interaction)
// console.debug("Reporting playback position");
// await playstateApi.reportPlaybackProgress({
// playbackProgressInfo: {
// SessionId: sessionId,
// ItemId: track.ItemId,
// PositionTicks: convertSecondsToRunTimeTicks(progress.position)
// }
// });
}
export async function handlePlaybackProgressUpdated(
sessionId: string,
playstateApi: PlaystateApi,
track: JellifyTrack,
progress: Progress,
) {
if (Math.floor(progress.duration - progress.position) === 5) {
console.debug('Track finished, scrobbling...')
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
PositionTicks: convertSecondsToRunTimeTicks(track.duration!),
},
})
} else {
// DO NOTHING, reporting playback will just eat up power
// Jellyfin can keep track of progress, we're going to intentionally
// only give it the "greatest hits" (i.e., anything that involves user interaction)
// console.debug("Reporting playback position");
// await playstateApi.reportPlaybackProgress({
// playbackProgressInfo: {
// SessionId: sessionId,
// ItemId: track.ItemId,
// PositionTicks: convertSecondsToRunTimeTicks(progress.position)
// }
// });
}
}

View File

@@ -1,7 +1,7 @@
import { isEmpty } from "lodash";
import { QueuingType } from "../../enums/queuing-type";
import { JellifyTrack } from "../../types/JellifyTrack";
import { getActiveTrackIndex } from "react-native-track-player/lib/src/trackPlayer";
import { isEmpty } from 'lodash'
import { QueuingType } from '../../enums/queuing-type'
import { JellifyTrack } from '../../types/JellifyTrack'
import { getActiveTrackIndex } from 'react-native-track-player/lib/src/trackPlayer'
/**
* Finds and returns the index of the player queue to insert additional tracks into
@@ -9,29 +9,26 @@ import { getActiveTrackIndex } from "react-native-track-player/lib/src/trackPlay
* @returns The index to insert songs to play next at
*/
export const findPlayNextIndexStart = async (playQueue: JellifyTrack[]) => {
if (isEmpty(playQueue))
return 0;
if (isEmpty(playQueue)) return 0
return (await getActiveTrackIndex())! + 1;
return (await getActiveTrackIndex())! + 1
}
/**
* Finds and returns the index of the play queue to insert user queue tracks into
* Finds and returns the index of the play queue to insert user queue tracks into
* @param playQueue The current player queue
* @returns The index to insert songs to add to the user queue
*/
export const findPlayQueueIndexStart = async (playQueue: JellifyTrack[]) => {
if (isEmpty(playQueue)) return 0
if (isEmpty(playQueue))
return 0;
const activeIndex = await getActiveTrackIndex()
const activeIndex = await getActiveTrackIndex();
if (playQueue.findIndex((track) => track.QueuingType === QueuingType.FromSelection) === -1)
return activeIndex! + 1
if (playQueue.findIndex(track => track.QueuingType === QueuingType.FromSelection) === -1)
return activeIndex! + 1
return playQueue.findIndex((queuedTrack, index) =>
queuedTrack.QueuingType === QueuingType.FromSelection &&
index > activeIndex!
);
}
return playQueue.findIndex(
(queuedTrack, index) =>
queuedTrack.QueuingType === QueuingType.FromSelection && index > activeIndex!,
)
}

View File

@@ -1,19 +1,21 @@
import _ from "lodash";
import { JellifyTrack } from "../../types/JellifyTrack";
import _ from 'lodash'
import { JellifyTrack } from '../../types/JellifyTrack'
export function buildNewQueue(existingQueue: JellifyTrack[], tracksToInsert: JellifyTrack[], insertIndex: number) {
export function buildNewQueue(
existingQueue: JellifyTrack[],
tracksToInsert: JellifyTrack[],
insertIndex: number,
) {
console.debug(`Building new queue`)
console.debug(`Building new queue`);
let newQueue: JellifyTrack[] = []
let newQueue : JellifyTrack[] = [];
if (_.isEmpty(existingQueue)) newQueue = tracksToInsert
else {
newQueue = _.cloneDeep(existingQueue).splice(insertIndex, 0, ...tracksToInsert)
}
if (_.isEmpty(existingQueue))
newQueue = tracksToInsert;
else {
newQueue = _.cloneDeep(existingQueue).splice(insertIndex, 0, ...tracksToInsert);
}
console.debug(`Built new queue of ${newQueue.length} items`)
console.debug(`Built new queue of ${newQueue.length} items`);
return newQueue;
}
return newQueue
}

View File

@@ -1,20 +1,20 @@
import TrackPlayer, { RatingType } from "react-native-track-player"
import { CAPABILITIES } from "../constants";
import TrackPlayer, { RatingType } from 'react-native-track-player'
import { CAPABILITIES } from '../constants'
export const useUpdateOptions = async (isFavorite: boolean) => {
return await TrackPlayer.updateOptions({
progressUpdateEventInterval: 1,
capabilities: CAPABILITIES,
notificationCapabilities: CAPABILITIES,
compactCapabilities: CAPABILITIES,
ratingType: RatingType.Heart,
likeOptions: {
isActive: isFavorite,
title: "Favorite"
},
dislikeOptions: {
isActive: !isFavorite,
title: "Unfavorite"
}
});
}
return await TrackPlayer.updateOptions({
progressUpdateEventInterval: 1,
capabilities: CAPABILITIES,
notificationCapabilities: CAPABILITIES,
compactCapabilities: CAPABILITIES,
ratingType: RatingType.Heart,
likeOptions: {
isActive: isFavorite,
title: 'Favorite',
},
dislikeOptions: {
isActive: !isFavorite,
title: 'Unfavorite',
},
})
}

View File

@@ -1,23 +1,23 @@
import { JellifyTrack } from "../types/JellifyTrack";
import { QueuingType } from "../enums/queuing-type";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { Queue } from "./types/queue-item";
import { JellifyTrack } from '../types/JellifyTrack'
import { QueuingType } from '../enums/queuing-type'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { Queue } from './types/queue-item'
export interface QueueMutation {
track: BaseItemDto;
index?: number | undefined;
tracklist: BaseItemDto[];
queue: Queue;
queuingType?: QueuingType | undefined;
export interface QueueMutation {
track: BaseItemDto
index?: number | undefined
tracklist: BaseItemDto[]
queue: Queue
queuingType?: QueuingType | undefined
}
export interface AddToQueueMutation {
track: BaseItemDto,
queuingType?: QueuingType | undefined;
track: BaseItemDto
queuingType?: QueuingType | undefined
}
export interface QueueOrderMutation {
newOrder: JellifyTrack[];
from: number;
to: number;
}
newOrder: JellifyTrack[]
from: number
to: number
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +1,66 @@
import Client from "../api/client";
import { JellifyTrack } from "../types/JellifyTrack";
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api";
import TrackPlayer, { Event, RatingType } from "react-native-track-player";
import { getActiveTrack, getActiveTrackIndex } from "react-native-track-player/lib/src/trackPlayer";
import Client from '../api/client'
import { JellifyTrack } from '../types/JellifyTrack'
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api'
import TrackPlayer, { Event, RatingType } from 'react-native-track-player'
import { getActiveTrack, getActiveTrackIndex } from 'react-native-track-player/lib/src/trackPlayer'
/**
* Jellify Playback Service.
*
*
* Sets up event listeners for remote control events and
* runs for the duration of the app lifecycle
*/
export async function PlaybackService() {
TrackPlayer.addEventListener(Event.RemotePlay, async () => {
await TrackPlayer.play()
})
TrackPlayer.addEventListener(Event.RemotePause, async () => {
await TrackPlayer.pause()
})
TrackPlayer.addEventListener(Event.RemotePlay, async () => {
await TrackPlayer.play();
});
TrackPlayer.addEventListener(Event.RemotePause, async () => {
await TrackPlayer.pause();
});
TrackPlayer.addEventListener(Event.RemoteNext, async () => {
await TrackPlayer.skipToNext()
})
TrackPlayer.addEventListener(Event.RemoteNext, async () => {
await TrackPlayer.skipToNext();
});
TrackPlayer.addEventListener(Event.RemotePrevious, async () => {
await TrackPlayer.skipToPrevious()
})
TrackPlayer.addEventListener(Event.RemotePrevious, async () => {
await TrackPlayer.skipToPrevious();
});
TrackPlayer.addEventListener(Event.RemoteSeek, async (event) => {
await TrackPlayer.seekTo(event.position)
})
TrackPlayer.addEventListener(Event.RemoteSeek, async (event) => {
await TrackPlayer.seekTo(event.position);
});
// TrackPlayer.addEventListener(Event.RemoteJumpForward, async (event) => {
// await TrackPlayer.seekBy(event.interval)
// });
// TrackPlayer.addEventListener(Event.RemoteJumpForward, async (event) => {
// await TrackPlayer.seekBy(event.interval)
// });
// TrackPlayer.addEventListener(Event.RemoteJumpBackward, async (event) => {
// await TrackPlayer.seekBy(-event.interval)
// });
// TrackPlayer.addEventListener(Event.RemoteJumpBackward, async (event) => {
// await TrackPlayer.seekBy(-event.interval)
// });
TrackPlayer.addEventListener(Event.RemoteLike, async () => {
const nowPlaying = (await getActiveTrack()) as JellifyTrack
const nowPlayingIndex = await getActiveTrackIndex()
TrackPlayer.addEventListener(Event.RemoteLike, async () => {
await getUserLibraryApi(Client.api!).markFavoriteItem({
itemId: nowPlaying.item.Id!,
})
const nowPlaying = await getActiveTrack() as JellifyTrack;
const nowPlayingIndex = await getActiveTrackIndex();
await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, {
rating: RatingType.Heart,
})
})
await getUserLibraryApi(Client.api!)
.markFavoriteItem({
itemId: nowPlaying.item.Id!
});
TrackPlayer.addEventListener(Event.RemoteDislike, async () => {
const nowPlaying = (await getActiveTrack()) as JellifyTrack
const nowPlayingIndex = await getActiveTrackIndex()
await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, {
rating: RatingType.Heart
})
});
await getUserLibraryApi(Client.api!).markFavoriteItem({
itemId: nowPlaying.item.Id!,
})
TrackPlayer.addEventListener(Event.RemoteDislike, async () => {
const nowPlaying = await getActiveTrack() as JellifyTrack;
const nowPlayingIndex = await getActiveTrackIndex();
await getUserLibraryApi(Client.api!)
.markFavoriteItem({
itemId: nowPlaying.item.Id!
});
await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, {
rating: undefined
});
});
}
await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, {
rating: undefined,
})
})
}

View File

@@ -1,3 +1,3 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
export type Queue = BaseItemDto | "Recently Played" | "Search" | "Favorite Tracks";
export type Queue = BaseItemDto | 'Recently Played' | 'Search' | 'Favorite Tracks'

View File

@@ -1,8 +1,8 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"
import { QueuingType } from "../../enums/queuing-type"
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { QueuingType } from '../../enums/queuing-type'
export interface QueuingRequest {
song: BaseItemDto
queuingType: QueuingType
atIndex?: number
}
song: BaseItemDto
queuingType: QueuingType
atIndex?: number
}

View File

@@ -1,14 +1,14 @@
module.exports = {
project: {
ios: {},
android: {},
},
assets: ['./assets/fonts/'],
dependencies: {
'react-native-carplay': {
platforms: {
android: null, // Disable autolinking for Android
},
},
},
};
project: {
ios: {},
android: {},
},
assets: ['./assets/fonts/'],
dependencies: {
'react-native-carplay': {
platforms: {
android: null, // Disable autolinking for Android
},
},
},
}

View File

@@ -1,6 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"]
}

View File

@@ -3,66 +3,66 @@ import { createTamagui, createTokens } from 'tamagui' // or '@tamagui/core'
import { headingFont, bodyFont } from './fonts.config'
const tokens = createTokens({
...TamaguiTokens,
color: {
danger: "#ff0000",
purpleDark: "#0C0622",
purple: "#100538",
purpleGray: "#66617B",
amethyst: "#7E72AF",
grape: "#5638BB",
telemagenta: "#cc2f71",
white: "#ffffff",
black: "#000000"
},
...TamaguiTokens,
color: {
danger: '#ff0000',
purpleDark: '#0C0622',
purple: '#100538',
purpleGray: '#66617B',
amethyst: '#7E72AF',
grape: '#5638BB',
telemagenta: '#cc2f71',
white: '#ffffff',
black: '#000000',
},
})
const jellifyConfig = createTamagui({
animations,
fonts:{
heading: headingFont,
body: bodyFont,
},
media,
shorthands,
tokens,
themes: {
dark: {
shadowColor: tokens.color.purple,
background: tokens.color.purpleDark,
backgroundActive: tokens.color.amethyst,
backgroundPress: tokens.color.amethyst,
backgroundFocus: tokens.color.amethyst,
backgroundHover: tokens.color.purpleGray,
borderColor: tokens.color.amethyst,
color: tokens.color.white
},
dark_inverted_purple: {
color: tokens.color.purpleDark,
borderColor: tokens.color.amethyst,
background: tokens.color.amethyst
},
light: {
background: tokens.color.white,
backgroundActive: tokens.color.amethyst,
borderColor: tokens.color.purpleGray,
color: tokens.color.purpleDark
},
light_inverted_purple: {
color: tokens.color.purpleDark,
borderColor: tokens.color.purpleDark,
background: tokens.color.purpleGray
}
}
});
animations,
fonts: {
heading: headingFont,
body: bodyFont,
},
media,
shorthands,
tokens,
themes: {
dark: {
shadowColor: tokens.color.purple,
background: tokens.color.purpleDark,
backgroundActive: tokens.color.amethyst,
backgroundPress: tokens.color.amethyst,
backgroundFocus: tokens.color.amethyst,
backgroundHover: tokens.color.purpleGray,
borderColor: tokens.color.amethyst,
color: tokens.color.white,
},
dark_inverted_purple: {
color: tokens.color.purpleDark,
borderColor: tokens.color.amethyst,
background: tokens.color.amethyst,
},
light: {
background: tokens.color.white,
backgroundActive: tokens.color.amethyst,
borderColor: tokens.color.purpleGray,
color: tokens.color.purpleDark,
},
light_inverted_purple: {
color: tokens.color.purpleDark,
borderColor: tokens.color.purpleDark,
background: tokens.color.purpleGray,
},
},
})
export type JellifyConfig = typeof jellifyConfig
declare module 'tamagui' {
// or '@tamagui/core'
// overrides TamaguiCustomConfig so your custom types
// work everywhere you import `tamagui`
interface TamaguiCustomConfig extends JellifyConfig {}
// or '@tamagui/core'
// overrides TamaguiCustomConfig so your custom types
// work everywhere you import `tamagui`
interface TamaguiCustomConfig extends JellifyConfig {}
}
export default jellifyConfig
export default jellifyConfig

View File

@@ -1,10 +1,8 @@
{
"extends": "@react-native/typescript-config/tsconfig.json",
"compilerOptions": {
"paths": {
"@/*": [
"./*"
]
}
}
}
"extends": "@react-native/typescript-config/tsconfig.json",
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}

View File

@@ -1,8 +1,7 @@
export interface JellifyLibrary {
musicLibraryId: string;
musicLibraryName?: string | undefined;
musicLibraryPrimaryImageId?: string | undefined;
playlistLibraryId?: string | undefined;
playlistLibraryPrimaryImageId?: string | undefined;
}
musicLibraryId: string
musicLibraryName?: string | undefined
musicLibraryPrimaryImageId?: string | undefined
playlistLibraryId?: string | undefined
playlistLibraryPrimaryImageId?: string | undefined
}

View File

@@ -1,7 +1,7 @@
export interface JellifyServer {
url: string;
address: string;
name: string;
version: string;
startUpComplete: boolean;
}
url: string
address: string
name: string
version: string
startUpComplete: boolean
}

View File

@@ -1,32 +1,34 @@
import { PitchAlgorithm, RatingType, Track, TrackType } from "react-native-track-player"
import { QueuingType } from "../enums/queuing-type";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { PitchAlgorithm, RatingType, Track, TrackType } from 'react-native-track-player'
import { QueuingType } from '../enums/queuing-type'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
export interface JellifyTrack extends Track {
url: string;
type?: TrackType | undefined;
userAgent?: string | undefined;
contentType?: string | undefined;
pitchAlgorithm?: PitchAlgorithm | undefined;
headers?: { [key: string]: any; } | undefined;
url: string
type?: TrackType | undefined
userAgent?: string | undefined
contentType?: string | undefined
pitchAlgorithm?: PitchAlgorithm | undefined
title?: string | undefined;
album?: string | undefined;
artist?: string | undefined;
duration?: number | undefined;
artwork?: string | undefined;
description?: string | undefined;
genre?: string | undefined;
date?: string | undefined;
rating?: RatingType | undefined;
isLiveStream?: boolean | undefined;
/* eslint-disable @typescript-eslint/no-explicit-any */
headers?: { [key: string]: any } | undefined
item: BaseItemDto;
title?: string | undefined
album?: string | undefined
artist?: string | undefined
duration?: number | undefined
artwork?: string | undefined
description?: string | undefined
genre?: string | undefined
date?: string | undefined
rating?: RatingType | undefined
isLiveStream?: boolean | undefined
/**
* Represents the type of queuing for this song, be it that it was
* queued from the selection chosen, queued by the user directly, or marked
* to play next by the user
*/
QueuingType?: QueuingType | undefined ;
}
item: BaseItemDto
/**
* Represents the type of queuing for this song, be it that it was
* queued from the selection chosen, queued by the user directly, or marked
* to play next by the user
*/
QueuingType?: QueuingType | undefined
}

View File

@@ -1,5 +1,5 @@
export interface JellifyUser {
id: string;
name: string;
accessToken: string;
}
id: string
name: string
accessToken: string
}