[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: { env: {
browser: true, browser: true,
node: true, node: true,
jest: true,
}, },
rules: { rules: {
'react/react-in-jsx-scope': 'off', 'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'react/prop-types': 'off', 'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'error', // Disallow usage of any '@typescript-eslint/no-explicit-any': 'error', // Disallow usage of any
'no-mixed-spaces-and-tabs': 'off', // refer https://github.com/prettier/prettier/issues/4199 '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 App Icon](assets/icon_dark_60pt_3x.png)
# 🪼 Jellify # 🪼 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) [![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) [Discord Server](https://discord.gg/yf8fBatktn)
[TestFlight](https://testflight.apple.com/join/etVSc7ZQ) [TestFlight](https://testflight.apple.com/join/etVSc7ZQ)
### About ### About
> **jellify** (verb) - *to make gelatinous* <br> > **jellify** (verb) - _to make gelatinous_ <br>
[see also](https://www.merriam-webster.com/dictionary/jellify) > [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 ### 🤓 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 ## 💡 Features
### ✨ Current ### ✨ Current
- Available via Testflight and Android APK
- APKs are associated with each [release](https://github.com/anultravioletaurora/Jellify/releases) - Available via Testflight and Android APK
- Light and Dark modes - APKs are associated with each [release](https://github.com/anultravioletaurora/Jellify/releases)
- Home screen access to previously played tracks, artists, and your playlists - Light and Dark modes
- Quick access to similar artists and items for discovering music in your library - Home screen access to previously played tracks, artists, and your playlists
- Jellyfin playback reporting and [Last.FM Plugin](https://github.com/jesseward/jellyfin-plugin-lastfm) support - Quick access to similar artists and items for discovering music in your library
- Library of Favorited Music, not too dissimilar to how streaming services handle your 'library' - Jellyfin playback reporting and [Last.FM Plugin](https://github.com/jesseward/jellyfin-plugin-lastfm) support
- Full playlist support, including creating, updating, and reordering - Library of Favorited Music, not too dissimilar to how streaming services handle your 'library'
- Full playlist support, including creating, updating, and reordering
### 🛠 Roadmap ### 🛠 Roadmap
- [Offline Playback](https://github.com/anultravioletaurora/Jellify/issues/10)
- [CarPlay / Android Auto Support](https://github.com/anultravioletaurora/Jellify/issues/5) - [Offline Playback](https://github.com/anultravioletaurora/Jellify/issues/10)
- [Support for Jellyfin Instant Mixes](https://github.com/anultravioletaurora/Jellify/issues/50) - [CarPlay / Android Auto Support](https://github.com/anultravioletaurora/Jellify/issues/5)
- [Shared, Public, and Collaborative Playlists](https://github.com/anultravioletaurora/Jellify/issues/175) - [Support for Jellyfin Instant Mixes](https://github.com/anultravioletaurora/Jellify/issues/50)
- [Web / Desktop support](https://github.com/anultravioletaurora/Jellify/issues/71) - [Shared, Public, and Collaborative Playlists](https://github.com/anultravioletaurora/Jellify/issues/175)
- [Watch (Apple Watch / WearOS) Support](https://github.com/anultravioletaurora/Jellify/issues/61) - [Web / Desktop support](https://github.com/anultravioletaurora/Jellify/issues/71)
- [TV (Android, Apple, Samsung) Support](https://github.com/anultravioletaurora/Jellify/issues/85) - [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! ## 👀 Lemme see!
### Home ### Home
Home Home
<img src="screenshots/home.png" alt="Jellify Home" width="275" height="600"> <img src="screenshots/home.png" alt="Jellify Home" width="275" height="600">
### Library ### Library
Library Library
<img src="screenshots/library.png" alt="Library" width="275" height="600"> <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"> <img src="screenshots/album_artists.png" alt="Album Artists" width="275" height="600">
Track Options Track Options
<img src="screenshots/track_options.png" alt="Track Options" width="275" height="600"> <img src="screenshots/track_options.png" alt="Track Options" width="275" height="600">
Playlist Playlist
@@ -84,9 +93,11 @@ Playlist
<img src="screenshots/playlist.png" alt="Playlist" width="275" height="600"> <img src="screenshots/playlist.png" alt="Playlist" width="275" height="600">
### Search ### Search
<img src="screenshots/search.png" alt="Search" width="275" height="600"> <img src="screenshots/search.png" alt="Search" width="275" height="600">
### Player ### Player
<img src="screenshots/player.png" alt="Player" width="275" height="600"> <img src="screenshots/player.png" alt="Player" width="275" height="600">
<img src="screenshots/player_queue.png" alt="Queue" 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”'> <img src='screenshots/favorite_track.png' alt='Favorite Track' width='275' height='600”'>
### CarPlay (Sneak Preview) ### CarPlay (Sneak Preview)
<img src="screenshots/carplay_nowplaying.jpeg" alt="Now Playing (CarPlay)" width="500" height="350"> <img src="screenshots/carplay_nowplaying.jpeg" alt="Now Playing (CarPlay)" width="500" height="350">
### On the Server ### On the Server
<img src="https://github.com/user-attachments/assets/741884a2-b9b7-4081-b3a0-6655d08071dc" alt="Playback Tracking" width="300" height="200"> <img src="https://github.com/user-attachments/assets/741884a2-b9b7-4081-b3a0-6655d08071dc" alt="Playback Tracking" width="300" height="200">
## 🏗 Built with good stuff ## 🏗 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”) [![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 ### 🎨 Frontend
[Tamagui](https://tamagui.dev/)\ [Tamagui](https://tamagui.dev/)\
[Burnt](https://github.com/nandorojo/burnt)\ [Burnt](https://github.com/nandorojo/burnt)\
[React Navigation](https://reactnavigation.org/)\ [React Navigation](https://reactnavigation.org/)\
@@ -109,9 +125,11 @@ Playlist
[React Native Draggable Flatlist](https://github.com/computerjazz/react-native-draggable-flatlist)\ [React Native Draggable Flatlist](https://github.com/computerjazz/react-native-draggable-flatlist)\
[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)\ [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)\
[React Native Vector Icons](https://github.com/oblador/react-native-vector-icons) [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 ### 🎛️ Backend
[Expo SDK](https://expo.dev/)\ [Expo SDK](https://expo.dev/)\
[Jellyfin SDK](https://typescript-sdk.jellyfin.org/)\ [Jellyfin SDK](https://typescript-sdk.jellyfin.org/)\
[Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/react-native)\ [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) [React Native URL Polyfill](https://github.com/charpeni/react-native-url-polyfill)
### 👩‍💻 Monitoring ### 👩‍💻 Monitoring
[GlitchTip](https://glitchtip.com/) [GlitchTip](https://glitchtip.com/)
### 💜 Love from Wisconsin 🧀 ### 💜 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 :) 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 ## 🏃Running Locally
### ⚛️ Universal Dependencies ### ⚛️ 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 ### 🍎 iOS
#### Dependencies #### Dependencies
- [Xcode](https://developer.apple.com/xcode/) for building
- [Xcode](https://developer.apple.com/xcode/) for building
#### Instructions #### Instructions
##### Setup ##### Setup
- Clone this repository
- Run `npm run init` to initialize the project - Clone this repository
- This will install `npm` packages, install `bundler` and required gems, and installs CocoaPods - Run `npm run init` to initialize the project
- In the `ios` directory, run `fastlane match development --readonly` to fetch the development signing certificates - This will install `npm` packages, install `bundler` and required gems, and installs CocoaPods
- *You will need access to the *Jellify Signing* private repository* - 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 ##### Running
- Run `npm run start` to start the dev server
- Open the `Jellify.xcodeworkspace` with Xcode, *not* the `Jellify.xcodeproject` - Run `npm run start` to start the dev server
- Run either on a device or in the simulator - Open the `Jellify.xcodeworkspace` with Xcode, _not_ the `Jellify.xcodeproject`
- *You will need to wait for Xcode to finish it's "Indexing" step* - Run either on a device or in the simulator
- _You will need to wait for Xcode to finish it's "Indexing" step_
##### Building ##### 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 ### 🤖 Android
#### Dependencies #### 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 #### Instructions
##### Setup ##### Setup
- Clone this repository
- Run `npm i` to install `npm` packages - Clone this repository
- Run `npm i` to install `npm` packages
##### Running ##### Running
- Run `npm run start` to start the dev server
- Open the `android` folder with Android Studio - Run `npm run start` to start the dev server
- *Android Studio should automatically grab the "Run Configurations" and initialize Gradle* - Open the `android` folder with Android Studio
- Run either on a device or in the simulator - _Android Studio should automatically grab the "Run Configurations" and initialize Gradle_
- Run either on a device or in the simulator
##### Building ##### 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 #### 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 ## 🙏 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) - The [Jellyfin Team](https://jellyfin.org/) for making this possible with their software, SDKs, and unequivocal helpfulness.
- [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. - Extra thanks to [Niels](https://github.com/nielsvanvelzen) and [Bill](https://github.com/thornbill)
- James [API Blog Post](https://jmshrv.com/posts/jellyfin-api/) proved to be exceptionally valuable during development - [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.
- The folks in the [Margelo Community Discord](https://discord.com/invite/6CSHz2qAvA) for their assistance - James [API Blog Post](https://jmshrv.com/posts/jellyfin-api/) proved to be exceptionally valuable during development
- Extra thanks to [Ritesh](https://github.com/riteshshukla04) for your help, knowledge, and guidance - 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 - Extra thanks to [Ritesh](https://github.com/riteshshukla04) for your help, knowledge, and guidance
- 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 - [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
- Extra thanks to [John Grant](https://github.com/johngrantdev) for shaping and designing the user experience in many places - 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
- The friends I made along the way that have been critical in fostering an amazing community around *Jellify* - Extra thanks to [John Grant](https://github.com/johngrantdev) for shaping and designing the user experience in many places
- [Thalia](https://github.com/PercyGabriel1129) - The friends I made along the way that have been critical in fostering an amazing community around _Jellify_
- [BotBlake](https://github.com/BotBlake) - [Thalia](https://github.com/PercyGabriel1129)
- 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 - [BotBlake](https://github.com/BotBlake)
- Tony (iOS, Android) - 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
- Trevor (Android) - Tony (iOS, Android)
- [Laine](https://github.com/lainie-ftw) (Android) - Trevor (Android)
- [Jordan](https://github.com/jordanbleu) (iOS) - [Laine](https://github.com/lainie-ftw) (Android)
- My best(est) friend [Alyssa](https://www.instagram.com/uhh.lyssarae?igsh=MTRmczExempnbjBwZw==), for your design knowledge and for making various artwork for *Jellify*. - [Jordan](https://github.com/jordanbleu) (iOS)
- Youve been instrumental in shaping its user experience, my rock during development, and an overall inspiration in my life - 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 # Installation
@@ -29,8 +28,6 @@ Runs all the tests
[bundle exec] fastlane android build [bundle exec] fastlane android build
``` ```
### android deploy ### android deploy
```sh ```sh
@@ -39,7 +36,7 @@ Runs all the tests
Deploy a new version to the Google Play 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. 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, "migIndex": 1,
"data": [ "data": [
{ {
"path": "assets/fonts/Aileron-Black.otf", "path": "assets/fonts/Aileron-Black.otf",
"sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999" "sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999"
}, },
{ {
"path": "assets/fonts/Aileron-BlackItalic.otf", "path": "assets/fonts/Aileron-BlackItalic.otf",
"sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73" "sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73"
}, },
{ {
"path": "assets/fonts/Aileron-Bold.otf", "path": "assets/fonts/Aileron-Bold.otf",
"sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a" "sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a"
}, },
{ {
"path": "assets/fonts/Aileron-BoldItalic.otf", "path": "assets/fonts/Aileron-BoldItalic.otf",
"sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e" "sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e"
}, },
{ {
"path": "assets/fonts/Aileron-Heavy.otf", "path": "assets/fonts/Aileron-Heavy.otf",
"sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6" "sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6"
}, },
{ {
"path": "assets/fonts/Aileron-HeavyItalic.otf", "path": "assets/fonts/Aileron-HeavyItalic.otf",
"sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259" "sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259"
}, },
{ {
"path": "assets/fonts/Aileron-Italic.otf", "path": "assets/fonts/Aileron-Italic.otf",
"sha1": "338b043581d997314a4a03924ed30ff6461fd37e" "sha1": "338b043581d997314a4a03924ed30ff6461fd37e"
}, },
{ {
"path": "assets/fonts/Aileron-Light.otf", "path": "assets/fonts/Aileron-Light.otf",
"sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad" "sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad"
}, },
{ {
"path": "assets/fonts/Aileron-LightItalic.otf", "path": "assets/fonts/Aileron-LightItalic.otf",
"sha1": "48a4355b8792657845b3b0cd39c42994923a117a" "sha1": "48a4355b8792657845b3b0cd39c42994923a117a"
}, },
{ {
"path": "assets/fonts/Aileron-Regular.otf", "path": "assets/fonts/Aileron-Regular.otf",
"sha1": "5a78965873fbce38941cd3da109280af89a42de5" "sha1": "5a78965873fbce38941cd3da109280af89a42de5"
}, },
{ {
"path": "assets/fonts/Aileron-SemiBold.otf", "path": "assets/fonts/Aileron-SemiBold.otf",
"sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824" "sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824"
}, },
{ {
"path": "assets/fonts/Aileron-SemiBoldItalic.otf", "path": "assets/fonts/Aileron-SemiBoldItalic.otf",
"sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f" "sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f"
}, },
{ {
"path": "assets/fonts/Aileron-Thin.otf", "path": "assets/fonts/Aileron-Thin.otf",
"sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c" "sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c"
}, },
{ {
"path": "assets/fonts/Aileron-ThinItalic.otf", "path": "assets/fonts/Aileron-ThinItalic.otf",
"sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214" "sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214"
}, },
{ {
"path": "assets/fonts/Aileron-UltraLight.otf", "path": "assets/fonts/Aileron-UltraLight.otf",
"sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24" "sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24"
}, },
{ {
"path": "assets/fonts/Aileron-UltraLightItalic.otf", "path": "assets/fonts/Aileron-UltraLightItalic.otf",
"sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0" "sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0"
} }
] ]
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,32 +1,32 @@
import { fonts } from "@tamagui/config/v4"; import { fonts } from '@tamagui/config/v4'
import { createFont } from "tamagui"; import { createFont } from 'tamagui'
const aileronFace = { const aileronFace = {
100: { normal: 'Aileron-UltraLight', italic: 'Aileron UltraLight Italic' }, 100: { normal: 'Aileron-UltraLight', italic: 'Aileron UltraLight Italic' },
200: { normal: 'Aileron-Thin', italic: 'Aileron Thin Italic' }, 200: { normal: 'Aileron-Thin', italic: 'Aileron Thin Italic' },
300: { normal: 'Aileron-Light', italic: 'Aileron Light Italic' }, 300: { normal: 'Aileron-Light', italic: 'Aileron Light Italic' },
400: { normal: 'Aileron-Regular', italic: 'Aileron Italic'} , 400: { normal: 'Aileron-Regular', italic: 'Aileron Italic' },
500: { normal: 'Aileron-Regular', italic: 'Aileron Italic' }, 500: { normal: 'Aileron-Regular', italic: 'Aileron Italic' },
600: { normal: 'Aileron-SemiBold', italic: 'Aileron SemiBold Italic' }, 600: { normal: 'Aileron-SemiBold', italic: 'Aileron SemiBold Italic' },
700: { normal: 'Aileron-Bold', italic: 'Aileron Bold Italic' }, 700: { normal: 'Aileron-Bold', italic: 'Aileron Bold Italic' },
800: { normal: 'Aileron-Heavy', italic: 'Aileron Heavy Italic' }, 800: { normal: 'Aileron-Heavy', italic: 'Aileron Heavy Italic' },
900: { normal: 'Aileron-Black', italic: 'Aileron-BlackItalic' } 900: { normal: 'Aileron-Black', italic: 'Aileron-BlackItalic' },
}; }
export const bodyFont = createFont({ export const bodyFont = createFont({
family: "Aileron-Bold", family: 'Aileron-Bold',
size: fonts.body.size, size: fonts.body.size,
lineHeight: fonts.body.lineHeight, lineHeight: fonts.body.lineHeight,
weight: fonts.body.weight, weight: fonts.body.weight,
letterSpacing: fonts.body.letterSpacing, letterSpacing: fonts.body.letterSpacing,
face: aileronFace face: aileronFace,
}) })
export const headingFont = createFont({ export const headingFont = createFont({
family: "Aileron-Black", family: 'Aileron-Black',
size: fonts.heading.size, size: fonts.heading.size,
lineHeight: fonts.heading.lineHeight, lineHeight: fonts.heading.lineHeight,
weight: fonts.heading.weight, weight: fonts.heading.weight,
letterSpacing: fonts.heading.letterSpacing, letterSpacing: fonts.heading.letterSpacing,
face: aileronFace 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 // 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 'react-native-gesture-handler'
import {AppRegistry} from 'react-native'; import { AppRegistry } from 'react-native'
import App from './App'; import App from './App'
import {name as appName} from './app.json'; import { name as appName } from './app.json'
import { PlaybackService } from './player/service' import { PlaybackService } from './player/service'
import TrackPlayer from 'react-native-track-player'; import TrackPlayer from 'react-native-track-player'
import Client from './api/client'; import Client from './api/client'
// Initialize API client instance // Initialize API client instance
Client.instance; /* eslint-disable @typescript-eslint/no-unused-expressions */
Client.instance
// Enable React Navigation freeze for detaching inactive screens // Enable React Navigation freeze for detaching inactive screens
// enableFreeze(); // enableFreeze();
AppRegistry.registerComponent(appName, () => App); AppRegistry.registerComponent(appName, () => App)
AppRegistry.registerComponent('RNCarPlayScene', () => App); AppRegistry.registerComponent('RNCarPlayScene', () => App)
// Register RNTP playback service for remote controls // 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" : { "info": {
"version" : 1, "version": 1,
"author" : "xcode" "author": "xcode"
} }
} }

View File

@@ -1,5 +1,4 @@
fastlane documentation ## fastlane documentation
----
# Installation # Installation
@@ -23,7 +22,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
Push a new beta build to TestFlight 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. 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, "migIndex": 1,
"data": [ "data": [
{ {
"path": "assets/fonts/Aileron-Black.otf", "path": "assets/fonts/Aileron-Black.otf",
"sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999" "sha1": "f9c3d80856ec2f23c8733b63edc9bd9d3051d999"
}, },
{ {
"path": "assets/fonts/Aileron-BlackItalic.otf", "path": "assets/fonts/Aileron-BlackItalic.otf",
"sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73" "sha1": "ffb617e90f50dfe1eb2bd4df80736d55ccacbb73"
}, },
{ {
"path": "assets/fonts/Aileron-Bold.otf", "path": "assets/fonts/Aileron-Bold.otf",
"sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a" "sha1": "9daa863f1c9a0f9efacd19fe9329c0fb9332ca7a"
}, },
{ {
"path": "assets/fonts/Aileron-BoldItalic.otf", "path": "assets/fonts/Aileron-BoldItalic.otf",
"sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e" "sha1": "13dbc6d1c10932eeacac7c680a7f71a25f6f821e"
}, },
{ {
"path": "assets/fonts/Aileron-Heavy.otf", "path": "assets/fonts/Aileron-Heavy.otf",
"sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6" "sha1": "56a9def7cf4ad3efefec7485be8cd95a265ab1f6"
}, },
{ {
"path": "assets/fonts/Aileron-HeavyItalic.otf", "path": "assets/fonts/Aileron-HeavyItalic.otf",
"sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259" "sha1": "23255fa29564f9757f779ba29c1d5649c2bf4259"
}, },
{ {
"path": "assets/fonts/Aileron-Italic.otf", "path": "assets/fonts/Aileron-Italic.otf",
"sha1": "338b043581d997314a4a03924ed30ff6461fd37e" "sha1": "338b043581d997314a4a03924ed30ff6461fd37e"
}, },
{ {
"path": "assets/fonts/Aileron-Light.otf", "path": "assets/fonts/Aileron-Light.otf",
"sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad" "sha1": "bf29e850d4c6dc3c73e46eb322f367c81ca07aad"
}, },
{ {
"path": "assets/fonts/Aileron-LightItalic.otf", "path": "assets/fonts/Aileron-LightItalic.otf",
"sha1": "48a4355b8792657845b3b0cd39c42994923a117a" "sha1": "48a4355b8792657845b3b0cd39c42994923a117a"
}, },
{ {
"path": "assets/fonts/Aileron-Regular.otf", "path": "assets/fonts/Aileron-Regular.otf",
"sha1": "5a78965873fbce38941cd3da109280af89a42de5" "sha1": "5a78965873fbce38941cd3da109280af89a42de5"
}, },
{ {
"path": "assets/fonts/Aileron-SemiBold.otf", "path": "assets/fonts/Aileron-SemiBold.otf",
"sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824" "sha1": "3c4affc8a57d6915e1255fd6c5312d1443bcc824"
}, },
{ {
"path": "assets/fonts/Aileron-SemiBoldItalic.otf", "path": "assets/fonts/Aileron-SemiBoldItalic.otf",
"sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f" "sha1": "46f85a5b66cf813651057ff2ed623527bdcd4b6f"
}, },
{ {
"path": "assets/fonts/Aileron-Thin.otf", "path": "assets/fonts/Aileron-Thin.otf",
"sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c" "sha1": "ee9d845c2b370a3ac00cfe402079233f8621ef9c"
}, },
{ {
"path": "assets/fonts/Aileron-ThinItalic.otf", "path": "assets/fonts/Aileron-ThinItalic.otf",
"sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214" "sha1": "31db89d81d0f354cc67dfc53bad54be5bfd44214"
}, },
{ {
"path": "assets/fonts/Aileron-UltraLight.otf", "path": "assets/fonts/Aileron-UltraLight.otf",
"sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24" "sha1": "ee4b6ef0bb1606ef950ba9acca0e78bb2cc2dc24"
}, },
{ {
"path": "assets/fonts/Aileron-UltraLightItalic.otf", "path": "assets/fonts/Aileron-UltraLightItalic.otf",
"sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0" "sha1": "8a34c35019102ac48f86fc0255d06c8ca05933d0"
} }
] ]
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,14 @@
const { getDefaultConfig } = require('@react-native/metro-config') const { getDefaultConfig } = require('@react-native/metro-config')
const config = getDefaultConfig(__dirname, { const config = getDefaultConfig(__dirname, {
// [Web-only]: Enables CSS support in Metro. // [Web-only]: Enables CSS support in Metro.
isCSSEnabled: true, isCSSEnabled: true,
}) })
// Expo 49 issue: default metro config needs to include "mjs" // Expo 49 issue: default metro config needs to include "mjs"
// https://github.com/expo/expo/issues/23180 // https://github.com/expo/expo/issues/23180
config.resolver.sourceExts.push('mjs') config.resolver.sourceExts.push('mjs')
config.watchFolders = [ config.watchFolders = ['components', 'api', 'player']
"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 { 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", "name": "jellify",
"version": "0.10.91", "version": "0.10.91",
"private": true, "private": true,
"scripts": { "scripts": {
"init": "npm i", "init": "npm i",
"android": "react-native run-android", "android": "react-native run-android",
"ios": "react-native run-ios", "ios": "react-native run-ios",
"lint": "eslint .", "lint": "eslint .",
"start": "react-native start", "start": "react-native start",
"test": "jest", "test": "jest",
"clean:ios": "cd ios && pod deintegrate", "clean:ios": "cd ios && pod deintegrate",
"clean:android": "cd android && rm -rf app/ build/", "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": "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: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:build": "cd ios && bundle exec fastlane build",
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta", "fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build", "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'", "androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@jellyfin/sdk": "^0.11.0", "@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1", "@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^15.1.3", "@react-native-community/cli": "^15.1.3",
"@react-native-masked-view/masked-view": "^0.3.2", "@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/native": "^7.0.14", "@react-navigation/native": "^7.0.14",
"@react-navigation/native-stack": "^7.1.1", "@react-navigation/native-stack": "^7.1.1",
"@react-navigation/stack": "^7.1.0", "@react-navigation/stack": "^7.1.0",
"@tamagui/config": "^1.124.17", "@tamagui/config": "^1.124.17",
"@tamagui/toast": "^1.124.17", "@tamagui/toast": "^1.124.17",
"@tanstack/query-sync-storage-persister": "^5.66.0", "@tanstack/query-sync-storage-persister": "^5.66.0",
"@tanstack/react-query": "^5.66.0", "@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-persist-client": "^5.66.0", "@tanstack/react-query-persist-client": "^5.66.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"bundle": "^2.1.0", "bundle": "^2.1.0",
"bundler": "^0.8.0", "bundler": "^0.8.0",
"burnt": "^0.12.2", "burnt": "^0.12.2",
"expo": "^52.0.0", "expo": "^52.0.0",
"expo-image": "^2.0.7", "expo-image": "^2.0.7",
"gem": "^2.4.3", "gem": "^2.4.3",
"invert-color": "^2.0.0", "invert-color": "^2.0.0",
"jest-expo": "^52.0.6", "jest-expo": "^52.0.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"npm-bundle": "^3.0.3", "npm-bundle": "^3.0.3",
"react": "18.3.1", "react": "18.3.1",
"react-freeze": "^1.0.4", "react-freeze": "^1.0.4",
"react-native": "0.77.0", "react-native": "0.77.0",
"react-native-background-actions": "^4.0.1", "react-native-background-actions": "^4.0.1",
"react-native-blurhash": "^2.1.1", "react-native-blurhash": "^2.1.1",
"react-native-boost": "^0.5.5", "react-native-boost": "^0.5.5",
"react-native-carplay": "^2.4.1-beta.0", "react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4", "react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.1", "react-native-draggable-flatlist": "^4.0.1",
"react-native-file-access": "^3.1.1", "react-native-file-access": "^3.1.1",
"react-native-gesture-handler": "^2.23.0", "react-native-gesture-handler": "^2.23.0",
"react-native-haptic-feedback": "^2.3.3", "react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "^2.12.2", "react-native-mmkv": "^2.12.2",
"react-native-reanimated": "^3.17.2", "react-native-reanimated": "^3.17.2",
"react-native-safe-area-context": "^5.2.0", "react-native-safe-area-context": "^5.2.0",
"react-native-screens": "^4.6.0", "react-native-screens": "^4.6.0",
"react-native-swipeable-item": "^2.0.9", "react-native-swipeable-item": "^2.0.9",
"react-native-text-ticker": "^1.14.0", "react-native-text-ticker": "^1.14.0",
"react-native-track-player": "^4.1.1", "react-native-track-player": "^4.1.1",
"react-native-url-polyfill": "^2.0.0", "react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3", "react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0", "react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1", "ruby": "^0.6.1",
"tamagui": "^1.124.17" "tamagui": "^1.124.17"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3", "@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0", "@babel/runtime": "^7.25.0",
"@react-native-community/cli-platform-android": "15.1.3", "@react-native-community/cli-platform-android": "15.1.3",
"@react-native-community/cli-platform-ios": "15.1.3", "@react-native-community/cli-platform-ios": "15.1.3",
"@react-native/babel-preset": "0.77.0", "@react-native/babel-preset": "0.77.0",
"@react-native/eslint-config": "0.77.0", "@react-native/eslint-config": "0.77.0",
"@react-native/metro-config": "0.77.0", "@react-native/metro-config": "0.77.0",
"@react-native/typescript-config": "0.77.0", "@react-native/typescript-config": "0.77.0",
"@types/jest": "^29.5.13", "@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10", "@types/lodash": "^4.17.10",
"@types/react": "^18.2.6", "@types/react": "^18.2.6",
"@types/react-native-vector-icons": "^6.4.18", "@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^18.3.1", "@types/react-test-renderer": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.29.1", "@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1", "@typescript-eslint/parser": "^8.29.1",
"babel-plugin-module-resolver": "^5.0.2", "babel-plugin-module-resolver": "^5.0.2",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.2", "eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.6", "eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0", "eslint-plugin-react-native": "^5.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^29.6.3", "jest": "^29.6.3",
"jscodeshift": "^0.15.2", "jscodeshift": "^0.15.2",
"lint-staged": "^15.5.0", "lint-staged": "^15.5.0",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"react-native-cli-bump-version": "^1.5.1", "react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "18.3.1", "react-test-renderer": "18.3.1",
"typescript": "5.7.3" "typescript": "5.7.3"
}, },
"lint-staged": { "lint-staged": {
"*.{js,jsx,ts,tsx}": [ "*.{js,jsx,ts,tsx}": [
"prettier --write", "prettier --write",
"eslint --fix" "eslint --fix"
] ]
}, },
"engines": { "engines": {
"node": ">=18" "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[] = [ export const CAPABILITIES: Capability[] = [
Capability.Pause, Capability.Pause,
Capability.Play, Capability.Play,
Capability.PlayFromId, Capability.PlayFromId,
Capability.SeekTo, Capability.SeekTo,
// Capability.JumpForward, // Capability.JumpForward,
// Capability.JumpBackward, // Capability.JumpBackward,
Capability.SkipToNext, Capability.SkipToNext,
Capability.SkipToPrevious, Capability.SkipToPrevious,
// Capability.Like, // Capability.Like,
// Capability.Dislike // Capability.Dislike
] ]

View File

@@ -1,61 +1,71 @@
import { Progress, State } from "react-native-track-player"; import { Progress, State } from 'react-native-track-player'
import { JellifyTrack } from "../types/JellifyTrack"; import { JellifyTrack } from '../types/JellifyTrack'
import { PlaystateApi } from "@jellyfin/sdk/lib/generated-client/api/playstate-api"; import { PlaystateApi } from '@jellyfin/sdk/lib/generated-client/api/playstate-api'
import { convertSecondsToRunTimeTicks } from "../helpers/runtimeticks"; import { convertSecondsToRunTimeTicks } from '../helpers/runtimeticks'
export async function handlePlaybackState(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, state: State) { export async function handlePlaybackState(
switch (state) { sessionId: string,
case (State.Playing) : { playstateApi: PlaystateApi,
console.debug("Report playback started") track: JellifyTrack,
await playstateApi.reportPlaybackStart({ state: State,
playbackStartInfo: { ) {
SessionId: sessionId, switch (state) {
ItemId: track.item.Id, case State.Playing: {
} console.debug('Report playback started')
}); await playstateApi.reportPlaybackStart({
break; playbackStartInfo: {
} SessionId: sessionId,
ItemId: track.item.Id,
case (State.Ended) : },
case (State.Paused) : })
case (State.Stopped) : { break
console.debug("Report playback stopped") }
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
}
});
break;
}
default : { case State.Ended:
return; 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) { export async function handlePlaybackProgressUpdated(
if (Math.floor(progress.duration - progress.position) === 5) { sessionId: string,
console.debug("Track finished, scrobbling..."); playstateApi: PlaystateApi,
await playstateApi.reportPlaybackStopped({ track: JellifyTrack,
playbackStopInfo: { progress: Progress,
SessionId: sessionId, ) {
ItemId: track.item.Id, if (Math.floor(progress.duration - progress.position) === 5) {
PositionTicks: convertSecondsToRunTimeTicks(track.duration!) console.debug('Track finished, scrobbling...')
} await playstateApi.reportPlaybackStopped({
}); playbackStopInfo: {
} else { SessionId: sessionId,
// DO NOTHING, reporting playback will just eat up power ItemId: track.item.Id,
// Jellyfin can keep track of progress, we're going to intentionally PositionTicks: convertSecondsToRunTimeTicks(track.duration!),
// only give it the "greatest hits" (i.e., anything that involves user interaction) },
// console.debug("Reporting playback position"); })
// await playstateApi.reportPlaybackProgress({ } else {
// playbackProgressInfo: { // DO NOTHING, reporting playback will just eat up power
// SessionId: sessionId, // Jellyfin can keep track of progress, we're going to intentionally
// ItemId: track.ItemId, // only give it the "greatest hits" (i.e., anything that involves user interaction)
// PositionTicks: convertSecondsToRunTimeTicks(progress.position) // 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 { isEmpty } from 'lodash'
import { QueuingType } from "../../enums/queuing-type"; import { QueuingType } from '../../enums/queuing-type'
import { JellifyTrack } from "../../types/JellifyTrack"; import { JellifyTrack } from '../../types/JellifyTrack'
import { getActiveTrackIndex } from "react-native-track-player/lib/src/trackPlayer"; import { getActiveTrackIndex } from 'react-native-track-player/lib/src/trackPlayer'
/** /**
* Finds and returns the index of the player queue to insert additional tracks into * 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 * @returns The index to insert songs to play next at
*/ */
export const findPlayNextIndexStart = async (playQueue: JellifyTrack[]) => { export const findPlayNextIndexStart = async (playQueue: JellifyTrack[]) => {
if (isEmpty(playQueue)) if (isEmpty(playQueue)) return 0
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 * @param playQueue The current player queue
* @returns The index to insert songs to add to the user queue * @returns The index to insert songs to add to the user queue
*/ */
export const findPlayQueueIndexStart = async (playQueue: JellifyTrack[]) => { export const findPlayQueueIndexStart = async (playQueue: JellifyTrack[]) => {
if (isEmpty(playQueue)) return 0
if (isEmpty(playQueue)) const activeIndex = await getActiveTrackIndex()
return 0;
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 playQueue.findIndex(
return activeIndex! + 1 (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 _ from 'lodash'
import { JellifyTrack } from "../../types/JellifyTrack"; 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)) console.debug(`Built new queue of ${newQueue.length} items`)
newQueue = tracksToInsert;
else {
newQueue = _.cloneDeep(existingQueue).splice(insertIndex, 0, ...tracksToInsert);
}
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 TrackPlayer, { RatingType } from 'react-native-track-player'
import { CAPABILITIES } from "../constants"; import { CAPABILITIES } from '../constants'
export const useUpdateOptions = async (isFavorite: boolean) => { export const useUpdateOptions = async (isFavorite: boolean) => {
return await TrackPlayer.updateOptions({ return await TrackPlayer.updateOptions({
progressUpdateEventInterval: 1, progressUpdateEventInterval: 1,
capabilities: CAPABILITIES, capabilities: CAPABILITIES,
notificationCapabilities: CAPABILITIES, notificationCapabilities: CAPABILITIES,
compactCapabilities: CAPABILITIES, compactCapabilities: CAPABILITIES,
ratingType: RatingType.Heart, ratingType: RatingType.Heart,
likeOptions: { likeOptions: {
isActive: isFavorite, isActive: isFavorite,
title: "Favorite" title: 'Favorite',
}, },
dislikeOptions: { dislikeOptions: {
isActive: !isFavorite, isActive: !isFavorite,
title: "Unfavorite" title: 'Unfavorite',
} },
}); })
} }

View File

@@ -1,23 +1,23 @@
import { JellifyTrack } from "../types/JellifyTrack"; import { JellifyTrack } from '../types/JellifyTrack'
import { QueuingType } from "../enums/queuing-type"; import { QueuingType } from '../enums/queuing-type'
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { Queue } from "./types/queue-item"; import { Queue } from './types/queue-item'
export interface QueueMutation { export interface QueueMutation {
track: BaseItemDto; track: BaseItemDto
index?: number | undefined; index?: number | undefined
tracklist: BaseItemDto[]; tracklist: BaseItemDto[]
queue: Queue; queue: Queue
queuingType?: QueuingType | undefined; queuingType?: QueuingType | undefined
} }
export interface AddToQueueMutation { export interface AddToQueueMutation {
track: BaseItemDto, track: BaseItemDto
queuingType?: QueuingType | undefined; queuingType?: QueuingType | undefined
} }
export interface QueueOrderMutation { export interface QueueOrderMutation {
newOrder: JellifyTrack[]; newOrder: JellifyTrack[]
from: number; from: number
to: 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 Client from '../api/client'
import { JellifyTrack } from "../types/JellifyTrack"; import { JellifyTrack } from '../types/JellifyTrack'
import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api"; import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api'
import TrackPlayer, { Event, RatingType } from "react-native-track-player"; import TrackPlayer, { Event, RatingType } from 'react-native-track-player'
import { getActiveTrack, getActiveTrackIndex } from "react-native-track-player/lib/src/trackPlayer"; import { getActiveTrack, getActiveTrackIndex } from 'react-native-track-player/lib/src/trackPlayer'
/** /**
* Jellify Playback Service. * Jellify Playback Service.
* *
* Sets up event listeners for remote control events and * Sets up event listeners for remote control events and
* runs for the duration of the app lifecycle * runs for the duration of the app lifecycle
*/ */
export async function PlaybackService() { 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 () => { TrackPlayer.addEventListener(Event.RemoteNext, async () => {
await TrackPlayer.play(); await TrackPlayer.skipToNext()
}); })
TrackPlayer.addEventListener(Event.RemotePause, async () => {
await TrackPlayer.pause();
});
TrackPlayer.addEventListener(Event.RemoteNext, async () => { TrackPlayer.addEventListener(Event.RemotePrevious, async () => {
await TrackPlayer.skipToNext(); await TrackPlayer.skipToPrevious()
}); })
TrackPlayer.addEventListener(Event.RemotePrevious, async () => { TrackPlayer.addEventListener(Event.RemoteSeek, async (event) => {
await TrackPlayer.skipToPrevious(); await TrackPlayer.seekTo(event.position)
}); })
TrackPlayer.addEventListener(Event.RemoteSeek, async (event) => { // TrackPlayer.addEventListener(Event.RemoteJumpForward, async (event) => {
await TrackPlayer.seekTo(event.position); // await TrackPlayer.seekBy(event.interval)
}); // });
// TrackPlayer.addEventListener(Event.RemoteJumpForward, async (event) => { // TrackPlayer.addEventListener(Event.RemoteJumpBackward, async (event) => {
// await TrackPlayer.seekBy(event.interval) // await TrackPlayer.seekBy(-event.interval)
// }); // });
// TrackPlayer.addEventListener(Event.RemoteJumpBackward, async (event) => { TrackPlayer.addEventListener(Event.RemoteLike, async () => {
// await TrackPlayer.seekBy(-event.interval) 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; await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, {
const nowPlayingIndex = await getActiveTrackIndex(); rating: RatingType.Heart,
})
})
await getUserLibraryApi(Client.api!) TrackPlayer.addEventListener(Event.RemoteDislike, async () => {
.markFavoriteItem({ const nowPlaying = (await getActiveTrack()) as JellifyTrack
itemId: nowPlaying.item.Id! const nowPlayingIndex = await getActiveTrackIndex()
});
await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, { await getUserLibraryApi(Client.api!).markFavoriteItem({
rating: RatingType.Heart itemId: nowPlaying.item.Id!,
}) })
});
TrackPlayer.addEventListener(Event.RemoteDislike, async () => { await TrackPlayer.updateMetadataForTrack(nowPlayingIndex!, {
rating: undefined,
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
});
});
}

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 { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { QueuingType } from "../../enums/queuing-type" import { QueuingType } from '../../enums/queuing-type'
export interface QueuingRequest { export interface QueuingRequest {
song: BaseItemDto song: BaseItemDto
queuingType: QueuingType queuingType: QueuingType
atIndex?: number atIndex?: number
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,32 +1,34 @@
import { PitchAlgorithm, RatingType, Track, TrackType } from "react-native-track-player" import { PitchAlgorithm, RatingType, Track, TrackType } from 'react-native-track-player'
import { QueuingType } from "../enums/queuing-type"; import { QueuingType } from '../enums/queuing-type'
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
export interface JellifyTrack extends Track { export interface JellifyTrack extends Track {
url: string; url: string
type?: TrackType | undefined; type?: TrackType | undefined
userAgent?: string | undefined; userAgent?: string | undefined
contentType?: string | undefined; contentType?: string | undefined
pitchAlgorithm?: PitchAlgorithm | undefined; pitchAlgorithm?: PitchAlgorithm | undefined
headers?: { [key: string]: any; } | undefined;
title?: string | undefined; /* eslint-disable @typescript-eslint/no-explicit-any */
album?: string | undefined; headers?: { [key: string]: any } | 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;
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
/** 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 * 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
QueuingType?: QueuingType | undefined ; * to play next by the user
} */
QueuingType?: QueuingType | undefined
}

View File

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